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文件的拦截实现
笼统的讲就是检测到高危类的加载,在类加载阶段拦截