反射中的方法
1
2
3
4public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}获取类的方法
forName
、obj.getClass()
实例化类对象的方法
newInstance
获取函数的方法
getMethod
执行函数的方法
invoke
比如,上下文中如果只有Integet类型的数字,如何获取到可以执行命令的Runtime类?
1 | Integer integer = 1; |
forName的两个函数重载
Class forName(String name)
Class forName(String name, **boolean** initialize, ClassLoader loader)
1
2
3Class.forName(className);
// 等于
Class.forName(className, true, currentLoader);第一个参数是类名,第二个参数表示是否初始化,第三个参数是一个加载器,告诉Java虚拟机如何加载这个类。
三种初始化方法
static {}
类初始化时调用。
{}
构造函数
首先调用
static{}
,其次是{}
,最后是构造函数。forName
中的initialize=true
其实就是告诉Java虚拟机是否执行类初始化。实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Demo1 {
static {
try {
Runtime rt = Runtime.getRuntime();
String commands = "whoami";
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("Demo1");
}
}forName
在正常情况下,想要使用一个类需要import才可以,而使用
forName
就不需要。$
符号$
的作用是查找内部类。通过Class.forName("C1$C2")
即可加载C1中的内部类C2。
newInstance
调用这个类的无参构造函数,如果失败,可能是以下原因:
- 没有无参构造函数
- 构造函数是私有的
getMethod
通过反射获取一个类的某个特定的公有方法,需要传递参数类型列表。
1
2// 获取 Runtime.exe(String command) 方法
class.getMethod("exec", String.class)invoke
作用:执行方法。它的第一个参数是:
- 如果这个方法是一个普通方法,那么第一个参数是类对象
- 如果这个方法是一个静态方法,那么第一个参数是类
1
2
3
4// 正常执行方法
clazz.method(...);
// 反射执行
method.invoke(...)实例
1
2Class clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");分解后
1
2
3
4
5Class clazz = Class.forName("java.lang.Runtime");
Method execMethod = clazz.getMethod("exec", String.class);
Method getRuntimeMethod = clazz.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(clazz);
execMethod.invoke(runtime, "calc.exe");getConstructor
接受的参数是构造函数列表类型,获取到构造函数后,我们用
newInstance
来执行。在一个类没有无参构造方法,也没有类似单例模式里的静态方法,可以通过
getConstructor
实例化。另一种执行命令的方式
ProcessBuilder
:1
2Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder) clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))).start();ProcessBuilder
ProcessBuilder
有两个构造函数public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
上面通过强制类型转换完成,有时候在利用漏洞的时候是没有这种语法的,需要通过反射完成。
1
2Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));可变长参数
1
2
3public void hello(String[] names) {}
// 等价
public void hello(String...names) {}通过
ProcessBuilder
第二个构造函数完成实例化。1
2Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getConstructor(String[].class);在调用
newInstance
的时候,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder
的也是一个可变长参数,二者叠加为一个二维数组:1
2Class clazz = Class.forName("java.lang.ProcessBuilder");
((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();完全反射:
1
2Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));getDeclared
可以执行私有方法。
与
getMethod
、getConstructor
区别getMethod
获取的是当前类中所有公共方法,包括从父类继承的方法。
getDeclaredMethod
获取的是当前类中声明的方法,包括私有方法,但不包括父类方法
实例
1
2
3
4Class clazz = Class.forName("java.lang.Runtime");
Constructor m = clazz.getDeclaredConstructor();
m.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(m.newInstance(), "calc.exe");获取到一个私有方法后,必须用
setAccessible
修改它的作用域,否则仍然不能调用。