10-java安全基础——javassist字节码编程

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了10-java安全基础——javassist字节码编程相关的知识,希望对你有一定的参考价值。

javassist是一个开源的分析、编辑和创建Java字节码的类库,通过javassist提供的API可以在java程序运行时编辑一个类的字节码信息,改变该类的结构信息。除了Javassist,常见的字节码编程工具有ASM和byte-buddy,这两个工具相对来说更加偏向于底层,需要了解关于jvm的指令。

使用javassist可以不需要了解jvm指令,只需使用javassist类库提供的API接口就可以实现字节码编程。

javassist字节码编程常用的类:

ClassPool:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与 JVM 类装载器类似

CtClass: CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法

CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等

CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码

CtConstructor:用于访问类的构造,与CtMethod类的作用类似

在maven项目中引入javassist类库:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.25.0-GA</version>
</dependency>

来看一段代码:

public static void main(String[] args) throws NotFoundException {
    //创建classPool类对象池
    ClassPool classPool = ClassPool.getDefault();
    //通过classPool类对象池创建一个Student类
    CtClass ctClass1 = classPool.makeClass("com.test.Student");
    //通过classPool类对象池加载Student类
    CtClass ctClass2 = classPool.getCtClass("com.test.Student");
}

ClassPool对象是代表class文件的CtClass对象的容器,可以控制字节码的修改,CtClass表示一个类的class文件的句柄:

当要创建一个类时,需要通过ClassPool对象的makeClass方法来创建

当要修改一个类时,可以通过getCtClass方法来加载一个已知的类

无论是创建还是修改一个类,都会返回操作该类的句柄CtClass对象,然后可以通过CtClass对象进一步操作该类,例如添加成员方法,成员属性等等。

1. 使用CtMethod类添加main方法

创建一个Student类并添加一个main方法

public class JavassistTest1 {
    //1.添加main方法
    public static void main(String[] args) throws Exception {
        //创建classPool类对象池
        ClassPool classPool = ClassPool.getDefault();
        //通过classPool类对象池创建一个Student类
        CtClass student_ctClass = classPool.makeClass("target.com.test.javassist.Student");
        //添加方法:参数1:方法的返回值类型你,参数2:方法名,参数3:方法的参数类型,参数4:方法所属的类
        CtMethod mainMethod = new CtMethod(CtClass.voidType , "main" , new CtClass[]{classPool.get(String[].class.getName())},student_ctClass);
        //设置main方法的访问修饰符
        mainMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
        //设置方法体
        mainMethod.setBody("{System.out.println(\\"hello world\\");}");
        //添加方法
        student_ctClass.addMethod(mainMethod);
        //输出类的内容
        student_ctClass.writeFile();

        //实例化Student对象
        Class aClass = student_ctClass.toClass();
        Object obj = aClass.newInstance();
        //反射调用Student对象的main方法
        Method main_method = aClass.getDeclaredMethod("main" , String[].class);
        main_method.invoke(obj, (Object) new String[1]);
    }
}

CtMethod 类是用于添加方法的,例如添加main方法主要包括:方法的属性、类型、名称、参数,方法体。new CtMethod操作主要是创建方法的声明,setModifiers方法用于设置方法的修饰符, setBody方法用于设置方法体,addMethod方法用于把main方法添加到Student类的CtClass对象中。然后调用CtClass对象的writeFile方法在磁盘上生成Student类的class文件。

程序执行结果:

 通过javassist创建一个Student类会生成Student类的class文件,通过反射可以调用Student类的class对象的main方法输出hello wrold。

还有一种方法可以直接创建方法,通过CtNewMethod类的静态方法make创建方法,make方法的参数1直接传入方法的完整结构信息,例如下面这段代码:

CtMethod ctMethod1 = CtNewMethod.make("public int print_stu(){ return 100;}", student_ctClass);
student_ctClass.addMethod(ctMethod1);

传入的参数类型是对象类型时需要注意以下几点:

例如传入的是String类型,那么就要使用classPool.get(String[].class.getName()) 这种方式传入

当传入多个参数,且参数类型相同,例如传入多个Integer类型的参数,需要使用new CtClass[] {classPool.get(Integer[].class.getName())} 这种方式传入

当传入多个参数,且参数类型不相同,例如传入char类型的参数和long类型的参数就要使用 new CtClass[] {CtClass.charType , CtClass.longType} 这种方式

如果方法接收的参数是一个对象类型,例如接收的是一个Student对象类型的参数,那么就可以通过classPool对象的getCtClass方法来获取Student类的CtClass对象的方式传入该参数。

classPool.getCtClass("target.com.test.javassist.Student");

2. 添加构造方法

来看一个创建Student类并添加一个构造方法的示例程序

package com.test;

import javassist.*;

/**
 * @auther songly_
 * @data 2021/7/28 13:47
 */
public class JavassistTest3 {

    public static void main(String[] args) throws Exception {
        //创建classPool类对象池
        ClassPool classPool = ClassPool.getDefault();
        //通过classPool类对象池创建一个Student类
        CtClass student_ctClass = classPool.makeClass("target.com.test.javassist.Student");

        //添加一个int类型的classid变量
        CtField ctField = new CtField(CtClass.intType,"classid",student_ctClass);
        //设置变量的访问权限
        ctField.setModifiers(Modifier.PRIVATE);
        //把变量添加到Student类的ctClass对象
        student_ctClass.addField(ctField);

        //添加一个String类型的name变量
        CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , student_ctClass);
        ctField2.setModifiers(Modifier.PUBLIC);
        student_ctClass.addField(ctField2);

        //添加有参构造
        CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{ CtClass.intType, classPool.getCtClass("java.lang.String")},student_ctClass);
        ctConstructor1.setModifiers(Modifier.PUBLIC);
        ctConstructor1.setBody("{$0.classid = $1;$0.name = $2;}");
        student_ctClass.addConstructor(ctConstructor1);

        //输出类的内容
        student_ctClass.writeFile();

        //实例化Student对象,会调用Student对象的构造方法
        Class aClass = student_ctClass.toClass();
    }
}

程序执行结果:

CtConstructor的参数1为创建的构造方法的参数,如果是创建无参构造的话,那么第一个参数可以设置为null ,如果是有参构造,参数1为CtClass[]数组,表示接收多个参数

    public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map) throws CannotCompileException {
        this((MethodInfo)null, declaring);
        this.copy(src, true, map);
    }

3. 添加成员属性

创建一个Student类并添加两个成员属性,如下所示:

package com.test;

import javassist.*;

/**
 * @auther songly_
 * @data 2021/7/28 13:47
 */
public class JavassistTest3 {

    public static void main(String[] args) throws Exception {
        //创建classPool类对象池
        ClassPool classPool = ClassPool.getDefault();
        //通过classPool类对象池创建一个Student类
        CtClass student_ctClass = classPool.makeClass("target.com.test.javassist.Student");

        //添加一个int类型的classid变量
        CtField ctField = new CtField(CtClass.intType,"classid",student_ctClass);
        //设置变量的访问权限
        ctField.setModifiers(Modifier.PRIVATE);
        //把变量添加到Student类的ctClass对象
        student_ctClass.addField(ctField);

        //添加一个String类型的name变量
        CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , student_ctClass);
        ctField2.setModifiers(Modifier.PUBLIC);
        student_ctClass.addField(ctField2);

        //输出类的内容
        student_ctClass.writeFile();

        //实例化Student对象,会调用Student对象的构造方法
        Class aClass = student_ctClass.toClass();
        Object obj = aClass.newInstance();
    }
}

javassist字节码编程可以通过几行代码创造一个类并生成class文件, 并且还可以通过反射调用该类的方法和成员属性,甚至还可以修改某一个类的方法或成员属性等等,是一门非常强大的技术。

以上是关于10-java安全基础——javassist字节码编程的主要内容,如果未能解决你的问题,请参考以下文章

10-java安全基础——javassist字节码编程

Android AOP编程——Javassist基础

Android AOP编程——Javassist基础

Android AOP编程——Javassist基础

字节码增强之Javassist

字节码增强之Javassist