Un exemple de plugin Volatility : winobj.py
Par Sylvain SARMEJEANNE, vendredi 6 mars 2009 à 12:39 :: General :: #283 :: rss
Le framework Volatility permet d'analyser un dump de mémoire vive et contient déjà un certain nombre de plugins pour Windows (liste des processus, DLLs, connexions réseau, etc). Pour présenter les possibilités de cet outil, nous allons prendre l'exemple simple de la réalisation d'un clone de l'outil WinObj de Sysinternals permettant de parcourir l'Object Manager.
NB : l'Object Manager est le composant de Windows chargé de la création, du maintien et de la suppresion des objets manipulés par le système. Ces objets possèdent un type (driver, mutex, etc) et sont stockés de manière hiérarchique (cf chapitre 3 du Windows Internals pour plus de détails).
Parcours manuel avec WinDBG
La première étape consiste à vérifier "à la main" avec WinDBG ce qui sera fait plus tard de manière automatique par le script Volatility. On utilise tout d'abord le KPCR trick afin de récupérer l'adresse de ObpRootDirectory, pointeur vers la racine de l'Object Manager (NB : on travaille ici sur Windows XP) :
lkd> dt _KPCR ffdff000 +0x034 KdVersionBlock : 0x8054c038
lkd> dt _DBGKD_GET_VERSION64 0x8054c038 +0x020 DebuggerDataList : 0xffffffff`80691b74
lkd> dt _LIST_ENTRY 80691b74 +0x000 Flink : 0x8054c060 _LIST_ENTRY [ 0x80691b74 - 0x80691b74 ]
Nous avons donc notre structure _KDDEBUGGER_DATA64 en 0x8054c060. A l'offset 0x98 (cf windbgexts.h), on y trouve le pointeur ObpRootDirectoryObject :
lkd> dd 0x8054c060+0x98 l 1 8054c0f8 8055fb58
lkd> dd 8055fb58 l 1 8055fb58 e1004ec8
ObpRootDirectoryObject pointe donc vers 0xe1004ec8, adresse à laquelle on trouvera la structure _OBJECT_DIRECTORY correspondant à la racine de l'Object Manager :
lkd> dt _OBJECT_DIRECTORY e1004ec8 +0x000 HashBuckets : [37] 0xe1007b70 _OBJECT_DIRECTORY_ENTRY
Il faut ensuite parcourir cet _OBJECT_DIRECTORY afin de récupérer son contenu. Cela a déjà été documenté et ne pose donc pas de problème ; on va simplement refaire ici le parcours du premier répertoire pour "mémoire" (hem) :
lkd> dt 0xe1007b70 _OBJECT_DIRECTORY_ENTRY +0x000 ChainLink : 0xe1279008 _OBJECT_DIRECTORY_ENTRY +0x004 Object : 0xe1007188
NB : le champ ChainLink pointe vers la structure suivante.
lkd> dt _object_header 0xe1007188-0x18 +0x008 Type : 0x80ebc3c0 _OBJECT_TYPE +0x00c NameInfoOffset : 0x10
Le champ NameInfoOffset nous donne l'offset vers la structure _OBJECT_HEADER_NAME_INFO :
lkd> dt _object_header_name_info 0xe1007188-0x18-0x10 +0x000 Directory : 0xe1004ec8 _OBJECT_DIRECTORY +0x004 Name : _UNICODE_STRING "ArcName"
lkd> dt 0x80ebc3c0 _OBJECT_TYPE +0x040 Name : _UNICODE_STRING "Directory"
On vient donc de trouver le premier objet, nommé "ArcName", de type "Directory". Pour lister son contenu, on lit l'objet en tant que _OBJECT_DIRECTORY :
lkd> dt _object_directory 0xe1007188 +0x000 HashBuckets : [37] 0xe12793e8 _OBJECT_DIRECTORY_ENTRY
On récupére ses éléments comme précédemment, ici une chaîne de caractères :
lkd> dt _object_header_name_info e129a658-0x18-0x10 +0x004 Name : _UNICODE_STRING "multi(0)disk(0)rdisk(0)"
Application à Volatility
La version 1.3 de Volatility a introduit deux avancées majeures :
- la possibilité de développer des plugins indépendants, utilisables en les copiant simplement dans le répertoire memory_plugins ;
- la possibilité d'instancier une adresse en tant qu'objet et d'avoir un accès direct à ses différents champs avec la syntaxe Python (ex : "monobjet.monchamp").
L'accès à la structure _OBJECT_HEADER_NAME_INFO pourra ainsi se faire via (noter de même l'accès direct à la valeur NameInfoOffset) :
header_info = Object('_OBJECT_HEADER_NAME_INFO', myobj-offset-header.NameInfoOffset,
addr_space, None, profile=Profile())
Reste maintenant à automatiser le parcours des structures ci-dessus sous la forme d'un plugin. Pour le KPCR trick, inutile de tout recoder : la technique est déjà implémentée dans forensics/win32/info.py. Il manque cependant plusieurs choses pour notre winobj.py :
- dans vtypes.py, il manque la description des objets _OBJECT_DIRECTORY, _OBJECT_DIRECTORY_ENTRY et _OBJECT_HEADER_NAME_INFO. Leur implémentation se réalise un peu à la manière de dissecteurs Scapy à partir des informations issues de WinDBG ;
- dans vtypes.py, la description de _OBJECT_HEADER existe mais elle est incomplète : il manque par exemple le champ NameInfoOffset ;
- dans vtypes.py, la description de _KDDEBUGGER_DATA{32,64} ne contient pas l'offset vers le champ ObpRootDirectoryObject ;
- pour le KPCR trick, il faut implémenter les fonctions find_obprootdirectoryobject() et info_obprootdirectoryobject{32,64}(), retournant la valeur de ObpRootDirectory, à la manière de ce qui est déjà implémenté pour les autres champs de _KDDEBUGGER_DATA{32,64}.
Une fois l'adresse de la racine de l'Object Manager déterminée, on la parcourt récursivement pour en extraire les informations voules. Voici par exemple un extrait de la sortie du plugin sur l'image de référence xp-laptop-2005-07-04-1430.img du NIST :

Évidemment, comme toute liste chaînée qui se respecte, les structures _OBJECT_DIRECTORY_ENTRY peuvent être sujettes à du DKOM utilisable par une rootkit afin de cacher tout ou partie de ses éléments... C'est par exemple ce que faisait la preuve de concept Unreal.A.
Pour quoi faire ?
Le forensics en mémoire vive est une discipline en plein essor et complète le forensics "classique" réalisé sur des disques durs lors des missions de réponse à incident. Dans l'exemple ci-dessus, on l'utilise pour lister les objets manipulés par le système, ce qui inclut par exemple les pilotes (\Driver), les mutexes (\BaseNamedObjects) ou encore les sessions (\Sessions). Avantage de Volatility : la possibilité de scripter facilement, ouvrant ainsi la voie Ã
l'analyse automatisée de malware via l'application de techniques forensics.