Archive for décembre, 2009

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


Calendar

décembre 2009
L Ma Me J V S D
« nov   jan »
 123456
78910111213
14151617181920
21222324252627
28293031  

Posts by Month

Posts by Category