Heap Overflows Revisited, Part 3
On continue notre exploration des heap overflows sous Win, cette fois ci on s’attaque à l’étude d’un autre méchanisme de protection. Je veux parler du cookie ajouté dans la structure HEAP_ENTRY qui permet (un peu comme celui qu’on ajoute dans la pile) de vérifier si le chunk à été corrompu, le système, lors de la manipulation des chunks regarde si celui-ci correspond bien à la valeur donné au départ. Avant d’expliquer tout cela je vais parler des structures plus « générales » utilisées par le heap manager en userland.
Accrochez vous parce que c’est violent, rien de ceci n’est documenté officiellement, MS fournit juste les définitions des structures nécessaire pour le débuggage.
Les valeurs des structures dumpées ici sont celles obtenu avec le code de la partie précédente.
Tout d’abord partons d’en haut, dans la structure PEB (Process Environment Bloc) on trouve en 0×90 un pointeur sur un tableau de pointeurs de structure HEAP, celles-ci sont crées lors d’un appel à HeapCreate (RtlCreateHeap).
kd> dt nt!_PEB 7ffdf000 [...] +0x088 NumberOfHeaps : 5 +0x08c MaximumNumberOfHeaps : 0x10 +0x090 ProcessHeaps : 0x7c98de80 -> 0x00140000 [...]
Ici notre process possède 5 heaps.
kd> dd 7c98de80 ; 5 heaps 7c98de80 00140000 00240000 00250000 00320000 7c98de90 00340000 00000000 00000000 00000000
Celui qu’on à crée est le dernier, dumpons la structure HEAP à l’aide du kernel debugger.
kd> dt nt!_HEAP 00340000 +0x000 Entry : _HEAP_ENTRY +0x008 Signature : 0xeeffeeff +0x00c Flags : 0x1000 +0x010 ForceFlags : 0 +0x014 VirtualMemoryThreshold : 0xfe00 +0x018 SegmentReserve : 0x100000 +0x01c SegmentCommit : 0x2000 +0x020 DeCommitFreeBlockThreshold : 0x200 +0x024 DeCommitTotalFreeThreshold : 0x2000 +0x028 TotalFreeSize : 0x11f +0x02c MaximumAllocationSize : 0x7ffdefff +0x030 ProcessHeapsListIndex : 5 ;5 ème heap +0x032 HeaderValidateLength : 0x608 +0x034 HeaderValidateCopy : (null) +0x038 NextAvailableTagIndex : 0 +0x03a MaximumTagIndex : 0 +0x03c TagEntries : (null) +0x040 UCRSegments : (null) +0x044 UnusedUnCommittedRanges : 0x00340588 _HEAP_UNCOMMMTTED_RANGE +0x048 AlignRound : 0xf +0x04c AlignMask : 0xfffffff8 +0x050 VirtualAllocdBlocks : _LIST_ENTRY [ 0x340050 - 0x340050 ] +0x058 Segments : [64] 0x00340640 _HEAP_SEGMENT ;tableau de descripteurs de segments +0x158 u : __unnamed +0x168 u2 : __unnamed +0x16a AllocatorBackTraceIndex : 0 +0x16c NonDedicatedListLength : 1 +0x170 LargeBlocksIndex : (null) +0x174 PseudoTagEntries : (null) +0x178 FreeLists : [128] _LIST_ENTRY [ 0x340710 - 0x340710 ] +0x578 LockVariable : 0x00340608 _HEAP_LOCK +0x57c CommitRoutine : (null) +0x580 FrontEndHeap : (null) +0x584 FrontHeapLockCount : 0 +0x586 FrontEndHeapType : 0 '' +0x587 LastSegmentIndex : 0 ''
Grosse structure qui contient plein d’infos, notamment on trouve un tableau de 64 pointeurs sur des structure HEAP_SEGMENT, je cite encore l’article de Kortchinsky paru dans Misc basé sur les travaux de Matt Connover : « le tableau des segments du tas : d’une taille fixe (64 entrées), il regroupe les informations nécessaires à la gestion de chaque segment : adresse de base, nombre de pages, première entrée dans le tas, dernière entrée dans le tas, etc. »
kd> dd 00340000 + 58 ; 1 seul segment 00340058 00340640 00000000 00000000 00000000
Un seul segment pour nous.
kd> dt nt!_HEAP_SEGMENT 00340640 +0x000 Entry : _HEAP_ENTRY +0x008 Signature : 0xffeeffee +0x00c Flags : 0 +0x010 Heap : 0x00340000 _HEAP +0x014 LargestUnCommittedRange : 0 +0x018 BaseAddress : 0x00340000 +0x01c NumberOfPages : 1 +0x020 FirstEntry : 0x00340680 _HEAP_ENTRY +0x024 LastValidEntry : 0x00341000 _HEAP_ENTRY +0x028 NumberOfUnCommittedPages : 0 +0x02c NumberOfUnCommittedRanges : 0 +0x030 UnCommittedRanges : (null) +0x034 AllocatorBackTraceIndex : 0 +0x036 Reserved : 0 +0x038 LastEntryInSegment : 0x00340708 _HEAP_ENTRY
Il est tout de même possible d’avoir des infos plus claires à l’aide de la commande !heap du kernel debugger.
kd> !heap -a 00340000 No heaps to display. Index Address Name Debugging options enabled 1: 00340000 Segment at 00340000 to 00341000 (00001000 bytes committed) Flags: 00001000 ForceFlags: 00000000 Granularity: 8 bytes Segment Reserve: 00100000 Segment Commit: 00002000 DeCommit Block Thres: 00000200 DeCommit Total Thres: 00002000 Total Free Size: 0000011f Max. Allocation Size: 7ffdefff Lock Variable at: 00340608 Next TagIndex: 0000 Maximum TagIndex: 0000 Tag Entries: 00000000 PsuedoTag Entries: 00000000 Virtual Alloc List: 00340050 UCR FreeList: 00340588 FreeList Usage: 00000000 00000000 00000000 00000000 FreeList[ 00 ] at 00340178: 00340710 . 00340710 00340708: 00088 . 008f8 [10] - free Segment00 at 00340640: Flags: 00000000 Base: 00340000 First Entry: 00340680 Last Entry: 00341000 Total Pages: 00000001 Total UnCommit: 00000000 Largest UnCommit:00000000 UnCommitted Ranges: (0) Heap entries for Segment00 in Heap 00340000 00340000: 00000 . 00640 [01] - busy (640) -> 1600d structure HEAP (sizeof 1416d) 00340640: 00640 . 00040 [01] - busy (40) -> 64d struture HEAP_SEGMENT (sizeof 60d) 00340680: 00040 . 00088 [01] - busy (80) -> 128d notre bloc de 128 octets 00340708: 00088 . 008f8 [10]
Comme on peut le voir, chaque structure est aussi « allouée » dans la mémoire et possède donc comme premier membre un HEAP_ENTRY.
Lors d’un appel à HeapCreate (RtlCreateHeap) on récupère un handle sur le heap qui est tout simplement l’adresse de la structure HEAP.
Maintenant qu’on à posé les bases intéressons nous à ce fameux cookie. Voiçi les valeurs que j’ai obtenu après plusieurs essais avec le code de la partie 2.
Allocating Memory MemAlloc: 0x340688 pUsedEntry : 0x340680 Size: 17 PreviousSize: 8 SmallTagIndex: 123 Flags: 1 UnusedBytes: 8 SegmentIndex: 0 Allocating Memory MemAlloc: 0x340688 pUsedEntry : 0x340680 Size: 17 PreviousSize: 8 SmallTagIndex: 70 Flags: 1 UnusedBytes: 8 SegmentIndex: 0 Allocating Memory MemAlloc: 0x340688 pUsedEntry : 0x340680 Size: 17 PreviousSize: 8 SmallTagIndex: 134 Flags: 1 UnusedBytes: 8 SegmentIndex: 0
Même si l’adresse du bloc alloué ne change pas, le cookie (SmallTagIndex) lui varie à chaque exécution. Allay hop un petit breakpoint en écriture sur la zone oû est inscrit le cookie (1 byte) lors de l’appel à HeapAlloc (RtlAllocateHeap) et on tombe sur ce petit code.
ntdll RtlAllocateHeap 7C92B5CC 8BC6 MOV EAX,ESI ;eax=esi=addr de HEAP_ENTRY 7C92B5CE C1E8 03 SHR EAX,3 ;eax >> 3 7C92B5D1 0FB6C0 MOVZX EAX,AL ; eax=(UCHAR)eax 7C92B5D4 8985 2CFFFFFF MOV DWORD PTR SS:[EBP-D4],EAX ;osef 7C92B5DA 33C9 XOR ECX,ECX ;ecx=0 7C92B5DC 8A4F 04 MOV CL,BYTE PTR DS:[EDI+4] ;edi=addr du HEAP, +4 -> SmallTagIndex cookie du HEAP 7C92B5DF 33C8 XOR ECX,EAX 7C92B5E1 884E 04 MOV BYTE PTR DS:[ESI+4],CL ; écrit dans le chunk le SmallTagIndex
Ce qui correspond à : SmallTagIndex=(UCHAR)(pHeapEntry>>3) XOR (pHeap->HeapEntry->SmallTagIntex)
Il est donc possible en ayant le handle sur le heap renvoyé par HeapCreate et l’adresse du bloc renvoyé par HeapAlloc de recalculé ce cookie. Dans le code précédent cela revient à écrire :
UCHAR Cookie=(UCHAR)(((ULONG)MemAlloc-8)>>3) ^ (*((PUCHAR)hHeap+0×4));
Truc chelou que je n’est pas compris le cookie n’est vérifié que lors de l’appel à HeapFree oO.
ntdll RtlFreeHeap 7C9204D7 8BC6 MOV EAX,ESI ;eax=esi=HEAP_ENTRY 7C9204D9 C1E8 03 SHR EAX,3 ;eax >> 3 7C9204DC 3246 04 XOR AL,BYTE PTR DS:[ESI+4] ;al=(UCHAR)((eax >> 3) XOR pHeapEntry->SmallTagIndex) ; le XOR étant reversible (a=b xor c <=> c= a xor b <=> b = c xor a) on recalcul le SmallTagIndex de la struc HEAP 7C9204DF 3247 04 XOR AL,BYTE PTR DS:[EDI+4] ; si egaux alors le xor vaut 0, sinon ya eu fuckage du chunk 7C9204E2 0F85 31B80200 JNZ ntdll.7C94BD19
En cas d’altération du chunk la fonction HeapFree nous renvoie le numéro d’erreur 87 : invalid parameter.
La question à 1000 € est de savoir comment est calculer le cookie de la structure HEAP elle même :] Hop on pose un petit breakpoint en écriture et on tombe sur :
ntdll RtlCreateHeap 7C9357AF BA 0000FE7F MOV EDX,7FFE0000 7C9357B4 8B02 MOV EAX,DWORD PTR DS:[EDX] 7C9357B6 F762 04 MUL DWORD PTR DS:[EDX+4] 7C9357B9 0FACD0 18 SHRD EAX,EDX,18 7C9357BD 8B4D E4 MOV ECX,DWORD PTR SS:[EBP-1C] ;ecx=adresse de HEAP 7C9357C0 8841 04 MOV BYTE PTR DS:[ECX+4],AL ;HEAP+4 -> SmallTagIndex
Hum chose étrange on trouve une adresse hardcodé 0x7FFE000, alors là coup de chance je google dessus et arrive sur :
http://www.uninformed.org/index.cgi?v=2&a=2&p=15
Magnifique, en 0x7FFE0000 on tombe sur une struct commune à tous les process: KUSER_SHARED_DATA, ses 2 premiers champ sont des variables s’incrémentant en fonction du temps.
lkd> dt nt!_KUSER_SHARED_DATA 7FFE0000 -a TickCountLow +0x000 TickCountLow : 0x9d294f lkd> dt nt!_KUSER_SHARED_DATA 7FFE0000 -a TickCountLow +0x000 TickCountLow : 0x9d29bc lkd> dt nt!_KUSER_SHARED_DATA 7FFE0000 -a TickCountLow +0x000 TickCountLow : 0x9d29f2 lkd> dt nt!_KUSER_SHARED_DATA 7FFE0000 -a TickCountLow +0x000 TickCountLow : 0x9d2a26 lkd> dt nt!_KUSER_SHARED_DATA 7FFE0000 -a TickCountMultiplier +0x004 TickCountMultiplier : 0xa03afb7 Celle la semble constant ....
Anayway cela nous donne pour le code précédent :
ntdll RtlCreateHeap 7C9357AF BA 0000FE7F MOV EDX,7FFE0000 7C9357B4 8B02 MOV EAX,DWORD PTR DS:[EDX] ;KUSER_SHARED_DATA->TickCountLow 7C9357B6 F762 04 MUL DWORD PTR DS:[EDX+4] ;KUSER_SHARED_DATA->TickCountMultiplier 7C9357B9 0FACD0 18 SHRD EAX,EDX,18 SHRD Double Precision Shift Right 0F AC SHRD r/m32,r32,imm8 3/7 r/m32 gets SHR of r/m32 concatenated with r32 SHRD shifts the first operand provided by the r/m field to the right as many bits as specified by the count operand. The second operand (r16 or r32) provides the bits to shift in from the left (starting with bit 31). The result is stored back into the r/m operand. The register remains unaltered. 7C9357BD 8B4D E4 MOV ECX,DWORD PTR SS:[EBP-1C] ;ecx=adresse de HEAP 7C9357C0 8841 04 MOV BYTE PTR DS:[ECX+4],AL ;HEAP+4 -> SmallTagIndex
Bon ca c’était juste pour le fun hein
On sait enfin comment recalculer le cookie en connaissant bien l’environnement :] Concernant les techniques d’exploitations je ne sais pas encore si j’en parlerais, elles semblent assez difficiles, j’ai regardé les papers et codes de Matt Connover sur le sujet et ….. GROUNF ! je verrais si je continue en fonction de ma motivation. En attendant vous pourvez aller voir les docs que j’ai regroupé sur le sujet dans mon repo http://ivanlef0u.fr/repo/index.php?path=./windoz/heap
Ivanlef0u
Add comment février 18th, 2007