反射
反射: 通过一个对象少量信息获取其其余全部信息.
这里的对象是一个抽象的概念
通过反射可以实现什么?
- 获取一个类的java.lang.Class对象
- 根据类名获取类的java.lang.Class对象
- 根据类对象获取类java.lang.Class对象
获取到一个类对象的java.lang.Class之后可以获取它的任意方法.
下面是获取一个类java.lang.Class对象的三种方法,前两种是反射
package reflect1;public class test {public static void main(String[] args) throws ClassNotFoundException {Class c0 = Class.forName("reflect1.test"); // 根据类名Class c1 = new test().getClass(); // 根据类对象Class c2 = test.class;}}
类似python的沙箱逃逸, 在Java中可以通过反射来实现类似的效果:
123.getClass().forName("java.lang.Runtime"); // 根据Integer获取执行命令的Runtime类
通过反射获取类和类方法并执行: ```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()); } }
<a name="G5Doq"></a>## 反射加载恶意类**tips : Class.forName在根据类名加载类时并不要求该类已经import到代码中. **<a name="HAFT2"></a>### 几个特殊代码块测试类: 首先了解一下构造代码块/静态代码块/构造函数执行时机```javapackage reflect1;public class ct {// 构造代码块: 每创建一个对象时就会执行一次,且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境{System.out.println("debug 1");}// 静态代码块: 随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化;static {System.out.println("debug 2");}// 构造函数: 每创建一个对象时就会执行一次。同时构造函数是给特定对象进行初始化,而构造代码是给所有对象进行初始化,作用区域不同public ct(){System.out.println("debug 3");}}
测试:
package reflect1;public class test {public static void main(String[] args) throws ClassNotFoundException{Class.forName("reflect1.ct");System.out.println("== == == == == == ==");new ct();}}
结果:
debug 2== == == == == == ==debug 1debug 3
- 构造代码块: 每创建一个对象时就会执行一次,且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境
- 静态代码块: 随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化;
- 构造函数: 每创建一个对象时就会执行一次。同时构造函数是给特定对象进行初始化,而构造代码是给所有对象进行初始化,作用区域不同
- 构造代码块会放在构造函数的Super的后面,但是在当前构造函数内容的前面.
forName的重载
Class.forName还有一个重载:
第二个参数是加载类时是否运行静态代码块, 第三个参数是类加载器.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()); } }
结果:```java=== === ===debug 2
恶意类
在静态代码块中添加恶意代码,这样这个类加载的时候就会执行这个恶意代码.
public class calc {static {try {Runtime rt = Runtime.getRuntime();String command = "calc";Process pc = rt.exec(command);pc.waitFor();} catch (Exception e) {// pass}}}
反射tricks
反射加载内部类
测试类:
package reflect1;public class cc1 {public class cc2 {public void test(){System.out.println("cc1#cc2#test\n");}}}
测试代码: 加载内部类和方法并执行,
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 clz1 = Class.forName("reflect1.cc1");Class clz2 = Class.forName("reflect1.cc1$cc2");Method m = clz2.getMethod("test");m.invoke(clz2.getDeclaredConstructors()[0].newInstance(clz1.newInstance()));}}
- 内部类名称为cc1$cc2, 编译的时候会产生cc1$cc2.class文件
加载单例模式Class并执行
package reflect1;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class test {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {Class clazz = Class.forName("java.lang.Runtime"); // 获取类Method execMethod = clazz.getMethod("exec", String.class); // 获取exec函数的一个重载Method getRuntimeMethod = clazz.getMethod("getRuntime"); // 获取单例模式入口Object runtime = getRuntimeMethod.invoke(clazz); // 获取类对象execMethod.invoke(runtime, "calc.exe"); // 执行exec}}
- method.invoke:
public class calc { private void pm(){ try { Runtime.getRuntime().exec(“calc”); } catch (Exception e) { // pass } } }
测试代码:```javapackage reflect1;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class test {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class clazz = Class.forName("reflect1.calc");Method calcMethod = clazz.getDeclaredMethod("pm");calcMethod.setAccessible(true);calcMethod.invoke(clazz.newInstance());}}
使用getDeclaredMethod获取私有方法,
- getDeclaredMethod: 获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私有的方法,但从父类里继承来的就不包含了
- getMethod: 获取的是当前类中所有公共方法,包括从父类继承的方法
反射执行类私有构造方法
测试类:
package reflect1;public class calc {private calc(){}private void pm(){try {Runtime.getRuntime().exec("calc");} catch (Exception e) {// pass}}}
测试代码:
package reflect1;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class test {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class clazz = Class.forName("reflect1.calc");Method calcMethod = clazz.getDeclaredMethod("pm");calcMethod.setAccessible(true);Constructor constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);calcMethod.invoke(constructor.newInstance());}}
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); } }
<a name="Xztjj"></a>### 反射调用ProcessBuilderProcess的构造函数有如下两种形式:- public ProcessBuilder(List<String> command) {}- public ProcessBuilder(String... command) {}反射调用第一种执行命令:```javaList<String> cmds = Arrays.asList("calc");Class clz = Class.forName("java.lang.ProcessBuilder");Constructor constructor = clz.getDeclaredConstructor(List.class);Method m = clz.getMethod("start");m.invoke(constructor.newInstance(cmds));
第二种: 比较特殊,因为存在一个可变参数. 对于可变参数,Java会在编译的时候把它编译成一个数组,所以下面两个等价:
- public ProcessBuilder(String… command) {}
- public ProcessBuilder(String[] command) {}
反射调用:
package reflect1;import java.io.IOException;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class test {public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {// new ProcessBuilder("calc").start();Class clz = Class.forName("java.lang.ProcessBuilder");Constructor constructor = clz.getDeclaredConstructor(String[].class);constructor.setAccessible(true);Method m = clz.getMethod("start");m.invoke(constructor.newInstance(new String[][]{{"calc.exe"}}));}}
这里constructor.newInstance(new String[][]{{“calc.exe”}})传的是一个二维数组:
- newInstance本身接受可变参数
- ProcessBuilder的构造函数接受可变参数
参考
https://govuln.com/ - Java安全漫谈1,2,3
