Java字节码对比 ASM和Javaassist——代码实现

0x01 代码实现

创建一个Hello类,打印“hello”
——在BytecodeModifierASM类中,添加System.out.println("Modified By ASM.");
——在BytecodeModifierJavassist类中,添加System.out.println("Modified By JavaAssist!");

BytecodeModificationLauncher类
调用BytecodeModifierASM和BytecodeModifierJavassist

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

class CustomClassLoader extends ClassLoader {
    private Map<String, Class<?>> loadedClasses = new HashMap<>();

    public CustomClassLoader() {
        super(CustomClassLoader.class.getClassLoader());
    }

    public Class<?> defineClassFromFile(String className, String filePath) throws IOException {
        if (loadedClasses.containsKey(className)) {
            return loadedClasses.get(className);
        }

        File file = new File(filePath);
        byte[] b = new byte[(int) file.length()];
        FileInputStream fis = new FileInputStream(file);
        fis.read(b);
        fis.close();

        Class<?> clazz = defineClass(className, b, 0, b.length);
        loadedClasses.put(className, clazz);
        return clazz;
    }
}

public class BytecodeModificationLauncher {
    public static void main(String[] args) {
        // 正常调用 Hello 类
        System.out.println("正常调用Hello类:");
        Hello.main(args);

        try {
            // 调用 ASM 方式修改字节码
            System.out.println("\nASM字节码修改");
            BytecodeModifierASM.main(args);

            System.out.println("字节码修改后调用:");

            // 使用自定义类加载器加载修改后的类
            CustomClassLoader classLoader1 = new CustomClassLoader();
            Class<?> modifiedHelloClass1 = classLoader1.defineClassFromFile("Hello", "out/test/test/Hello.class");
            Method mainMethod = modifiedHelloClass1.getMethod("main", String[].class);
            mainMethod.invoke(null, (Object) args);


            System.out.println("=====");
        } catch (Exception e) {
            System.err.println("An error occurred during bytecode modification: " + e.getMessage());
            e.printStackTrace();
        }

        try {
            System.out.println("正常调用Hello类:");
            Hello.main(args);
            // 调用 JavaAssist 方式修改字节码

            
            System.out.println("\nJavaAssist字节码修改");
            BytecodeModifierJavassist.main(args);
            System.out.println("字节码修改后调用(重新加载类):");

            // 再次使用自定义类加载器加载修改后的类
            CustomClassLoader classLoader2 = new CustomClassLoader();
            Class<?> modifiedHelloClass2 = classLoader2.defineClassFromFile("Hello", "out/test/test/Hello.class");
            Method mainMethod = modifiedHelloClass2.getMethod("main", String[].class);
            mainMethod.invoke(null, (Object) args);

            System.out.println("=====");
        } catch (Exception e) {
            System.err.println("An error occurred during bytecode modification: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

BytecodeModifierASM类
通过ASM实现字节码修改

import org.objectweb.asm.*;
import java.io.FileOutputStream;
import java.io.IOException;

public class BytecodeModifierASM {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader("Hello");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new HelloClassVisitor(Opcodes.ASM9, cw);
        cr.accept(cv, ClassReader.EXPAND_FRAMES);
        byte[] modifiedClassBytes = cw.toByteArray();

        try (FileOutputStream fos = new FileOutputStream("out/test/test/Hello.class")) {
            fos.write(modifiedClassBytes);
        }
    }
}

class HelloClassVisitor extends ClassVisitor {
    public HelloClassVisitor(int api, ClassVisitor classVisitor) {
        super(api, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
        if ("main".equals(name)) {
            return new HelloMethodVisitor(api, mv);
        }
        return mv;
    }
}

class HelloMethodVisitor extends MethodVisitor {
    public HelloMethodVisitor(int api, MethodVisitor methodVisitor) {
        super(api, methodVisitor);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {

        System.out.println("Found call to OutTest.printMessage, modifying bytecode...");
        // 加载 System.out
        super.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        // 加载字符串 "Modified"
        super.visitLdcInsn("Modified By ASM.");
        // 调用 PrintStream.println 方法
        super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

    }
}

BytecodeModifierJavassist类
通过Javaassist实现字节码修改

import javassist.*;

import java.io.IOException;

public class BytecodeModifierJavassist {
    public static void main(String[] args) {
        try {
            ClassPool pool = ClassPool.getDefault();
            // 手动添加类路径
            pool.appendClassPath("/out/test/test/");
            // 使用正确的类名

            // 获取 Hello 类的 CtClass 对象
            CtClass cc = pool.get("Hello");
            if (cc.isFrozen()) {
                cc.defrost();
            }
            CtMethod mainMethod = cc.getDeclaredMethod("main");
            mainMethod.insertBefore("System.out.println(\"Modified By JavaAssist!\");");
            // 写入修改后的字节码到文件
            cc.writeFile("out/test/test/");
        } catch (NotFoundException | CannotCompileException | IOException e) {
            e.printStackTrace();
        }
    }
}

Hello类

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello.");
    }
}

0x02 对比

  • JavaAssist
    • 使用 ClassPool 来管理类的字节码,通过 pool.appendClassPath 方法手动添加类路径。
    • 通过 pool.get("Hello") 方法获取 Hello 类的 CtClass 对象,该对象是 JavaAssist 中表示类的核心类。
    • 通过 CtClass 对象获取目标方法 CtMethod,然后使用 insertBefore 方法在方法开始处插入代码。
    • 使用 CtClass 对象的 writeFile 方法将修改后的字节码写入文件。
  • ASM
    • 使用 ClassReader 来读取目标类的字节码,通过 new ClassReader("Hello") 直接读取 Hello 类的字节码。
    • 没有显式的类路径管理,依赖于 Java 类加载机制和字节码文件的位置。
    • 使用 ClassVisitor 和 MethodVisitor 来遍历和修改字节码。通过重写 visitMethod 和 visitMethodInsn 等方法,在特定的方法调用处插入字节码指令。
    • 使用 ClassWriter 生成修改后的字节码,通过 cw.toByteArray() 方法获取字节码数组,然后使用 FileOutputStream 将字节码数组写入文件。

0x03 结果分析

注释BytecodeModifierJavassist.main(args);BytecodeModifierASM.main(args);的Hello.class文件

带有BytecodeModifierJavassist.main(args);和BytecodeModifierASM.main(args);的Hello.class文件

System.out.println("Modified By JavaAssist!"); 是Javaassist方法添加的内容;
PrintStream var10000 = System.out;
String var10001 = "Hello.";
System.out.println("Modified By ASM.");
是ASM方法添加的内容。

可以看到ASM方法生成了2个变量(局部变量表的命名方式)存储了原本的内容
ASM的操作更细,Javaassist操作简单直观,对应到Rasp场景,应该是更适合用javaassit。

0x04 后续

1、OpenRasp的实现,检测的时候如何建立检测池;
黑名单?
2、两种方法均会修改具体的class文件,不修改class文件的拦截实现
笼统的讲就是检测到高危类的加载,在类加载阶段拦截

上一篇
下一篇