JAVA command execution learning notes

preface

Today, let's learn about command execution in Java. Command execution is still widely used in the field of security. You have to understand and study it. If you forget some knowledge points used below, you can go to the previous article JAVA RMI learning notes ***

About exec

The Runtime class, as its name implies, is the Runtime environment. The exec method of Runtime is the most common way to execute commands in Java. Now let's analyze the execution process of exec

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class lmonstergg {

    public static void main(String[] args) throws IOException {
        Process p = Runtime.getRuntime().exec("whoami"); //Execute system commands
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}

The classic command executes the experimental code. We set the breakpoint in exec and then enter the

Here is another exec method. Go in and have a look

Another exec method is returned here. Continue to follow up

This returns a ProcessBuilder Object that calls the start method

The ProcessBuilder class is J2SE 1.5 in Java Lang, which is used to create operating system processes. It provides a way to start and manage processes (that is, applications). Before J2SE 1.5, the Process class was used to control and manage the Process. Each ProcessBuilder instance manages a Process property set. Its start() method uses these attributes to create a new Process instance. The start() method can be called repeatedly from the same instance to create a new child Process with the same or related properties.

It is not difficult to see from the code that the principle of exec() method executing commands is to create a child process executing commands through ProcessBuilder object

java.lang.Runtime

java. The exec() method of lang. runtime class is the most common way to execute commands in Java

1. Get class

Normally, if we want to get a class other than the system class, we must import it before using it Otherwise, errors such as cannot find symbol will appear However, when using, the system will not let you load classes at will

However, through class Forname (classname) does not have this restriction. We can load any class through the forName() method

As shown below

2. Get callable methods

Got Java After the lang. runtime class, we must want to know the methods that this class can call So through classname getMethods() , className. Getdeclaraedmethods() to get the methods of the class

From the output, we can find the exec() method we want to execute

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test {

    public String prt(String name)
    {
        return name;
    }
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        String name="lmonstergg";
        Class<?> cls=Class.forName("java.lang.Runtime");
        Method[] methods = cls.getMethods();
        Method[] declaredMethods = cls.getDeclaredMethods();
        System.out.println("getMethods Method of obtaining:");
        for(Method m:methods)
            System.out.println(m);
        //All methods obtained by getdeclaraedmethods() method
        System.out.println("getDeclaredMethods Method of obtaining:");
        for(Method m:declaredMethods)
            System.out.println(m);
    }
}

3. Call method

After you get the class and the method of the class, you can instantiate the object through reflection and call the method through invoke()

It is found that the program threw an exception, and the error message is: Exception in thread "main" Java lang.IllegalAccessException: Class test7 can not access a member of class java. lang.Runtime with modifiers “private”

The result is that the test7 class cannot access Java Member variable / member function with "private" modifier in lang.runtime class

It's interesting here. When outputting the methods of the class in the second step, the modifiers of the exec() method are all "public" So where did the "private" modifier come from?

Through CLS When newinstance() constructs an instance object, it will call the parameterless constructor by default Is this parameterless constructor private?

Let's look at the source code of a Java project lang.Runtime

As you can see, Java The constructor of lang. runtime class does use the "private" modifier We know that the method modified by the "private" modifier can only be called in the current class, and the outside is invisible So why do designers do this?

I don't know about Java design patterns, so I quote a passage from master Phith0n, which is very vivid

This is a common design pattern , go by the name of "Singleton mode / Factory mode"

Similar to one Web application , The database connection should only be established once when the service is started , Instead of establishing a connection every time you access the database .

Therefore, developers can write the database connection in the constructor , And give the function "private" Modifier  . Then write a static method to get the connection .

such , The constructor is called only once when the class is initialized , Establish database connection . Later, you only need to obtain the database connection through the static method , Avoid establishing multiple database links .

When we look at the code again, we find that there is indeed a static method of getRuntime() and returns Java Instance object of lang.runtime


You may wonder, the construction method here is Runtime(), and the instantiation process is not written in the constructor?

Personally, I don't think this is important. I just need to ensure that the instantiation process is only carried out once. Taking Java reflection as an example, the content in the static {} code block will be executed during class initialization (see the beginning of this article for details), so the instantiation process will be executed once Since the process is given the "private" modifier, it can no longer be accessed later The result is the same

This also leads to class Two key points for successful execution of newinstance() method:

Class must have a parameterless constructor .

The constructor of a class cannot be private , That is, you can't pass "private" Modifier to decorate the constructor .

With these conclusions, we can execute the exec() method through the Java reflection mechanism

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test {

    public String prt(String name)
    {
        return name;
    }
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        String name="lmonstergg";
        Class<?> cls=Class.forName("java.lang.Runtime");
        Method mgetruntime=cls.getMethod("getRuntime");
        Method mexec=cls.getMethod("exec",String.class);
        Object obj=mgetruntime.invoke(null);
        Process p=(Process)mexec.invoke(obj,"ipconfig");
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}


The command was executed successfully. Next, let's analyze the code

adopt Method mGetRuntime = cls.getMethod("getRuntime"); and Method mExec = cls.getMethod("exec",String.class); Get separately getRuntime() Methods and exec() method.

adopt getRuntime() of invoke(null) Method acquisition Runtime Instance object . Because the static method is called , So omit obj parameter , because getRuntime() Method has no parameters , So the parameter array here is null .

adopt exec() of invoke(obj , args[]) Method to execute the command . here obj yes Runtime Instance object , Obtained from the previous step , Parameters are system commands "ifconfig" .

Gets the byte stream of the execution result , Process it into a character stream , Last output string .

Some supplement

As for the above code, reading one byte at a time is not the most efficient way to read the stream. Many streams support reading multiple bytes to the buffer at one time. For file and network streams, using the buffer to read multiple bytes at one time is often much more efficient.

About object obj = mgetruntime invoke(null); This point

Why is the method parameter of invoke() a Class?

We can use objects Method name to call instance method, class name Method name to call a static method, and vice versa The invoke (object) cannot be mapped to a method name Invoke (class)

java.lang.ProcessBuilder

From the above content, we know that the exec method of Runtime executes the command. In fact, it calls the start method of ProcessBuilder to create a process and execute the command. In this case, we can consider executing the command directly through the ProcessBuilder class

Let's first look at the constructor of the ProcessBuilder class

You can see that the ProcessBuilder class has two constructors. The definitions of these two functions are very simple. One is used to execute commands without parameters and the other is used to execute commands with parameters. Note: the type of command parameter is List

So, let's call Java lang.ProcessBuilder. The start () method creates a child process to execute the command

Execute system commands without parameters

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        String name="lmonstergg";
        Class<?> cls=Class.forName("java.lang.ProcessBuilder");
        //The above paragraph gets the class
        Object obj=cls.getConstructor(List.class).newInstance(Arrays.asList("ipconfig"));
        //The above paragraph uses classname getConstructor( parameterType ). Newinstance (parametername) mode constructs an object whose parameters are ipconfig, arrays Aslist is used to convert to List
        Method mstart=cls.getMethod("start");
        //The above paragraph gets the start method
        Process p=(Process)mstart.invoke(obj);
        //Call this method
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}

adopt className.getConstructor( parameterType ).newInstance( parameterName ) To call with parameters parameter Constructor for

because cmdarray The type of the parameter is List , So the type of command we execute must also be List , You can use Arrays.asList() Method converts a variable length parameter or array to List type .

because start() Method has no parameters , So call directly Method.invoke(obj) That's it

Execute the system command with parameters

There are two methods to execute system commands with parameters. In fact, they are how to use the two constructors of ProcessBuilder to execute system commands respectively. Let's talk about them respectively

1.public ProcessBuilder(List command)

It's still through arrays Aslist() method Since the parameter of this method can be a variable length parameter, we can directly write the system command carrying the parameter to an array, and then convert it into a list through this method

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        String name="lmonstergg";
        Class<?> cls=Class.forName("java.lang.ProcessBuilder");
        //The above paragraph gets the class
        Object obj=cls.getConstructor(List.class).newInstance(Arrays.asList("netstat","-r"));
        //The above paragraph uses classname getConstructor( parameterType ). Newinstance (parametername) mode constructs an object whose parameters are ipconfig, arrays Aslist is used to convert to List
        Method mstart=cls.getMethod("start");
        //The above paragraph gets the start method
        Process p=(Process)mstart.invoke(obj);
        //Call this method
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}

2.public ProcessBuilder(String... command)

This is the second constructor of the ProcessBuilder class mentioned above, that is, the constructor specially used to execute system commands with parameters

It can be seen that the parameter of the construction method is also a variable length parameter The variable length parameter represents an indefinite length parameter, which means that the formal parameter can accept multiple parameter values. Multiple parameter values are passed in as an array, so

public void test(String[] names)
Equivalent to
public void test(String ... names)

For details about variable parameters, see here

Therefore, we can set string [] Class is passed as a parameter to the getConstructor() method and tells ProcessBuilder to call the second constructor to process the system command with parameters

That's what master Phith0n wrote in his document

However, it cannot be compiled successfully Javac will throw an exception!

Warning: non-varargs call of varargs method with inexact argument type for last parameter

warning: The last argument uses an imprecise variable type varargs Non of method varargs call

Where varargs represents variable parameters, what does this error mean?

Javac under Windows gives a hint: for varargs calls, Object should be used

So we wrote it this way

// Defines a one - dimensional array instance that contains the commands to execute-
String[] command = new String[]{"uname","-a"};
// Create an Object array and assign the reference of the array instance created in the previous step to the Object array
Object cmds[] = new Object[]{command};

If you have any questions about the above writing, you can refer to it Discussion on Java Object array reference One article Combine the above two steps together to form the following line of code

// Here, try to execute the uname command with the parameter - a
Object obj = new Object[]{new String[]{"uname","-a"}}

The final modified code is as follows:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {
        Object cmd[]=new Object[]{new String[]{"netstat","-r"}};
        //Object cmd[]=new Object [] {} to accept objects
        Class<?> cls=Class.forName("java.lang.ProcessBuilder");
        //The above paragraph gets the class
        Object obj=cls.getConstructor(String[].class).newInstance(cmd);
        //The above paragraph uses classname getConstructor( parameterType ). Newinstance (parametername) pattern construct object
        Method mstart=cls.getMethod("start");

        //The above paragraph gets the start method
        Process p=(Process)mstart.invoke(obj);
        //Call this method
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}


The system command was executed successfully

How to call private methods of a class

I'm talking about Java When lang.Runtime executes system commands, because the constructor Runtime() of this class is a private method, we cannot call this method. We can only return a Runtime instance object through the getRuntime() static method, and then call the exec() method The design pattern of "Singleton pattern" is also mentioned

In other words, we can't get the private constructor directly So is there another way to get private constructor?

java.lang.reflect.AccessibleObject.class: setAccessible(boolean flag)

Let's see how this method is defined in the official document

From this, we can know that when the parameter of this method is set to True, the Java language access check will be cancelled, that is, the check of public, protected, private and other modifiers will be cancelled

However, if the object is Java lang.Class. Constructor, an exception will be thrown That is, we cannot get the constructor method through the getConstructor() method

Then we can use getDeclaredConstructor() Method, which is similar to getConstructor() The biggest difference between methods is : This method returns all constructors of the specified parameter type . include public , protected as well as private Modifier decorated .

and getConstructor() Method returns only a subset of all construction methods , Namely public Modifier decorated .

Therefore, through the getdeclaraedconstructor () method, we can get the private constructor Runtime() In addition, when the Java language access check is turned off through setAccessible(boolean flag), no exception will be thrown

The code is as follows

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, IOException {

        Class<?> cls=Class.forName("java.lang.Runtime");
        //The above paragraph gets the class
        Method mexec=cls.getMethod("exec",String.class);
        Constructor<?> cst=cls.getDeclaredConstructor();
        cst.setAccessible(true);
        Object obj=cst.newInstance();

        //The above paragraph gets the start method
        Process p=(Process)mexec.invoke(obj,"netstat");
        //Call this method
        InputStream is=p.getInputStream();  //Gets the standard output stream of process p as the input byte stream
        InputStreamReader isr=new InputStreamReader(is);  //Convert byte stream to character stream
        BufferedReader br=new BufferedReader(isr);  //Provide a buffer for the character stream to read the whole block of data
        String line=null;
        while((line=br.readLine())!=null)
        {
            System.out.println(line);
        }
    }
}

Get the Runtime() construction method through the getDelclaredConstructor() method, close the Java language access check, and then build the instance object Finally, through method Invoke (obj, parameter) calls the exec() method.

In this way, you can directly access the private constructor of any class

Reference articles

Keywords: Java security

Added by mark bowen on Fri, 10 Dec 2021 19:04:00 +0200