java 反射研究
文章最后更新时间为:2021年06月22日 10:34:36
1. 为什么需要java反射
java 代码在 new 某个类生成对象之前,都需要先 import 这个类,否则在编译时 jvm 会提示找不到这个类。
但是有些场景需要在没有 import 这个类的情况下调用这个类的某个方法。这个时候可以使用 java 反射,在运行时动态获取类的方法、属性、父类、接口等。
反射是 java 漏洞利用的基础,我们一般使用 java.lang.Runtime 类去执行命令,但是一般代码中都不会用到这个类,所以我们需要利用反射动态调用 Runtime 的方法,比如 exec 方法,去执行任意命令。
2. 代码实现java反射
首先我们创建一个 Print 类,有两个构造方法,有参构造方法支持修改参数 s 。类的 PrintSome 方法打印出字符串 s。
package class1;
public class Print {
public String s ;
public Print(){
this.s = "我被调用了";
}
public Print(String st){
this.s = st;
}
public void PrintSome() {
System.out.println(this.s);
}
}
然后正常调用和反射调用这个类:
package class1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Revoke {
public static void main(String[] args) {
System.out.println("正常调用:");
Print p = new Print();
p.PrintSome();
System.out.println("反射调用:");
try{
execute("class1.Print","PrintSome");
}catch(Exception e){
e.printStackTrace();
}
System.out.println("end");
}
public static void execute(String className, String methodName) throws Exception {
// 1. 获取反射中的Class对象
Class clz = Class.forName(className); // 获取类的 Class 对象实例
// 2. 通过反射创建类对象
Constructor constructor = clz.getConstructor(); // 根据 Class 对象实例获取 Constructor 对象 (默认的构造方法)
Object object = constructor.newInstance(); // constructor 对象的 newInstance 方法获取反射类对象
// 3. 通过反射获取类属性、方法、构造器
Method method = clz.getMethod(methodName);
// 4. 利用 invoke 方法调用方法
method.invoke(object);
}
}
执行结果:
正常调用:
我被调用了
反射调用:
我被调用了
end
反射调用一个方法一般需要四个步骤
- 通过类名获取 Class 对象
- 通过反射创建类对象
- 通过反射获取类属性、方法、构造器
- 执行获取到的方法
下面依次来说这几个步骤的实现
2.1 通过类名获取Class对象
获取 Class 类对象有三种方法:
第一种也是最常用的方法,使用 Class.forName 静态方法。这种方法必须知道全路径。
Class clz = Class.forName("test1.Print");
第二种,使用 .class 方法。如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就
拿它的 class 属性即可。
Class clz = Print.class;
第三种,使用对象本身的 getClass() 方法。如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过
obj.getClass() 来获取它的类
String str = new Print();
Class clz = str.getClass();
2.2 通过反射创建类对象
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
第一种:通过 Class 对象的 newInstance() 方法。class.newInstance() 的作用就是调用这个类的无参构造函数。
Class clz = Print.class;
Print p = (Print)clz.newInstance();
第二种:通过 Constructor 对象的 newInstance() 方法
Class clz = Print.class;
Constructor constructor = clz.getConstructor();
Print p = (Print)constructor.newInstance();
通过 Constructor 对象创建类对象的好处是可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。比如
package class1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class PrintRevoke {
public static void main(String[] args)throws Exception {
Class clz = Class.forName("class1.Print");
Constructor constructor = clz.getConstructor(String.class);
Object object = constructor.newInstance("我是构造方法的参数");
Method method = clz.getMethod("PrintSome");
method.invoke(object);
}
}
2.3 通过反射获取类属性、方法、构造器
1.获取属性
// getFields() 方法可以获取 Class 类的公有属性
// getDeclaredFields() 方法则可以获取所有属性
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
2.获取方法
// 获取当前类和父类中声明的公有方法,后面的参数是函数的参数类型列表。
Method method = clz.getMethod(String methodName,Class<?>... parameterTypes);
// 获取当前类中声明的所有方法
Method method = clz.getDeclaredMethod(methodName);
3.4 函数执行
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
Object invoke(Object obj,Object...args)
- obj:实例化后的对象
- args:用于方法调用的参数
如果这个方法是一个普通方法,那么第一个参数是类对象
如果这个方法是一个静态方法,那么第一个参数无所谓了,传递一个null即可。
3. 反射调用Runtime
看完第二部分,下面我们开始尝试使用反射执行任意命令。最常见的是用Runtime弹个计算器,下面是正常调用Runtime执行命令:
package class1;
import java.lang.Runtime;
public class ExecRuntime {
public static void main(String[] args) throws Exception{
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
接下来我们尝试用反射来弹个计算器,按照上面的过程4步走:
package class1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ExecRuntime {
public static void main(String[] args) throws Exception{
// 1. 获取反射中的Class对象
Class clz = Class.forName("java.lang.Runtime");
// 2. 通过反射创建类对象
Constructor constructor = clz.getConstructor(); // 根据 Class 对象实例获取 Constructor 对象 (默认的构造方法)
Object object = constructor.newInstance(); // constructor 对象的 newInstance 方法获取反射类对象
// 3. 通过反射获取类方法
Method method = clz.getMethod("exec", String.class);
// 4. 利用 invoke 方法调用方法
method.invoke(object,"/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
执行的时候却发现执行出错了:
提示NoSuchMethod,导致使用 getConstructor 执行失败。其实是因为Runtime的构造方法是私有的,
有两种解决方法:
1.可以通过使用getDeclaredConstructor方法获取私有构造方法,再将其私有的构造方法设置为公有。
package class1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class ExecRuntime {
public static void main(String[] args) throws Exception{
// 1. 获取反射中的Class对象
Class clz = Class.forName("java.lang.Runtime");
// 2. 通过反射创建类对象
Constructor constructor = clz.getDeclaredConstructor(); // getDeclaredConstructor可以获取私有的构造函数
constructor.setAccessible(true); // 通过setAccessible(true)可以打破私有方法访问限制,从而进行调用
Object object = constructor.newInstance();
// 3. 通过反射获取类方法
Method method = clz.getMethod("exec", String.class);
// 4. 利用 invoke 方法调用方法
method.invoke(object,"/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
2.可以通过getRuntime方法返回一个对象。
package class1;
import java.lang.reflect.Method;
public class ExecRuntime {
public static void main(String[] args) throws Exception{
// 1. 获取反射中的Class对象
Class clz = Class.forName("java.lang.Runtime"); // 获取类的 Class 对象实例
// 2. 通过反射创建类对象
Method getRunTimeMethod = clz.getMethod("getRuntime");
Object object = getRunTimeMethod.invoke(clz);
// 3. 通过反射获取类方法
Method execMethod = clz.getMethod("exec", String.class);
// 4. 利用 invoke 方法调用方法
execMethod.invoke(object,"/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
成功执行命令。