前言:
java反射机制作用主要是在java执行过程中允许我们去构造任意类的的对象,我们可以对修改任意类的成员变量值,并调用任意对象的属性和方法。有点类似shellcode里的ROP链。
简单来说java反射机制就是通过获取Class对象然后使用java.lang.reflect里提供的方法操作Class对象,Class与java.lang.reflect最终实现了java反射
通过反射机制我们可以调用任意类的任意方法,且可以修改成员变量内容,常来做webshell的免杀和java反序列化漏洞执行命令等。
代码实现:
正射:
首先我们先了解下正常的调用方式:
我们创建main.java和people.java,代码如下:
main函数如下:
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception {
people peo = new people();
peo.num_people();
}
}
people函数如下:
public class people{
private String boy = "66";
int girl = 10;
public void num_people(){
System.out.println("boy is " + this.boy + " girl is " + this.girl);
}
}
正常我们的调用方式我们首先要new一个对象,然后才能去调用里面的方法,而且方法必须为public属性才可以调用。下面我们来了解下反射是如果实现上面的功能。
反射:
获取class对象三种方法:
1.使用.class方法
Class clazz = String.class;
2.使用getclass方法
String str = new String("Hello");
Class clazz = str.getClass();
3.使用Class.forName 静态方法
Class clazz = Class.forName("java.lang.String");
获取和修改字段和方法:
getField(String name) getFields() |
可以获取子类和父类的所有public变量 |
getDeclaredField(String name) getDeclaredFields() |
只能获取子类所有的public、protected和private类型的变量,不能获取父类的变量 |
field.get(Obj) field.get(null) |
获取Obj对象的field字段 获取static修饰的静态字段field的值 |
field.set(Obj,field_value) field.set(null,field_value) |
设置Obj对象的field字段值为field_value 设置static修饰的静态字段field的值为field_value |
setAccessible(boolean flag) |
当为private和protected时将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
getMethods() getMethod(String name, Class... parameterTypes) |
根据方法名和参数类型获取对应的方法,只能获取public类型方法,可以获取父类和子类方法 |
getDeclaredMethods() getDeclaredMethod(String name, Class... parameterTypes) |
获取子类所有的方法,包括public、protected和private,但是不能获取父类的方法 |
invoke(Object obj, Object[] args) |
执行通过反射获取的方法,obj代表要执行方法的对象,args代表方法的参数 |
构造函数获取和使用:
getConstructors() getConstructor(Class... parameterTypes) |
获取类所有的构造函数,只能获取public类型的字段,不能获取父类的构造函数。 |
getDeclaredConstructors() getDeclaredConstructor (Class... parameterTypes) |
获取类所有的构造函数,包括public、protected和private。不能获取父类的构造函数。 |
newInstance(Object ... initargs) |
newInstance函数是实现“虚拟构造器”的一种方法,使用该方法创建类,接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。 |
通过类名反射调用方法:
people.java:
public class people{
private String boy = "66";
int girl = 10;
private final String fin_boy = "99";
private void num_people(){
System.out.println("boy is " + this.boy + " girl is " + this.girl + " fin_boy is " + this.fin_boy);
}
}
main.java
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception {
test();
}
public static void test() throws Exception{
String class_name = "people";
String function_name = "num_people";
Class clazz = Class.forName(class_name);
Method method = Class.forName(class_name).getDeclaredMethod(function_name);
method.setAccessible(true);
method.invoke(clazz.newInstance());
}
}
这里使用forName获取了people的类,反射调用num_people方法,最后使用invoke发射执行方法
反射获取并修改参数值
people.java
public class people {
private String boy = "66";
int girl = 10;
private final String fin_boy = "99";
private final StringBuilder fin_boy2 = new StringBuilder("99");
private static final StringBuilder fin_boy3 = new StringBuilder("88");
public void num_people(){
System.out.println("boy is " + this.boy + " girl is " + this.girl + " fin_boy is " + this.fin_boy + "fin_boy2 is " + this.finboy2 + "fin_boy3 is " + this.finboy3);
}
}
main.java
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception {
test();
}
public static void test() throws Exception{
people peo = new people();
Class peoclass = peo.getClass();
Field field = peoclass.getDeclaredField("boy");
field.setAccessible(true);
String boy = (String) field.get(peo);
peo.num_people();
field.set(peo,"change");
peo.num_people();
field = peoclass.getDeclaredField("fin_boy2");
field.setAccessible(true);
field.set(peo,new StringBuilder("fin_boy2 change"));
peo.num_people();
}
}
这里boy是private,所以可以使用getDeclaredField获取boy变量,使用set对值进行修改,但是fin_boy是final String类型,并且其值为直接赋值,所以可以看到打印第三行修改失败。只有为间接赋值的时候才可以进行修改,如fin_boy2可以虽然为final,但是可以进行修改。
当类型既有final又有static修饰的字段,这个时候直接修改会报错,要采用反射修改final修饰符的值,然后就可以进行正常的修改。
直接赋值:直接赋值是指在创建字段时就对字段进行赋值,并且值为JAVA的8种基础数据类型或者String类型,类似:
private final int number = 5;
private final String sex = "boy";
除此以外均为间接赋值,均可以进行修改。
反射调用方法
people.java
public class people{
public void num_people5(String number){
System.out.println("fin_boy5 is " + number);
}
public void num_people6(String strnumber, int intnumber){
System.out.println("fin_boy6 is " + strnumber + " int is " + intnumber);
}
private static void num_people7(String strnumber, int intnumber){
System.out.println("fin_boy7 is " + strnumber + " int is " + intnumber);
}
}
main.java
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception {
test_reflect();
}
public static void test_reflect() throws Exception{
people peo = new people();
Class peoclass = peo.getClass();
Method method = peoclass.getMethod("num_people5", String.class);
method.invoke(peo,"change");
method = peoclass.getMethod("num_people6", new Class<?>[]{String.class, int.class});
method.setAccessible(true);
method.invoke(peo,new Object[]{"999",100});
method = peoclass.getDeclaredMethod("num_people7", new Class<?>[]{String.class, int.class});
method.setAccessible(true);
method.invoke(null,new Object[]{"888",111});
}
}
反射调用方法首先需要获取class对象,然后根据方法属性选择使用getDeclaredMethod还是getMethod,如果参数较多,采用new Class的方式传入,然后使用invoke执行方法,此处需要注意,如果函数为static静态函数,则在使用invoke的时候传入null进行反射调用。
反射调用构造函数
people.java
public class people{
private int int_test8;
private String str_test8;
public people(){
this.int_test8 = 11;
this.str_test8 = "cheange";
}
public people(int int_test8, String str_test8){
this.int_test8 = int_test8;
this.str_test8 = str_test8;
}
public void num_people8(){
System.out.println("fin_boy7 is " + this.str_test8 + " int is " + this.int_test8);
}
}
main.java
import java.lang.reflect.*;
public class Main{
public static void main(String[] args) throws Exception {
con_reflect();
}
public static void con_reflect() throws Exception{
Class peoclass = people.class;
Constructor constructor = peoclass.getConstructor();
people peo = (people) constructor.newInstance();
peo.num_people8();
constructor = peoclass.getConstructor(int.class,String.class);
constructor.setAccessible(true);
peo = (people) constructor.newInstance(20,"mychange");
peo.num_people8();
}
}
反射调用构造函数首先获得class对象,然后调用getConstructor函数获得构造函数,然后使用newInstance构造函数,并调用
总结:
当我们需要反射调用函数的时候参照如下方法:
- 首先需要获得要反射类的class对象,获取class对象方式有三种,可以根据实际情况进行选择
- 当我们需要修改参数值的时候,可以采用field来获取参数值或对参数值进行修改。
- 当需要反射调用方法的时候,可以使用method来进行反射调用。
- 当需要调用构造函数的时候,可以使用Constructor进行反射调用
- 另外就是要注意static和final类型的函数和参数修改方法。
反射在webshell免杀和反序列化漏洞中使用会比较频繁,但是反射本身并不难,只要掌握了主要的几个函数基本就可以了,在实战中经常采用反射的方式调用危险函数,进而绕过免杀检测和进行调用链的构造。后续我会对反射在安全中的实战进行讲解。