使用Java/Kotln编写的JVM字节码执行引擎

JAVA 字节码执行引擎

传统的 Java 动态调试只能基于源代码级别,没有源代码或混淆的 Java 类文件就无法进行动态调试。

Java 程序的运行基于 JVM 虚拟机,JVM 负责执行字节码。我们使用 Kotlin 构建了一个 JVM 字节码执行引擎,这使得我们可以使用 IntelliJ IDEA 等现代 IDE 在字节码级别调试 Java 程序,从而观察程序在执行过程中的行为。

注意,本项目仅用于研究JVM的运行原理和分析恶意程序,严禁将其用于任何非法用途。

先决知识基础

在使用本项目之前,请确保您具备以下知识基础。

  1. 了解 Java 类文件的格式
  2. 理解 JVM 中每个字节码的作用和含义

使用IDEA进行字节码级调试

git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git

使用 IDEA 打开此项目(需要 JDK 17),然后导航至“测试用例”部分。

TestCases 中包含两个测试用例,一个用于执行静态方法,另一个用于执行实例方法,分别名为 executeStaticMethodexecuteVirtualMethod

在相应的方法中,填写以下信息: classPathclassNamemethodName, 和 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 为真。