Heap Overflows Revisited, Part 2

février 16th, 2007 at 05:55 admin

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

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