使用 Java/Kotlin 编写的 JVM 字节码执行引擎
Java 字节码执行引擎
传统的 Java 动态调试只能基于源代码层面进行,没有源代码或者 Java class 文件被混淆后就无法进行动态调试。
Java 程序的运行基于 JVM 虚拟机,JVM 以字节码作为执行基础。我们使用 Kotlin 构建了一个 JVM 字节码执行引擎,可以配合 IntelliJ IDEA 等现代 IDE 使用,在字节码级别调试 Java 程序,观察程序的执行行为。
注意,本项目仅用于学习和研究 JVM 的运行原理以及分析恶意程序。严禁将其用于非法目的。
前置知识基础
使用本项目前,请确保您具备以下知识基础:
- 了解 Java class 文件的格式
- 了解 JVM 中每个字节码的作用和含义
使用 IDEA 进行字节码级别调试
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
使用 IDEA(需要 JDK 17)打开本项目,导航到 TestCases
TestCases 中有两个测试用例,一个用于执行静态方法,一个用于执行实例方法,分别命名为 executeStaticMethod 和 executeVirtualMethod。
在对应的方法上,填入 classPath、className、methodName、methodSignature 等信息。class 文件的详细信息可以使用 ClassViewer 查看。
直接运行
以以下代码编译后的 class 文件为例
public class Hello {
public void hello() {
System.out.println("hello");
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
执行 executeVirtualMethod,运行该类的 hello 方法。
val classPath = "your-classpath"
val className = "Hello"
val methodName = "hello"
val methodSignature = "()V"
val args = listOf<Any?>()
val url = File(classPath).toURI().toURL()
val urls = arrayOf(url)
val loader = VlxClassLoader(urls)
val clazz = loader.loadClass(className)
val method = ClassUtils.getMethod(clazz, methodName, methodSignature, loader)
val instance = clazz.getDeclaredConstructor().newInstance()
val thread = VMThread(VMEngine.instance, loader)
thread.execute(instance, method!!, args, true, 0)
控制台可以获得以下输出:
2023-05-21 17:51:10 [DEBUG] Execute method: public void Hello.hello()
...
hello
2023-05-21 17:51:11 [DEBUG] "L8: RETURN"
控制台输出显示了该方法的所有字节码指令、指令执行过程中栈的变化以及每条字节码指令的执行结果。
调试
如果需要调试字节码指令,可以在 VMExecutor 中的 execute() 方法前后设置断点。
fun execute() {
while (true) {
try {
pc = sequence.index()
val opcode = sequence.readUnsignedByte().toShort()
if (execute(opcode)) {
break
}
} catch (vme: VlxVmException) {
Logger.FATAL(vme)
exitProcess(1)
} catch (t: Throwable) {
handleException(t)
}
}
}
调试子方法字节码
默认情况下,虚拟引擎只解释执行指定方法的字节码。指定方法内部调用的子方法仍然在 JVM 中运行,以避免多层调用带来的显著性能开销。如果您希望所有方法都由虚拟引擎解释执行,请修改 io.vlinx.vmengine.Options,将 handleSubMethod 改为 true。