LdrpHashTable
Après avoir passé un peu de temps sur du linux ARM et PPC je retourne sous Windows parce que c’est la vraie vie. Rien de très innovant cette fois, juste un trick permettant de retrouver les DLLs chargées par un processus. Bien sur je veux parler de DLLs qui tenteraient de se cacher par rapport à certaines APIs et outils. On va voir que le loader de Windows maintient des structures en internes pour se simplifier la vie et que celles ci peuvent nous servir aussi.
Une liste des DLL qu’un processus à en mémoire est maintenu par le loader au niveau du PEB. On parle aussi bien des DLLs chargées statiquement que dynamiquement. Cette structure de type PEB_LDR_DATA
se situe au niveau du champ Ldr (offset 0xC) du PEB :
// // Loader Data stored in the PEB // typedef struct _PEB_LDR_DATA { ULONG Length; BOOLEAN Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID EntryInProgress; } PEB_LDR_DATA, *PPEB_LDR_DATA;
Comme vous pouvez le voir il y a 3 listes qui contiennent les mêmes informations mais pas dans le même ordre :
- InLoadOrderModuleList : Liste des modules dans leur ordre de chargement.
- InMemoryOrderModuleList : Liste des modules dans leur ordre croissant d’ImageBase.
- InInitializationOrderModuleList : Liste des modules dans leur ordre d’initialisation. Cette liste est différente de la InLoadOrderModuleList parcequ’une DLL est initialisé uniquement lorsque ses imports sont complets. Dans certains cas il est nécessaire de charger et d’initialiser d’autres DLLs avant la nôtre.
Ces listes sont en fait maintenues dans des structures de type LDR_DATA_TABLE_ENTRY :
// // Loader Data Table Entry // typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; PVOID SectionPointer; }; ULONG CheckSum; union { ULONG TimeDateStamp; PVOID LoadedImports; }; PVOID EntryPointActivationContext; PVOID PatchInformation; } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
On retrouve des informations comme le nom de la DLL, son ImageBase et sa taille (SizeOfImage). Il existe aussi un champ assez peu connu dans cette structure : HashLinks. En fait lorsque le loader doit charger une DLL dans un processus il n’utilise pas les listes qu’on a vues plus haut pour des raisons de performances, en effet parcourir une liste chainée se fait en temps linéaire et peut être long s’il y a beaucoup d’éléments. Pour aller plus vite le loader maintient une hashtable afin de diminuer le temps de recherche. Le champ HashLinks fait justement partie de cette hashtable.
En fait on parle de la variable globale LdrpHashTable, c’est un tableau de 32 LIST_ENTRY. Les DLLs y sont reparties en fonction de leur nom, la fonction de hashage prend la première lettre du nom de la DLL en uppercase et le soustrait à L’A’, le tout est bien sur passé dans un modulo 32. Au final tout cela se résume par ces définitions :
#define LDRP_HASH_TABLE_SIZE 32 #define LDRP_HASH_MASK (LDRP_HASH_TABLE_SIZE-1) #define LDRP_COMPUTE_HASH_INDEX(wch) ( (RtlUpcaseUnicodeChar((wch)) - (WCHAR)'A') & LDRP_HASH_MASK ) LIST_ENTRY LdrpHashTable[LDRP_HASH_TABLE_SIZE];
LdrpHashTable est accédée par :
- LdrpInitializeProcess : Initialise la table au début.
- LdrpInsertMemoryTableEntry : Ajoute une entrée à la table.
- LdrpCheckForLoadedDll : Vérifie si la DLL est déjà chargée.
Maintenant imaginons une DLL malveillante qui s’enlèverait des listes InLoadOrderModuleList, InMemoryOrderModuleList et InInitializationOrderModuleList. La question se pose si il est possible de la retrouver sans scanner la mémoire en cherchant des patterns du format PE dans un try{}except(){} tout moche.
Justement comme la LdrpHashTable est souvent oublié par les attaquants on peut l’utiliser pour énumérer les DLL d’un processus. J’ai codé un petit tool qui parcourt la mémoire d’un processus à l’aide de ReadProcessMemory() pour dumper cette hashtable.
C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! Usage is : GetModuleListByHashTable.exe(0 mean this process) C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe 0 Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! LdrpHashTable is at 7C98E260 Dumping Dlls ListHead[0] : 0x7C98E260 ListHead[1] : 0x7C98E268 ListHead[2] : 0x7C98E270 ListHead[3] : 0x7C98E278 ListHead[4] : 0x7C98E280 ListHead[5] : 0x7C98E288 ListHead[6] : 0x7C98E290 ListEntry : 0x00241EFC FullDllName : C:\ProgHack\c\LdrpHashTable\GetModuleListByHashTable.exe ListHead[7] : 0x7C98E298 ListHead[8] : 0x7C98E2A0 ListHead[9] : 0x7C98E2A8 ListHead[10] : 0x7C98E2B0 ListEntry : 0x00241FFC FullDllName : C:\WINDOWS\system32\kernel32.dll ListHead[11] : 0x7C98E2B8 ListHead[12] : 0x7C98E2C0 ListEntry : 0x0024209C FullDllName : C:\WINDOWS\system32\MSVCRT.dll ListHead[13] : 0x7C98E2C8 ListEntry : 0x00241F54 FullDllName : C:\WINDOWS\system32\ntdll.dll ListHead[14] : 0x7C98E2D0 ListHead[15] : 0x7C98E2D8 ListHead[16] : 0x7C98E2E0 ListHead[17] : 0x7C98E2E8 ListHead[18] : 0x7C98E2F0 ListHead[19] : 0x7C98E2F8 ListHead[20] : 0x7C98E300 ListHead[21] : 0x7C98E308 ListHead[22] : 0x7C98E310 ListHead[23] : 0x7C98E318 ListHead[24] : 0x7C98E320 ListHead[25] : 0x7C98E328 ListHead[26] : 0x7C98E330 ListHead[27] : 0x7C98E338 ListHead[28] : 0x7C98E340 ListHead[29] : 0x7C98E348 ListHead[30] : 0x7C98E350 ListHead[31] : 0x7C98E358 C:\ProgHack\c\LdrpHashTable>
Maintenant raison de plus de développer à coté un code qui enlève une DLL de ces 4 listes :]
C:\ProgHack\c\LdrpHashTable>HideDll.exe Hide ntdll from InMemoryOrderModuleList, InLoadOrderModuleList, InInitialization OrderModuleList and LdrpHashTable By Ivanlef0u BE M4D ! # Ici le process est en attente
On effectue un dump de ce process avec GetModuleListByHashTable :
C:\ProgHack\c\LdrpHashTable>tasklist | find "HideDll.exe" HideDll.exe 3436 0 708 Ko C:\ProgHack\c\LdrpHashTable>GetModuleListByHashTable.exe 3436 Process DLLs dumper using LdrpHashTable By Ivanlef0u BE M4D ! LdrpHashTable is at 7C98E260 Dumping Dlls ListHead[0] : 0x7C98E260 ListHead[1] : 0x7C98E268 ListHead[2] : 0x7C98E270 ListHead[3] : 0x7C98E278 ListHead[4] : 0x7C98E280 ListHead[5] : 0x7C98E288 ListHead[6] : 0x7C98E290 ListHead[7] : 0x7C98E298 ListEntry : 0x00241EFC FullDllName : C:\ProgHack\c\LdrpHashTable\HideDll.exe ListHead[8] : 0x7C98E2A0 ListHead[9] : 0x7C98E2A8 ListHead[10] : 0x7C98E2B0 ListEntry : 0x00241FFC FullDllName : C:\WINDOWS\system32\kernel32.dll ListHead[11] : 0x7C98E2B8 ListHead[12] : 0x7C98E2C0 ListEntry : 0x0024209C FullDllName : C:\WINDOWS\system32\MSVCRT.dll ListHead[13] : 0x7C98E2C8 ListHead[14] : 0x7C98E2D0 ListHead[15] : 0x7C98E2D8 ListHead[16] : 0x7C98E2E0 ListHead[17] : 0x7C98E2E8 ListHead[18] : 0x7C98E2F0 ListHead[19] : 0x7C98E2F8 ListHead[20] : 0x7C98E300 ListHead[21] : 0x7C98E308 ListHead[22] : 0x7C98E310 ListHead[23] : 0x7C98E318 ListHead[24] : 0x7C98E320 ListHead[25] : 0x7C98E328 ListHead[26] : 0x7C98E330 ListHead[27] : 0x7C98E338 ListHead[28] : 0x7C98E340 ListHead[29] : 0x7C98E348 ListHead[30] : 0x7C98E350 ListHead[31] : 0x7C98E358 C:\ProgHack\c\LdrpHashTable>
Hop, on voit bien que ntdll.dll n’apparait plus. Pour information des outils comme Process Explorer et LordPE ne ‘voient’ plus la DLL lorsqu’elle est unlink des 3 premières doubles listes chainées. De ce fait beaucoup de malwares le font pour être tranquille. Avec la LdrpHashTable on est capable de voir ces vilaines DLLs.
Sauf que les choses ne sont pas si simples. Il existe des outils comme VMMap qui sont capable de détecter nos DLLs autrement. Comment ? En fait lorsqu’une DLL est chargé en mémoire, elle est mappée, le système maintient donc en interne un objet de type Section le fichier qui est mappé. A l’aide de VirtualQueryEx() on peut connaître le type de mémoire et avec GetMappedFileName() le nom du fichier mappé. Ces APIs utilisent toutes deux le syscall ZwQueryVirtualMemory. Comme par hasard VMMap aussi :]
Je me suis donc codé un petit tool qui fonctionne pareil.
# On reprend le process vu plus haut qui cache ses DLLs C:\ProgHack\c\LdrpHashTable>GetModuleListByVirtualQuery.exe 3436 Process DLLs dumper using VirtualQueryEx+GetMappedFileName By Ivanlef0u BE M4D ! [*] Dumping Dlls MappedFile : \Device\HarddiskVolume1\ProgHack\c\LdrpHashTable\HideDll.exe (Addre ss : 0x400000 - SizeOfImage : 0x960) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\msvcrt.dll (Address : 0x77be0000 - SizeOfImage : 0x58000) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\kernel32.dll (Address : 0x7c800000 - SizeOfImage : 0x106000) MappedFile : \Device\HarddiskVolume1\WINDOWS\system32\ntdll.dll (Address : 0x7c910000 - SizeOfImage : 0xb9000) C:\ProgHack\c\LdrpHashTable>
Cool on retrouve bien notre ntdll.dll ! D’après ce que je sais, le seul moyen pour planquer notre module c’est de hooker le syscall ZwQueryVirtualMemory en kernel-land …
Au final vous avez compris qu’il existe plusieurs manières pour retrouver les DLLs d’un processus. On a vu que pour être sur de nos infos il faut utiliser ZwQueryVirtualMemory mais dans le cas ou cette API est hookée et que l’attaquant oublie la LdrpHashTable on peut encore s’en sortir. Bien sur ce n’est pas la technique ultime mais il est toujours bon de l’avoir sous le bras, c’est n’est pas difficile à comprendre et à implémenter en plus.
Peut être qu’un jour le monstrueux DLL Hell arrêtera de nous poser des problèmes.
Vous trouverez le code est les binaires ici :
http://ivanlef0u.fr/repo/LdrpHashTable.rar
Pour finir une petite selection de son parce que vous le valez bien
An Inch Above Sand du dernier album ‘What We All Come To Need’ de Pelican.
An Inch Above Sand
Postponed. End de l’EP ‘Samsara’ de Deviniance.
Postponed. End
Hordes To War de l’album ‘All Shall Fall’ de Immortal.
Hordes To War
Enfin Bleed de l’album ‘Obzen’ de Meshuggah parce que ca rigole zéro :
Bleed
12 comments décembre 28th, 2009