使用Java/Kotln编写的JVM字节码执行引擎
JAVA 字节码执行引擎
传统的 Java 动态调试只能基于源代码级别,没有源代码或混淆的 Java 类文件就无法进行动态调试。
Java 程序的运行基于 JVM 虚拟机,JVM 负责执行字节码。我们使用 Kotlin 构建了一个 JVM 字节码执行引擎,这使得我们可以使用 IntelliJ IDEA 等现代 IDE 在字节码级别调试 Java 程序,从而观察程序在执行过程中的行为。
注意,本项目仅用于研究JVM的运行原理和分析恶意程序,严禁将其用于任何非法用途。
先决知识基础
在使用本项目之前,请确保您具备以下知识基础。
- 了解 Java 类文件的格式
- 理解 JVM 中每个字节码的作用和含义
使用IDEA进行字节码级调试
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
使用 IDEA 打开此项目(需要 JDK 17),然后导航至“测试用例”部分。
TestCases 中包含两个测试用例,一个用于执行静态方法,另一个用于执行实例方法,分别名为 executeStaticMethod 和 executeVirtualMethod。
在相应的方法中,填写以下信息: classPath, className, methodName, 和 methodSignature可以使用以下命令查看类文件的详细信息。 类查看器。
直接运行
以以下代码的编译类文件为例
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()
2023-05-21 17:51:10 [DEBUG] Receiver: Hello@3daf7722
2023-05-21 17:51:10 [DEBUG] Args: []
2023-05-21 17:51:11 [DEBUG] LocalVars: [kotlin.Unit]
2023-05-21 17:51:11 [DEBUG] "L0: GETSTATIC"
2023-05-21 17:51:11 [DEBUG] "#7"
2023-05-21 17:51:11 [DEBUG] public static final java.io.PrintStream java.lang.System.out
2023-05-21 17:51:11 [DEBUG] "push" org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] "L3: LDC"
2023-05-21 17:51:11 [DEBUG] "#13"
2023-05-21 17:51:11 [DEBUG] "hello"
2023-05-21 17:51:11 [DEBUG] "push" "hello"
2023-05-21 17:51:11 [DEBUG] "L5: INVOKEVIRTUAL"
2023-05-21 17:51:11 [DEBUG] "#15"
2023-05-21 17:51:11 [DEBUG] "class java.io.PrintStream, NameAndType(name='println', type='(Ljava/lang/String;)V')"
2023-05-21 17:51:11 [DEBUG] public void java.io.PrintStream.println(java.lang.String)
2023-05-21 17:51:11 [DEBUG] "pop" "hello"
2023-05-21 17:51:11 [DEBUG] "pop" org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] Execute method: public void org.gradle.internal.io.LinePerThreadBufferingOutputStream.println(java.lang.String)
2023-05-21 17:51:11 [DEBUG] Receiver: org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905
2023-05-21 17:51:11 [DEBUG] Args: [org.gradle.internal.io.LinePerThreadBufferingOutputStream@6aa3a905, hello]
2023-05-21 17:51:11 [ERROR] Can't parse class class org.gradle.internal.io.LinePerThreadBufferingOutputStream
hello
2023-05-21 17:51:11 [DEBUG] "L8: RETURN"
控制台输出显示此方法的所有字节码指令、指令执行期间堆栈的变化以及每个字节码指令的结果。
调试
如果需要调试字节码指令,可以在字节码指令之前和之后设置断点。 execute() 方法 VMExecutor。
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 为真。