Hidding Module from the Virtual Address Descriptor Tree
Hi =)
Recently I read a post of Ivanlef0u : LdrpHashTable where he was showing how to hide a Dll from this table. So by reading this post we can say that we know 4 techniques to hide a Dll :
- Unlink from InLoadOrderModuleList.
- Unlink from InMemoryOrderModuleList.
- Unlink from InInitializationOrderModuleList.
- Unlink from LdrpHashTable.
Well my purpose is to show you a new one. This technique is based on the Virtual Address Descriptors. So let’s take a look to the functioning of these descriptors management.
Understanding Virtual Address Descriptors :
Actually, each process has VADs located in kernel land and there is a VAD to describe each process’s address space. A VAD is constituted by a MMVAD structure:
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;
Now I catch your attention on these three fields :
/*0x008*/ struct _MMVAD* Parent; /*0x00C*/ struct _MMVAD* LeftChild; /*0x010*/ struct _MMVAD* RightChild;
We see that a VAD has a parent VAD and two children VADs. That is to say that the VADs are organised like a tree.
Now an other useful field is :
/*0x018*/ struct _CONTROL_AREA* ControlArea;
And the structure definition is :
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;
So we see we can have a pointer on the _FILE_OBJECT which can allow us, if the memory space corresponds to a module loaded, to retrieve its name :
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
Okay so I made a schema to demonstrate it :
Moreover, windbg allow you to list the VADs of a process. Here is an example :
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
Well now we will manipulate the VAD tree ourselves.
Walking into the VAD Tree :
I’ve shown you have to walk in the VAD manually so now we are able to create a driver that will list all the VADs of a process.
First we have to get a pointer on the first VAD (VadRoot) from the EPROCESS structure :
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; }
And now we can list the VAD children and so on. But you may think it’s a bit difficult because there is a right child and a left child which both have right and left child… The trick is to use recursion. I obtain this function :
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; }
You see the VADs will have different levels depending of their position in the tree(parents, children, grandchildren, … =) ).
For each VAD I display few informations and the result is :
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
Okay, you understand that with this method we can list modules and find the one which are unlinked from the 4 previous lists !
Ivanlef0u owned ;p.
But what if we want to hide a module from this new list ?
Hiding from VAD :
So it is not very difficult now to hide a dll from the VAD. For example we can parse the VADs looking for our dll and when we find it, we just replace its parent pointer with a null pointer and it is going to be impossible to find it with our previous function. This technique is not so clean because all the others VAD depending to the one we hide will be hidden too. We could have create a fake VAD that would have takend the place of the hidden one for example, it would have been cleaner . But I don’t really have the time so I’ve just implemented the first method :
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; }
Good, it’s the end of this article, you can view online the sources here : VADTree.c & structs.h
Or download Sources+Binary here : 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.