java安全-反射机制

前言:

        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构造函数,并调用

 总结:

        当我们需要反射调用函数的时候参照如下方法:

  1. 首先需要获得要反射类的class对象,获取class对象方式有三种,可以根据实际情况进行选择
  2. 当我们需要修改参数值的时候,可以采用field来获取参数值或对参数值进行修改。
  3. 当需要反射调用方法的时候,可以使用method来进行反射调用。
  4. 当需要调用构造函数的时候,可以使用Constructor进行反射调用
  5. 另外就是要注意static和final类型的函数和参数修改方法。

        反射在webshell免杀和反序列化漏洞中使用会比较频繁,但是反射本身并不难,只要掌握了主要的几个函数基本就可以了,在实战中经常采用反射的方式调用危险函数,进而绕过免杀检测和进行调用链的构造。后续我会对反射在安全中的实战进行讲解。

猜你喜欢

转载自blog.csdn.net/GalaxySpaceX/article/details/129829392