使用Java/Kotln编写的JVM字节码执行引擎
Motor de ejecución de código de bytes 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 bytecode. Hemos construido un motor de ejecución de bytecode JVM con Kotlin, que nos permite depurar programas Java a nivel de bytecode mediante IDE modernos como IntelliJ IDEA para observar el comportamiento del programa durante la ejecución.
Atención, este proyecto tiene como único objetivo estudiar e investigar el funcionamiento de la JVM y analizar programas maliciosos. Queda estrictamente prohibido su uso con fines ilegales.
Fundamentos de conocimientos previos
Antes de utilizar este proyecto, asegúrese de tener la siguiente base de conocimientos.
- Comprender el formato de los archivos de clase de Java
- Comprender el rol y el significado de cada bytecode en JVM
Depuración a nivel de bytecode usando IDEA
git clone https://github.com/vlinx-io/vlx-vmengine-jvm.git
Abra este proyecto usando IDEA (requiere JDK 17) y navegue hasta Casos de prueba
Hay dos casos de prueba en TestCases, uno para ejecutar métodos estáticos y otro para ejecutar métodos de instancia, denominados respectivamente executeStaticMethod y executeVirtualMethod.
En el método correspondiente, complete la información como classPath, className, methodName, y methodSignatureLa información detallada del archivo de clase se puede ver usando Visor de clases.
Ejecutar 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!");
}
}
Ejecutar executeVirtualMethod, 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 la siguiente salida 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 código de bytes, puede establecer puntos de interrupción antes y después de la execute() método en 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)
}
}
}
Depuración del código de bytes del submétodo
De forma predeterminada, el motor virtual solo interpreta y ejecuta el bytecode del método especificado. Los submétodos llamados dentro del método especificado se ejecutan en la JVM para evitar una sobrecarga de rendimiento significativa causada por múltiples capas de llamadas. Si desea que el motor virtual interprete y ejecute todos los métodos, modifique io.vlinx.vmengine.Options y cambiar handleSubMethod a la verdad.