Heap Overflows Revisited, Part 2
Suite de la série consacrée à l’études des Heap Overflows, la dernière fois j’avais conclu en montrant que modifier la LIST_ENTRY d’un chunk libre pouvait conduire à une manipulation de la mémoire du process lors de l’allocation de ce chunk. Maintenant on va passer à un exemple concret, je tiens juste à dire que j’ai chialé ma race car les fonction de gestion de heap ne se comportent pas de la même façon si elles sont débuggées ou pas. Tout ca à cause du putin de NtGlobalFlags, anyway c’est toujours un autre anti debugger trick, référencé bien sur par les gars d’OpenRCE
http://www.openrce.org/reference_library/anti_reversing_view/16/NtGlobalFlag%20Debugger%20Detection/
Finit de pleurer et passons à l’action.
Voici le code qui va nous faire suer :
#include#include typedef struct _HEAP_ENTRY { union { struct { USHORT Size; USHORT PreviousSize; }; PULONG SubSegmentCode; }; UCHAR SmallTagIndex; UCHAR Flags; UCHAR UnusedBytes; UCHAR SegmentIndex; } HEAP_ENTRY, *PHEAP_ENTRY; typedef struct _HEAP_FREE_ENTRY { union { struct { USHORT Size; USHORT PreviousSize; }; PULONG SubSegmentCode; }; UCHAR SmallTagIndex; UCHAR Flags; UCHAR UnusedBytes; UCHAR SegmentIndex; LIST_ENTRY FreeList; } HEAP_FREE_ENTRY, *PHEAP_FREE_ENTRY; void main() { HANDLE hHeap; PUCHAR MemAlloc; PHEAP_ENTRY pUsedEntry; PHEAP_FREE_ENTRY pFreeEntry; hHeap=HeapCreate(0, 0, 0x1000); //4096 bytes=1 PAGE printf("Allocating Memory"); MemAlloc=(PUCHAR)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, 128); if(!MemAlloc) { printf("Error with HeapAlloc : %dn", GetLastError()); goto bye; } } printf("nMemAlloc: 0x%xn", MemAlloc); /***************** Allocated chunk *****************/ pUsedEntry=(PHEAP_ENTRY)((PUCHAR)MemAlloc-sizeof(HEAP_ENTRY)); printf("pUsedEntry : 0x%xn", pUsedEntry); printf("Size: %dn", pUsedEntry->Size); printf("PreviousSize: %dn", pUsedEntry->PreviousSize); printf("SmallTagIndex: %dn", pUsedEntry->SmallTagIndex); printf("Flags: %dn", pUsedEntry->Flags); printf("UnusedBytes: %dn", pUsedEntry->UnusedBytes); printf("SegmentIndex: %dn", pUsedEntry->SegmentIndex); printf("nFreing memoryn"); if(!HeapFree(hHeap, 0, MemAlloc)) { printf("Error with HeapFree : %dn", GetLastError()); goto bye; } /***************** Freed chunk *****************/ pFreeEntry=(PHEAP_FREE_ENTRY)((PUCHAR)MemAlloc-sizeof(HEAP_ENTRY)); printf("pFreeEntry: 0x%xn", pFreeEntry); printf("Size: %dn", pFreeEntry->Size); printf("PreviousSize: %dn", pFreeEntry->PreviousSize); printf("SmallTagIndex: %dn", pFreeEntry->SmallTagIndex); printf("Flags: %dn", pFreeEntry->Flags); printf("UnusedBytes: %dn", pFreeEntry->UnusedBytes); printf("SegmentIndex: %dn", pFreeEntry->SegmentIndex); printf("Next Chunk 0x%xn", pFreeEntry->FreeList.Flink); printf("Prev Chunk 0x%xn", pFreeEntry->FreeList.Blink); bye: HeapDestroy(hHeap); }
On se crée un nouveau Heap de 4096 bytes avec HeapCreate et on alloue dedans 128 bytes avec HeapAlloc, ensuite on dump la structure HEAP_ENTRY qui précède le bloc alloué, jusqu’ici rien de violent. Après on libère notre bloc avec HeapFree et on dump notre HEAP_FREE_ENTRY, enfin on destroy notre heap avec HeapDestroy.
A l’exécution on obtient :
C:ProgHackcheap>heap_alloc Allocating Memory MemAlloc: 0x340688 pUsedEntry : 0x340680 Size: 17 PreviousSize: 8 SmallTagIndex: 95 Flags: 1 //HEAP_ENTRY_BUSY UnusedBytes: 8 SegmentIndex: 0 Freing memory pFreeEntry: 0x340680 Size: 304 PreviousSize: 8 SmallTagIndex: 95 Flags: 16 //HEAP_ENTRY_LAST_ENTRY|HEAP_ENTRY_FILL_PATTERN|HEAP_ENTRY_EXTRA_PRESENT UnusedBytes: 8 SegmentIndex: 0 Next Chunk 0x340178 Prev Chunk 0x340178
Le bloc est alloué en 0×340688 la structure HEAP_ENTRY est donc 8 bytes avant en 0×340680, n’oubliez pas que le champ size contient la taille, 128(notre bloc) + 8(la struct HEAP_ENTRY)=136, divisé par 8 nous donne bien 17.
A la libération est ajouté la structure LIST_ENTRY pointant sur le chunk précédent (respectivement suivant) libre. Ici les pointeurs sont égaux, osef ca ne change rien.
Souvenez vous, lors de l’allocation, le système « unlink » notre chunk de la LIST_ENTRY de chunk libres avec le code suivant :
mov eax, Flink mov ecx, Blink mov [ecx], eax mov [eax+4], ecx
En choisisant bien les valeurs de Flink et de Blink lors de l’altération du chunk par débordement il est alors possible d’écrire ou l’on veut en mémoire.
Windows XP depuis le sp2, pour nous faire chier à rajouté une petite vérification :
ntdll RtlAllocateHeap : .text:7C92142E mov edi, [ecx] ; ecx=Blink, edi contiendra le Flink du prev chunk .text:7C921430 cmp edi, [eax+4] ; eax=Flink, le cmp compare le Flink du prev chunk au .text:7C921430 ; Blink du next chunk, si pas egaux alors ya eu corruption .text:7C921433 jnz loc_7C944380 .text:7C921439 cmp edi, edx ; edx pointe sur la LIST_ENTRY de notre chunk .text:7C92143B jnz loc_7C944380 ; le cmp compare le Flink du prev chunk à l'addr de notre chunk ; si pas egaux alors ya eu fuckage .text:7C921441 mov [ecx], eax ; PrevChunk vers le NextChunk .text:7C921443 mov [eax+4], ecx ; NextChunk vers le PrevChunk
On en revient à ce que disait Connover lors de la conf BlackHat 2004 : « B->Flink->Blink == B->Blink->Flink == Header to free (B) ». Cette simple protection suffit à faire échouer de nombreuses tentatives d’exploitation des heap overflows.
Mais cela n’est pas finit, un autre méchanisme existe, je veux bien sur parler du fameux « cookie » insérer dans les structures HEAP_ENTRY sous le nom de SmallTagIndex, le principe est simple si le cookie a été altéré alors il y a eu corruption du chunk. Mais ça on le verra cela dans la prochaine partie…
Ivanlef0u
février 16th, 2007