反射
反射: 通过一个对象少量信息获取其其余全部信息.
这里的对象是一个抽象的概念
通过反射可以实现什么?
- 获取一个类的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>
### 几个特殊代码块
测试类: 首先了解一下构造代码块/静态代码块/构造函数执行时机
```java
package 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 1
debug 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 } } }
测试代码:
```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, 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>
### 反射调用ProcessBuilder
Process的构造函数有如下两种形式:
- public ProcessBuilder(List<String> command) {}
- public ProcessBuilder(String... command) {}
反射调用第一种执行命令:
```java
List<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