Heap Overflows Revisited, Part 3

février 18th, 2007 at 03:14 admin

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

Entry Filed under: Non classé


Calendar

octobre 2021
L Ma Me J V S D
« fév    
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts