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;
是ASM方法添加的内容。
String var10001 = "Hello.";
System.out.println("Modified By ASM.");
可以看到ASM方法生成了2个变量(局部变量表的命名方式)存储了原本的内容
ASM的操作更细,Javaassist操作简单直观,对应到Rasp场景,应该是更适合用javaassit。
0x04 后续
1、OpenRasp的实现,检测的时候如何建立检测池;
黑名单?
2、两种方法均会修改具体的class文件,不修改class文件的拦截实现
笼统的讲就是检测到高危类的加载,在类加载阶段拦截