Motor de ejecución de bytecode de JVM escrito en Java/Kotlin

Motor de ejecución de bytecode de JAVA

La depuración dinámica tradicional de Java solo puede basarse en el nivel del código fuente y no se puede depurar dinámicamente sin código fuente o archivos de clase Java ofuscados.

La ejecución de programas Java se basa en la máquina virtual JVM, que ejecuta código de bytes. Hemos construido un motor de ejecución de código de bytes JVM usando Kotlin, que nos permite depurar programas Java a nivel de código de bytes usando IDE modernos como IntelliJ IDEA para observar el comportamiento del programa durante la ejecución.

Atención, este proyecto es sólo para estudiar e investigar el principio de funcionamiento de JVM y analizar programas maliciosos. Está estrictamente prohibido utilizarlo con fines ilegales.

Conocimientos previos fundamentales

Antes de utilizar este proyecto, asegúrese de tener los siguientes conocimientos básicos.

  1. Comprendiendo el formato de los archivos de clase de Java
  2. Comprender el papel y el significado de cada bytecode en JVM

Depuración a nivel de bytecode utilizando IDEA

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

Abra este proyecto usando IDEA (requiere JDK 17) y navegue hasta TestCases

Hay dos casos de prueba en TestCases, uno para ejecutar métodos estáticos y otro para ejecutar métodos de instancia, respectivamente llamadosexecuteStaticMethodyexecuteVirtualMethod.

En el método correspondiente, complete la información comoclassPath,className,methodNameymethodSignature. La información detallada del archivo de clase se puede ver usandoVisor de clases.

Correr directamente

Usando el archivo de clase compilado del siguiente código como ejemplo

public class Hello {

    public void hello() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

EjecutarexecuteVirtualMethod, ejecuta el método hello de esta clase.

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)

Puede obtener el siguiente resultado en la consola.

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"

La salida de la consola muestra todas las instrucciones de código de bytes de este método, los cambios en la pila durante la ejecución de la instrucción y los resultados de cada instrucción de código de bytes.

Depuración

Si necesita depurar instrucciones de bytecode, puede establecer puntos de interrupción antes y después de laexecute()método enVMExecutor.

 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)
        }

    }
}

Depuración de bytecode de submétodo

De forma predeterminada, el motor virtual solo interpreta y ejecuta el código de bytes del método especificado. Los submétodos llamados dentro del método especificado aún se ejecutan en la JVM para evitar una sobrecarga de rendimiento significativa de múltiples capas de llamadas. Si desea que todos los métodos sean interpretados y ejecutados por el motor virtual, modifiqueio.vlinx.vmengine.Optionsy cambiohandleSubMethod a verdadero.