Moteur d'exécution de bytecode JVM écrit en Java/Kotlin

Moteur d'exécution du bytecode JAVA

Le débogage dynamique Java traditionnel ne peut être basé que sur le niveau du code source et ne peut pas être débogué dynamiquement sans code source ou fichiers de classe Java obscurcis.

L'exécution des programmes Java est basée sur la machine virtuelle JVM, qui exécute le bytecode. Nous avons construit un moteur d'exécution de bytecode JVM à l'aide de Kotlin, qui nous permet de déboguer des programmes Java au niveau du bytecode à l'aide d'IDE modernes tels qu'IntelliJ IDEA pour observer le comportement du programme pendant l'exécution.

Attention, ce projet est uniquement destiné à étudier et rechercher le principe de fonctionnement de la JVM et à analyser les programmes malveillants. Il est strictement interdit de l'utiliser à des fins illégales.

Connaissance préalable Fondation

Avant d'utiliser ce projet, veuillez vous assurer que vous disposez des connaissances de base suivantes.

  1. Comprendre le format des fichiers de classe Java
  2. Comprendre le rôle et la signification de chaque bytecode dans JVM

Débogage au niveau du bytecode en utilisant IDEA

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

Ouvrez ce projet en utilisant IDEA (nécessite JDK 17) et naviguez vers TestCases

Il y a deux cas de test dans TestCases, un pour exécuter des méthodes statiques et un pour exécuter des méthodes d'instance, respectivement nommésexecuteStaticMethodetexecuteVirtualMethod.

Sur la méthode correspondante, remplissez les informations telles queclassPath,className,methodNameetmethodSignature. Les informations détaillées du fichier de classe peuvent être visualisées en utilisantClassViewer.

Exécutez directement

En utilisant le fichier de classe compilé du code suivant comme exemple

public class Hello {

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

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

ExécuterexecuteVirtualMethod, exécutez la méthode hello de cette classe.

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)

Vous pouvez obtenir le résultat suivant sur la console.

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 sortie de la console affiche toutes les instructions de bytecode de cette méthode, les modifications apportées à la pile pendant l'exécution de l'instruction et les résultats de chaque instruction de bytecode.

Débogage

Si vous avez besoin de déboguer les instructions du bytecode, vous pouvez définir des points d'arrêt avant et après leexecute()méthode dansVMExecutor.

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

    }
}

Débogage du bytecode de la sous-méthode

Par défaut, le moteur virtuel interprète et exécute uniquement le bytecode de la méthode spécifiée. Les sous-méthodes appelées dans la méthode spécifiée s'exécutent toujours dans la JVM pour éviter une surcharge de performances importante due à plusieurs couches d'appels. Si vous souhaitez que toutes les méthodes soient interprétées et exécutées par le moteur virtuel, veuillez modifierio.vlinx.vmengine.Optionset changerhandleSubMethod à vrai.