Java反射总结

  • 反射中的方法

    1
    2
    3
    4
    public void execute(String className, String methodName) throws Exception {
    Class clazz = Class.forName(className);
    clazz.getMethod(methodName).invoke(clazz.newInstance());
    }
    • 获取类的方法

      forNameobj.getClass()

    • 实例化类对象的方法

      newInstance

    • 获取函数的方法

      getMethod

    • 执行函数的方法

      invoke

比如,上下文中如果只有Integet类型的数字,如何获取到可以执行命令的Runtime类?

1
2
Integer integer = 1;
integer.getClass().forName("java.lang.Runtime")
  • forName的两个函数重载

    • Class forName(String name)
    • Class forName(String name, **boolean** initialize, ClassLoader loader)
    1
    2
    3
    Class.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
    16
    public 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
    2
    Class clazz = Class.forName("java.lang.Runtime");
    clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");

    分解后

    1
    2
    3
    4
    5
    Class 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
    2
    Class 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
    2
    Class clazz = Class.forName("java.lang.ProcessBuilder");
    clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe")));
  • 可变长参数

    1
    2
    3
    public void hello(String[] names) {}
    // 等价
    public void hello(String...names) {}

    通过ProcessBuilder第二个构造函数完成实例化。

    1
    2
    Class clazz = Class.forName("java.lang.ProcessBuilder");
    clazz.getConstructor(String[].class);

    在调用newInstance的时候,因为这个函数本身接收的是一个可变长参数,我们传给ProcessBuilder的也是一个可变长参数,二者叠加为一个二维数组:

    1
    2
    Class clazz = Class.forName("java.lang.ProcessBuilder");
    ((ProcessBuilder)clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}})).start();

    完全反射:

    1
    2
    Class clazz = Class.forName("java.lang.ProcessBuilder");
    clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
  • getDeclared

    可以执行私有方法。

    • getMethodgetConstructor区别

      • getMethod

        获取的是当前类中所有公共方法,包括从父类继承的方法。

      • getDeclaredMethod

        获取的是当前类中声明的方法,包括私有方法,但不包括父类方法

    • 实例

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

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