反射

反射: 通过一个对象少量信息获取其其余全部信息.

这里的对象是一个抽象的概念

通过反射可以实现什么?

  1. 获取一个类的java.lang.Class对象
    1. 根据类名获取类的java.lang.Class对象
    2. 根据类对象获取类java.lang.Class对象

获取到一个类对象的java.lang.Class之后可以获取它的任意方法.
下面是获取一个类java.lang.Class对象的三种方法,前两种是反射

  1. package reflect1;
  2. public class test {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. Class c0 = Class.forName("reflect1.test"); // 根据类名
  5. Class c1 = new test().getClass(); // 根据类对象
  6. Class c2 = test.class;
  7. }
  8. }
  1. 类似python的沙箱逃逸, 在Java中可以通过反射来实现类似的效果:

    1. 123.getClass().forName("java.lang.Runtime"); // 根据Integer获取执行命令的Runtime类
  2. 通过反射获取类和类方法并执行: ```java package reflect1;

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;

public class test { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class clazz = Class.forName(“reflect1.clz”); Method method = clazz.getMethod(“test”); method.invoke(clazz.newInstance()); } }

  1. <a name="G5Doq"></a>
  2. ## 反射加载恶意类
  3. **tips : Class.forName在根据类名加载类时并不要求该类已经import到代码中. **
  4. <a name="HAFT2"></a>
  5. ### 几个特殊代码块
  6. 测试类: 首先了解一下构造代码块/静态代码块/构造函数执行时机
  7. ```java
  8. package reflect1;
  9. public class ct {
  10. // 构造代码块: 每创建一个对象时就会执行一次,且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境
  11. {
  12. System.out.println("debug 1");
  13. }
  14. // 静态代码块: 随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化;
  15. static {
  16. System.out.println("debug 2");
  17. }
  18. // 构造函数: 每创建一个对象时就会执行一次。同时构造函数是给特定对象进行初始化,而构造代码是给所有对象进行初始化,作用区域不同
  19. public ct(){
  20. System.out.println("debug 3");
  21. }
  22. }

测试:

  1. package reflect1;
  2. public class test {
  3. public static void main(String[] args) throws ClassNotFoundException{
  4. Class.forName("reflect1.ct");
  5. System.out.println("== == == == == == ==");
  6. new ct();
  7. }
  8. }

结果:

  1. debug 2
  2. == == == == == == ==
  3. debug 1
  4. debug 3
  • 构造代码块: 每创建一个对象时就会执行一次,且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境
  • 静态代码块: 随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化;
  • 构造函数: 每创建一个对象时就会执行一次。同时构造函数是给特定对象进行初始化,而构造代码是给所有对象进行初始化,作用区域不同
  • 构造代码块会放在构造函数的Super的后面,但是在当前构造函数内容的前面.

    forName的重载

    Class.forName还有一个重载:
    1. Class.forName(className, true, currentLoader)
    第二个参数是加载类时是否运行静态代码块, 第三个参数是类加载器.
    测试: ```java package reflect1;

public class test { public static void main(String[] args) throws ClassNotFoundException{ Class.forName(“reflect1.ct”,false, ClassLoader.getSystemClassLoader()); System.out.println(“=== === ===”); Class.forName(“reflect1.ct”,true, ClassLoader.getSystemClassLoader()); } }

  1. 结果:
  2. ```java
  3. === === ===
  4. debug 2

恶意类

在静态代码块中添加恶意代码,这样这个类加载的时候就会执行这个恶意代码.

  1. public class calc {
  2. static {
  3. try {
  4. Runtime rt = Runtime.getRuntime();
  5. String command = "calc";
  6. Process pc = rt.exec(command);
  7. pc.waitFor();
  8. } catch (Exception e) {
  9. // pass
  10. }
  11. }
  12. }

image.png

反射tricks

反射加载内部类

测试类:

  1. package reflect1;
  2. public class cc1 {
  3. public class cc2 {
  4. public void test(){
  5. System.out.println("cc1#cc2#test\n");
  6. }
  7. }
  8. }

测试代码: 加载内部类和方法并执行,

  1. package reflect1;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. public class test {
  5. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
  6. Class clz1 = Class.forName("reflect1.cc1");
  7. Class clz2 = Class.forName("reflect1.cc1$cc2");
  8. Method m = clz2.getMethod("test");
  9. m.invoke(clz2.getDeclaredConstructors()[0].newInstance(clz1.newInstance()));
  10. }
  11. }
  • 内部类名称为cc1$cc2, 编译的时候会产生cc1$cc2.class文件

image.png

加载单例模式Class并执行

  1. package reflect1;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. public class test {
  5. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  6. Class clazz = Class.forName("java.lang.Runtime"); // 获取类
  7. Method execMethod = clazz.getMethod("exec", String.class); // 获取exec函数的一个重载
  8. Method getRuntimeMethod = clazz.getMethod("getRuntime"); // 获取单例模式入口
  9. Object runtime = getRuntimeMethod.invoke(clazz); // 获取类对象
  10. execMethod.invoke(runtime, "calc.exe"); // 执行exec
  11. }
  12. }
  • method.invoke:
    • 如果这个方法是一个普通方法,那么第一个参数是类对象
    • 如果这个方法是一个静态方法,那么第一个参数是类

      反射执行类私有方法

      测试类: ```java package reflect1;

public class calc { private void pm(){ try { Runtime.getRuntime().exec(“calc”); } catch (Exception e) { // pass } } }

  1. 测试代码:
  2. ```java
  3. package reflect1;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.lang.reflect.Method;
  6. public class test {
  7. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  8. Class clazz = Class.forName("reflect1.calc");
  9. Method calcMethod = clazz.getDeclaredMethod("pm");
  10. calcMethod.setAccessible(true);
  11. calcMethod.invoke(clazz.newInstance());
  12. }
  13. }

使用getDeclaredMethod获取私有方法,

  • getDeclaredMethod: 获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
  • getMethod: 获取的是当前类中所有公共方法,包括从父类继承的方法

使用setAccessible修改方法作用域.

反射执行类私有构造方法

测试类:

  1. package reflect1;
  2. public class calc {
  3. private calc(){
  4. }
  5. private void pm(){
  6. try {
  7. Runtime.getRuntime().exec("calc");
  8. } catch (Exception e) {
  9. // pass
  10. }
  11. }
  12. }

测试代码:

  1. package reflect1;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.InvocationTargetException;
  4. import java.lang.reflect.Method;
  5. public class test {
  6. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  7. Class clazz = Class.forName("reflect1.calc");
  8. Method calcMethod = clazz.getDeclaredMethod("pm");
  9. calcMethod.setAccessible(true);
  10. Constructor constructor = clazz.getDeclaredConstructor();
  11. constructor.setAccessible(true);
  12. calcMethod.invoke(constructor.newInstance());
  13. }
  14. }
  • getDeclaredConstructor

    加载包私有类并执行

    ProcessImpl为包私有类package-private ```java package test;

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map;

public class CmdExcute { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException { String[] cmds = {“calc”}; Class clz = Class.forName(“java.lang.ProcessImpl”); Method method = clz.getDeclaredMethod( “start”, String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class ); method.setAccessible(true); method.invoke(clz,cmds, null, null, null, false); } }

  1. <a name="Xztjj"></a>
  2. ### 反射调用ProcessBuilder
  3. Process的构造函数有如下两种形式:
  4. - public ProcessBuilder(List<String> command) {}
  5. - public ProcessBuilder(String... command) {}
  6. 反射调用第一种执行命令:
  7. ```java
  8. List<String> cmds = Arrays.asList("calc");
  9. Class clz = Class.forName("java.lang.ProcessBuilder");
  10. Constructor constructor = clz.getDeclaredConstructor(List.class);
  11. Method m = clz.getMethod("start");
  12. m.invoke(constructor.newInstance(cmds));

第二种: 比较特殊,因为存在一个可变参数. 对于可变参数,Java会在编译的时候把它编译成一个数组,所以下面两个等价:

  • public ProcessBuilder(String… command) {}
  • public ProcessBuilder(String[] command) {}

反射调用:

  1. package reflect1;
  2. import java.io.IOException;
  3. import java.lang.reflect.Constructor;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.lang.reflect.Method;
  6. public class test {
  7. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  8. // new ProcessBuilder("calc").start();
  9. Class clz = Class.forName("java.lang.ProcessBuilder");
  10. Constructor constructor = clz.getDeclaredConstructor(String[].class);
  11. constructor.setAccessible(true);
  12. Method m = clz.getMethod("start");
  13. m.invoke(constructor.newInstance(new String[][]{{"calc.exe"}}));
  14. }
  15. }

这里constructor.newInstance(new String[][]{{“calc.exe”}})传的是一个二维数组:

  1. newInstance本身接受可变参数
  2. ProcessBuilder的构造函数接受可变参数

    参考

    https://govuln.com/ - Java安全漫谈1,2,3