Lemono
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

前言
Java反射是一种强大的机制,它允许程序在运行时动态地加载、检查和使用类,以及动态地调用类的方法、访问和修改类的属性。之前已经简单的学过反射的基本使用,但随着反序列化的深入学习,反射被用到的地方也越来越多,而很多东西都是在反射的基础上做伸展,比如动态代理、Spring的依赖注入等,因此这篇文章打算深入Java反射的了解和使用。
反射多种方式调用exec
在观看前需要了解最基本的Java反射知识,获取method、field等。
首先来认识下我们常见的命令执行的类Runtime.getRuntime().exec()
可以看到Runtime类中只存在一个无参的构造方法,且关键是为private属性,也就是说我们不能直接通过new来创建一个类对象。一般情况下,一个类的构造方法为public属性,这时就可以通过new Object()实例化一个对象,也就是通过类加载获取类,最终调用他的构造方法的过程。若构造方法为private属性时是没法访问到的,因此不能通过new Runtime()实例化对象,但是还好他提供了一个静态方法getRuntime
,从内部创建对象实例,如此便可利用currentRuntime
调用exec方法执行命令。
所以第一种最简单的调用方式:
//第一种方式:
Runtime.getRuntime().exec("calc");
第二种方式:基础反射调用类下的方法即可
//第二种方式:
Runtime runtime = Runtime.getRuntime();
Class<? extends Runtime> aClass = runtime.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime,"calc");
第三种方式:跳过从本身内部实例化对象,直接获取Runtime类,使用反射调用Constructor构造方法,最后再newInstance创建Runtime的对象实例。实例化一个对象对于后面反射调用方法是必要的,因为在最后invoke时需要传入两个参数,一个为实例化的对象,另一个是被调用方法传入的参数,往往第一个会成为最容易犯错和忽视的地方。
当创建了Runtime实例就不用调用getRuntime()方法了,直接反射调用exec方法执行命令即可。
//第三种方式
Class<Runtime> runtimeClass = Runtime.class;
Constructor<Runtime> runtime = runtimeClass.getDeclaredConstructor();
runtime.setAccessible(true);
Runtime runtime1 = runtime.newInstance();
Class<? extends Runtime> aClass = runtime1.getClass();
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(runtime1,"calc");
第四种方式实现与上一种大体一致,本身通过Class.forName()和.class都能实现反射。同样是上诉的方法创建Runtime实例,后面稍微绕了一下,先调用getRuntime方法,在利用该方法创建的Runtime实例反射调用exec执行命令。其实是大可不必的,这里也是为了加深反射调用的学习才多此一举。
//第四种方式
Class<?> aClass1 = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = aClass1.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Runtime runtime1 = (Runtime) declaredConstructor.newInstance();
Class<? extends Runtime> aClass2 = runtime1.getClass();
Method getRuntime = aClass2.getMethod("getRuntime");
Object invoke = getRuntime.invoke(runtime1);
Method method = invoke.getClass().getMethod("exec", String.class);
method.invoke(invoke,"calc");
反射调用私有内部类
再来看这样一个demo:
一个People类,私有有参构造方法,存在一个私有内部类Student,Student中存在私有无参有参构造和私有方法badBoy,如果说需要反射调用badBoy方法,那么阁下又该如何应对呢?
public class People {
private String name ;
private int age ;
private People(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello(){
System.out.println("Hello World!");
}
private void sayGoodBye() {
System.out.println("GoodBye World~");
}
private class Student {
public String username;
public int uid;
private Student() {
}
private Student(String username, int uid) {
this.username = username;
this.uid = uid;
}
private void goodBoy(String username) {
System.out.println(username + " is a good Boy~");
}
private void badBoy(String username) {
System.out.println(username + " is a bad Boy~");
}
}
}
先给出我的代码实现:
public class Main {
public static void main(String[] args) throws Exception{
Class<?> people_class = Class.forName("org.example.test.People");
Constructor<?> constructor = people_class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
People people = (People) constructor.newInstance("lemono",100);
Class<?> aClass = Class.forName("org.example.test.People$Student");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
//由于内部类隐含的持有一个外部类的引用,所以在使用反射获取内部类构造方法的时候。需要在参数中加入外部类的Class对象。
declaredConstructors[0].setAccessible(true);
Object o = declaredConstructors[0].newInstance(people);//private org.example.Person$Student(org.example.Person)默认存在一个参数
//不用数组形式实例化
// Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(people_class);//需带上参数
// declaredConstructor.setAccessible(true);
// Object o = declaredConstructor.newInstance(people);
Method declaredMethod = o.getClass().getDeclaredMethod("badBoy",String.class);
declaredMethod.setAccessible(true);
declaredMethod.invoke(o,"lemono");
}
}
首先需要实例化一个People对象,这是必要的,由于是私有构造方法 ,所以利用和上面是一样的了,只是有参的话需要在后面跟上参数类型的泛型。
随后是实例化内部类Student,对于内部类还是有一点区别,从类的获取上可以看到是使用的Class.forName("org.example.test.People$Student")
,在People后加$
访问Student,这是Java特有的形式,随后是反射调用构造方法实例化。而实际上内部类的构造方法是不同的,通过打印getDeclaredConstructors()
(获取类下所有构造方法包括私有,为一个数组) 可以看到,不论是无参构造还是有参,都会包含他的外部类作为第一个参数,即org.example.text.People
,所以对于反射方式实例化内部类的话,无参并不是真正的无参了。
总结
Java反射是Java中很强大的一个功能,即动态修改运行过程中对象的状态,甚至包括私有属性,也正是因为他的强大和灵活,导致一系列的安全问题。在反序列化过程中一定会被拿来动态修改某些值绕过检测判断,最终行成Godgate
,所以能够灵活运行就显得很重要了。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)