JAVA反射

反射的概念

java反射机制不是很陌生的东西,总结一波反射中重要的概念:

  1. Java 反射机制允许运行中的Java程序获取自身的信息, 操作类和对象的内部属性.

==>“ 动态获取信息 “ 和 “ 动态调用属性方法 “

==>对象实例可以通过反射机制获取它的类(类类型)

==>类可以通过反射机制获取它的所有方法和属性

==>获取的属性可以设值,获取的方法可以调用

反射最重要的用途是开发各种通用框架 . 很多框架都是通过XML文件来进行配置的( 例如 struts.xml , spring-*.xml 等 ) , 即所谓的框架核心配置文件 . 为了确保框架的通用性 , 程序运行时需要根据配置文件中对应的内容加载不同的类或对象 , 调用不同的方法 , 这也依赖于 Java 反射机制 .

总的来说,通过反射我们可以::

  1. 获得一个对象所属的类
  2. 获得任意一个类的成员变量和方法
  3. 构造任意一个类的对象
  4. 任意调用一个对象的方法

纸上得来终觉浅,还是需要多实践来获得更深的理解

反射的具体应用,在上一篇里面写的很详细

通过反射RCE

java.lang.Runtime

第一个写的详细一点

image-20220304012210-bbgk8za.png

java.lang.Runtime 类的 exec() 方法是Java中最常见的执行命令的方式

我们可以通过forName方法来加载任何类,当然也可以加载Runtime。

调用exec方法的两个条件:

  1. 一个实例对象,可以通过newInstance方法拿到

  2. 获取到具体的exec方法,反射中要获取一个方法需要知道方法名和参数类型

方式1

1.获取exec方法

(这里抛出了很多异常,暂时不讨论)

image-20220304013024-fncppyt.png

2.获取实例对象然后尝试调用

image-20220304013242-v57tfyw.png

报错了,查看报错信息:

class com.io.EncodingDemo cannot access a member of class java.lang.Runtime (in module java.base) with modifiers “private”

跟进到Runtime方法里,发现他的构造方法是private,而newInstance会默认调用无参构造方法,这里牵扯到java的设计模式中的单例设计,比如在学习Servlet的时候遇到的ServletContext。

单例模式有 3 个特点:
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点。

http://c.biancheng.net/view/1338.html

因此不能直接调用newInstance,要通过单例模式设计对象中的获取实例的方法,来获取一个runtime实例

image-20220304013856-gl5a5gu.png

由于exec返回的是一个Process对象,我们要通过字节流把结果读出来

https://zhuanlan.zhihu.com/p/44957705

image-20220304015412-we3v4tx.png

方式2

1
2
3
4
5
6
7
8
9
10
public class EncodingDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException, IOException {
Class<?> cls = Class.forName("java.lang.Runtime");
Method execMethod = cls.getMethod("exec", String.class);
Method getRuntimeMethod = cls.getMethod("getRuntime");
Object runtime = getRuntimeMethod.invoke(cls);
execMethod.invoke(runtime,"calc.exe");

}
}

看Epicccal师傅的博客中得知

我们可以通过 对象.方法名 来调用实例方法 , 类名.方法名 来调用静态方法 , 那么反过来 , 方法名.invoke(对象) 可以映射成 方法名.invoke(类)

接着通过p牛的漫谈,思考下面几个问题

类没有无参构造方法,没有单例模式里的静态方法

我们可以使用getConstructor方法,获取构造函数信息,它被封装在java.lang.reflect.Constructor中,当然他也需要特定的函数名和参数列表来获取这个唯一的构造函数

获取到构造函数之后,用newInstance来执行

例如ProcessBuilder类

image-20220304111005-qs9ld14.png

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

这里进行了强制类型转换,也就是直接调用了实例之后的ProcessBuilder,我们也可用反射来调用start

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

构造方法是私有方法,如何执行

例子就是上面的Runtime类

我们使用getDeclaredMethod方法,它能获取的是当前类中“声明”的方法,包括了私有方法,但不包括从父类继承过来的方法

getMethod方法只能获取公共方法和从父类继承的方法

我们在获取到一个私有方法后,必须用setAccessible 修改它的作用域,否则仍然不能调用。

1
2
3
4
Class<?> clazz = Class.forName("java.lang.Runtime");
Constructor<?> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(c.newInstance(),"calc.exe");

java.lang.ProcessBuilder

上文的Runtime类的exec方法,实质上就是通过ProcessBuilder来执行命令,通过跟进exec方法我们可以发现:

image-20220304113001-sz96lzx.png

我们直接通过ProcessBuilder执行命令:

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

可以通过cmdarray,传入数组来执行带参数的数据

1
Arrays.asList("ping","127.0.0.1")

推荐阅读

王哥,我的超人

https://forum.butian.net/share/1333