Présentation de DRAKVUF : analyser un malware depuis l'hyperviseur, sans agent dans la VM

Catégorie : DRAKVUF


Résumé

DRAKVUF analyse un malware pendant qu'il tourne, depuis l'hyperviseur Xen, sans rien installer dans la VM. Le malware ne trouve aucun outil d'analyse dans son système, aucun pilote, aucun processus suspect, et c'est tout l'intérêt. Ce billet explique le principe (altp2m et les points d'arrêt invisibles), les plugins, l'injection de programmes, la ligne de commande, comment on prépare une VM et son profil, les règles que je m'impose au lab, et ce que DRAKVUF ne sait pas faire. Les correctifs que j'ai envoyés au projet sont dans un billet à part.

1. Le principe

Une sandbox classique installe un agent dans la VM pour observer le malware. Le malware peut le détecter, le contourner, ou simplement refuser de tourner. DRAKVUF fait le contraire : il observe depuis le dom0 de Xen, à travers LibVMI, et il intervient dans l'exécution par des mécanismes du processeur que le guest ne peut pas voir.

Le cœur, c'est altp2m. Xen peut maintenir plusieurs tables de traduction (EPT) pour une même VM, plusieurs « vues » de sa mémoire physique. DRAKVUF s'en sert pour poser des points d'arrêt invisibles : dans une vue, une page de code contient une instruction int3 à l'adresse surveillée ; dans une autre, la page est intacte. Le guest exécute sur la vue modifiée ; quand il lit sa propre mémoire (un malware qui vérifie son intégrité, par exemple), il voit la vue intacte. Quand l'int3 est atteint, Xen remet la main à DRAKVUF, qui lit les registres et la pile, émet un événement, puis fait exécuter l'instruction d'origine en pas à pas sur une troisième vue avant de rebasculer. Tout ça sans qu'aucune page visible du guest ne change.

Les points d'arrêt sont posés sur des fonctions dont DRAKVUF connaît l'adresse par le profil du noyau : NtCreateUserProcess, NtWriteFile, les appels système, ou des fonctions de DLL dans les processus (via libusermode, qui attend que la DLL soit chargée pour poser le hook). Un ReturnHook est un point d'arrêt posé sur l'adresse de retour d'un appel, pour lire son résultat.

2. Les plugins

Chaque plugin surveille un aspect et produit une ligne par événement (texte, CSV ou JSON). Ceux que j'utilise le plus :

Plugin Ce qu'il voit
procmon création et fin de processus, ligne de commande, parent
syscalls chaque appel système, avec ses arguments (très bavard)
filetracer ouverture, lecture, écriture, suppression de fichiers
regmon lectures et écritures du registre
apimon appels d'API dans les processus, sur une liste de fonctions choisie
rpcmon appels RPC
codemon code exécuté hors des modules connus (injection, shellcode)
memdump vidage de régions mémoire sur certains événements
fileextractor copie des fichiers écrits ou supprimés par le guest (nécessite -D <dossier>)
dkommon objets noyau cachés (processus, pilotes, services)
socketmon connexions réseau vues depuis le noyau
hidsim simule souris et clavier pour que le malware se réveille
hidevm masque les signes de VM (requêtes WMI, compteur de ticks, sonde thermique)
exploitmon, bsodmon, crashmon exceptions, écrans bleus, crashs
etwmon, wmimon, tlsmon, ssdtmon, poolmon ETW, WMI, clés TLS, table des appels système, allocations de pool

Il y en a une quarantaine. Sans -a, DRAKVUF les active tous, ce qui est rarement ce qu'on veut.

3. L'injection

DRAKVUF peut lancer un programme dans la VM sans agent, en détournant un processus existant au moment où il fait un appel système : il sauvegarde les registres, fait exécuter CreateProcess (ou autre) avec ses propres arguments, récupère le résultat, et restaure. Les méthodes (-m) : createproc (lancer un exécutable), shellexec, shellcode, readfile et writefile (copier un fichier dans un sens ou dans l'autre), terminateproc. Le processus cible se donne par -i <pid>, en pratique explorer.exe, et la commande par -e. Une leçon du lab : trois injections createproc successives dans le même explorer.exe le font planter, donc une injection par démarrage, et un redémarrage propre entre deux tests.

4. La ligne de commande

drakvuf -r win11-25h2.json -d win11-25h2 -a procmon -a filetracer -t 60 -o json
  • -r : profil JSON du noyau de ce build. -d : nom de la VM Xen. -a : plugin à activer (répétable), -x : plugin à exclure. -t : durée en secondes. -o : format de sortie.
  • -k <kpgd> : donner le DTB du noyau à la main, ce qui évite à LibVMI de le chercher (mais la valeur change à chaque démarrage).
  • -i, -e, -m : l'injection. -D : dossier de sortie pour fileextractor et memdump.
  • --userhook-injection-mode : comment libusermode pose ses hooks (le mode par défaut attend plusieurs appels système du même processus avant de converger). --userhook-target-process <nom> : ne hooker que ce processus et ses descendants.
  • -v : traces de debug, dans un build compilé en debug.
  • Ctrl+C pour arrêter. DRAKVUF retire ses points d'arrêt et relâche la VM. Un drakvuf ... | head tué par SIGPIPE ne le fait pas, et laisse le domaine en pause.

Si l'initialisation échoue, DRAKVUF ne le dit pas : il tourne jusqu'au timeout, sort 0 événement et un code retour 0. Il faut toujours regarder le nombre d'événements.

5. Préparer une VM

La VM est une VM Xen HVM ordinaire, avec quelques options : - altp2m=1 (indispensable), et ept=ad=0 dans les options de démarrage de Xen (voir le billet sur le crash reason 49). - Pour ne pas se faire repérer trop vite : viridian=0, cpuid="host,hypervisor=0", xen_platform_pci=0, un SMBIOS de vraie machine (ici un Dell OptiPlex 7090), un disque IDE et une carte e1000 plutôt que les pilotes paravirtualisés. Ça ne cache pas tout : le modèle de disque QEMU reste visible par WMI, et le timing trahit toujours une VM. - Un réseau isolé, sans sortie vers Internet. - Un snapshot propre du disque, restauré avant chaque analyse.

Le profil JSON se génère depuis le ntoskrnl.exe de la VM : extraction du fichier (guestfish, ou qemu-nbd sur 24H2 où il est compressé), lecture du GUID du PDB, téléchargement du PDB chez Microsoft, conversion en JSON. Un profil vaut pour un build exact. Les plugins qui hookent des DLL (hidevm avec fastprox.dll) ont en plus leurs propres offsets, à jour ou non selon le build.

Sur Windows 11, TPM et Secure Boot se contournent à l'installation (clés LabConfig), et il faut compter avec VBS et HVCI activés par défaut, qui renforcent le KVA shadowing et compliquent l'attache (voir LibVMI, section 3).

6. Les règles que je m'impose

  • Aucun agent dans la VM, jamais. Si un test en a besoin, c'est le test qui est mauvais.
  • Une seule VM allumée à la fois pendant les mesures.
  • Attendre que Windows ait fini de démarrer avant d'attacher DRAKVUF : 15 s au minimum sur ces machines, et la bonne façon est de regarder l'état de la VM, pas l'horloge.
  • Jamais conclure « 0 événement » sans avoir lu xl dmesg, le journal de la VM, et le code du plugin.
  • Un binaire reconstruit proprement avant toute bissection ; un build incrémental a déjà faussé une conclusion.
  • Un seul DRAKVUF par LibVMI : ne jamais mélanger un LibVMI système et un DRAKVUF compilé contre son sous-module, les structures n'ont pas la même taille.

7. Ce que DRAKVUF ne fait pas

Il ne détecte pas qu'un guest redémarre : il sort une rafale d'erreurs altp2m et continue. Il ne sait pas que l'initialisation a échoué. Il ne hooke pas un processus déjà en cours quand on l'attache, par choix de conception : chaque analyse part d'une ardoise propre. Et il ne réveille pas le malware à votre place : un échantillon qui attend un clic, une connexion réseau ou une date ne fera rien, hidsim et un réseau simulé aident, pas plus.

Sources

  • DRAKVUF sur GitHub, drakvuf --help, et le code des plugins dans src/plugins/.
  • Le site du projet, drakvuf.com, pour l'installation.