在一个类中没有无参构造方法,也没有类似单列模式留下的静态方法

我们可以使用新方法 getConstructor .

getMethod类似,getConstructor 接收的参数是构造函数列表类型,因为构造函数也支持重载,

所以必须用参数列表类型才能唯一确定一个构造函数。

我们常用的另一种执行方法是 ProcessBuilder,我们使用反射来获取其构造函数,然后调用**start()**来执行函数

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.Constructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();

ProcessBuilder有两个构造函数

  • public ProcessBuilder(List command)
  • public ProcessBuilder(String… command)

先讲上面那个进行反射转换

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));

image-20240218150012026

因为ProcessBuilder有两个构造函数,所以我们还有另外一种写法

public ProcessBuilder(String… command) 这个构造函数涉及到了变长参数(varargs)

对于可变长参数,Java其实在编译的时候会编译成一个数组,也就是说,如下这两种写法在底层是等价

的(也就不能重载):

public void Hello(String[] names){}
public void Hello(String...names){}

那么对于反射来说,如果要获取的目标函数里包含可变长参数,其实我们认为它是数组就行了。

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class)

在调用 newInstance 的时候,因为这个函数本身接收的是一个可变长参数,我们传给

ProcessBuilder 的也是一个可变长参数,二者叠加为一个二维数组,所以整个Payload如下:

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

进行反射转换

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));

image-20240218151451818

如果一个方法是构造方法或是私有方法,我们该如何执行

使用方法getDeclared系列的反射,与普通的getMethodgetConstructor的区别是:

  • getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
  • getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
Class<?> clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec",String.class).invoke(m.newInstance(),"calc.exe");

这里使用了一个方法 setAccessible ,这个是必须的。我们在获取到一个私有方法后,必须用

setAccessible 修改它的作用域,否则仍然不能调用