In Part One of this article, we looked at how the Internet Explorer (IE) exploit known as CVE-2013-3893 got its foot in the door of Windows, if you will pardon the pun.
In Part Two, we are going to follow the exploit as it takes over IE, suppresses Data Execution Prevention (DEP), and reaches a point where it can run pretty much any program code it likes just as if you had downloaded it yourself.
We think this exploit makes a good example for study, because:
- It can theoretically be used against IE from version 6 to 11.
- It works despite DEP and ASLR (Address Space Layout Randomisation).
- It is already widely circulated, so we aren’t giving away secrets.
- The vulnerability on which it relies is already patched.
If you haven’t yet read Part One, we suggest that you do so now, as it explains where we will be starting this time, and how we got here.
The story so far
Our test system is running IE 9 on Windows 7 32-bit, and at the end of Part One, our attackers had:
- [1] Used malicious Javascript to fill known memory addresses with chosen binary data.
- [2] Loaded a known DLL at a fixed memory address, despite ASLR.
- [3] Crashed IE in such a way as to cause it to jump to a memory location of their choice from [1].
Visually, our attackers are here:
Controllable knowns
As we explained in Part One, the attackers used JavaScript to allocate and free up a series of text strings to provoke a use-after-free bug in IE.
The bug caused Microsoft’s HTML renderer to use untrusted text string data from the malicious JavaScript to tell IE where to jump in memory, leaving the crooks with three “controllable knowns”:
- Execution is about to jump to the memory address stored in location 0x121212D6, shown in yellow.
- The addresses shown in grey, from 0x12121212 onwards, are precisely controlled by a text string in the untrusted JavaScript.
- The DLL hxds.dll, part of Office, is loaded into executable memory at the unrandomised address 0x51BD0000.
Deciding where to go next
The attackers are about to jump to the address specified in 0x121212D6, and they control that value directly from their JavaScript.
With the memory layout shown above, where 0x12121212 has been written repeatedly, the attackers can’t actually get control of IE.
That’s because the address stored in 0x121212D6 (shown in yellow above) is in heap memory that is protected by DEP, and provokes an access violation if it executed:
But if the crooks choose an address inside hxds.dll, they will reach memory marked a executable, and they will know what code is going to execute next, because the address space of that DLL isn’t randomised.
In the actual exploit, the address stored at location 0x121212D6 (the yellow bytes below) is 0x51BD28D4, as shown here:
→ Don’t forget that the x86 and x64 Intel CPUs used in Windows computers are little endian. That means that the least significant byte of a multi-byte value is stored at the lowest memory address, and so on. So the 32-bit value 0xC001D00D would actually appear in memory as the bytes 0D D0 01 0C), just as 0x51BD28D4 appears above as D4 28 BD 51.
If we disassemble the code at the address chosen by the criminals, we get this:
MOV EAX, [ECX] ; Fetch the contents of the ; memory address in ECX, ; where ECX is controlled ; by the attackers CALL [EAX+8] ; Call the location specified ; in the address 8 bytes past that.
This time, the two lines of code above cause the following chain of execution:
The value of ECX above was forced to the value 0x12121202 by the attacker’s malicious JavaScript, and the contents of the memory block at and around the addresses shown above (from 0x1212111E0 to 0x12121310) were set up by the crooks in the same way.
At the moment, the attackers control the instruction pointer (EIP), but can’t yet aim it at their own machine code because of DEP.
The next best thing, then, is to control the stack pointer (ESP), because the stack lets you set up calls to system functions.
Pivoting the stack
The value chosen for the destination of the CALL [EAX+8], shown in green above, is critical to the rest of the exploit, and gives the attackers control of the stack by means of what is called a stack pivot.
The pivot for this exploit can be seen by disassembling at 0x51BE4A41:
XCHG EAX,ESP ; Put EAX into ESP, and vice versa RET
A stack pivot is just a fancy name for any machine code instruction sequence that sets ESP to an attacker-controlled value: it could be a MOV, a PUSH followed by POP, or, as here, an XCHG instruction that swaps the values in EAX and ESP.
The attackers can now use a trick called Return Oriented Programming, or ROP, to control the flow of code execution indirectly.
That’s because the stack now consists of the bytes shown in grey here:
Converted from little endian notation and listed as a vertical stack of 32-bit addresses and their contents, we get this:
12121212: 51C3B376 <--ESP points here after pivot 12121216: 51C2046E 1212121A: 51BE4A41 1212121E: 51C2046E 12121222: 51BD10B8 12121226: 51C0E455 1212122A: 51C3B376 1212122E: 51BD71F4 12121232: 121212DA 12121236: 12121212 1212123A: 00001000 1212123E: 00000040 12121242: 12120A0C 12121246: 51C3B376 1212124A: 51C3B376 1212124E: 51C3B376 . . . .
Thanks to the stack pivot, the attackers are about to execute a RET instruction with the stack pointer aimed at the topmost value in the list above.
Since RET, or “return from subroutine”, pops the value off the top of the stack and jumps to it, the attackers will now leap back into a carefully chosen instruction sequence inside hxds.dll.
In fact, you’ll notice that the topmost eight values on the stack are all addresses inside hxds.dll, so if each of the instruction sequences pointed to by those addresses ends with a RET, the attackers will execute a stitched-together series of instructions of their choice.
That’s not as convenient as simply putting the machine code they want right in their exploit data, but it’s the next best thing, and it’s where ROP gets its name.
→ In exploit literature, each instruction-snippet-plus-RET pointed to by a list of ROP addresses is known as a gadget. A string of ROP gadgets makes a ROP chain or program. ROP programs typically end up following a Byzantine execution sequence, leaping hither and thither in a DLL that hasn’t had its location randomised. This apparent complexity is irrelevant to the CPU, of course, which simply goes where it is told, and does what it is instructed.
The ROP gadget chain
Here’s what we get if we disassemble the gadgets at each of the addresses on the stack:
The chart looks rather complex, but the results are surprisingly straightforward:
- [1] The first step simply returns to the next ROP gadget, like a NOP (no-operation) instruction.
- [2] The POP EDI in step [2] serves merely to skip over the next gadget address (the already-used stack pivot); the value stored in EDI is irrelevant.
- [3] This time the POP instruction loads EDI with the data value 0x51BD10B8 off the stack, and that value is important.
- [4] Now EAX is loaded with the value stored at 0x51BD10B8. The POP EDI is redundant, but couldn’t be avoided by the attackers, who have to work with the gadget sequences available in hxds.dll.
- [5] The address loaded into EAX is used as a function pointer, and called by the ROP program by PUSHing it on the stack and then jumping to it with a RET instruction.
Notice that when the final RET in step [5] is processed, the top five values on the stack, denoted [P] in the chart above, are as follows:
12121232: 121212DA 12121236: 12121212 1212123A: 00001000 1212123E: 00000040 12121242: 12120A0C
That leaves three vital questions: what is the memory address stored in location 0x51BD10B8, why did the attackers choose it, and what are the [P] values for?
Neutralising DEP
On our test system, the address stored at location 0x51BD10B8 was 0x759F50AB; when disassembled, it turns out to be the entry point of the function VirtualProtect() in the core system library kernel32.dll:
Even though Windows randomises where this function is loaded, in order to make it hard to find (for reasons which are about to become obvious), the attackers can nevertheless locate it.
That’s because the variable location of the randomised entry point is saved at a fixed location in the unrandomised library hxds.dll.
Understanding system calls
Under 32-bit Windows, system calls are made with the stack set up as follows:
[ESP] -> Return address in calling program [ESP+04] -> Parameter 1 passed to system call [ESP+08] -> Parameter 2 [ESP+0A] -> Parameter 3 [ESP+0C] -> Parameter 4 . . . . etc.
→ When preparing for a system call, the parameters are PUSHed onto the stack in reverse order. (The stack grows upwards, towards lower memory addresses, in the diagram above.) That makes it easier to support functions with a variable number of arguments, since the first parameter is always 4 bytes down the stack; the second 8, and so on, regardless of how many arguments there are altogether.
Now the [P] values in the bottom-most section of the ROP chart above can be decoded, because they are the four parameters passed into, and the return address from, the function VirtualProtect():
This means our attackers are on the point of changing the memory protection for their exploit data, like this:
The memory area they will re-protect is the 4KB block starting at 0x12121212, which will end up with the protection permissions PAGE_EXECUTE_READWRITE.
The memory address 0x12120A0C is used to save the previous protection setting; the crooks don’t have any use for this information (the exploit doesn’t tidy up after itself), but the VirtualProtect() function won’t work without it.
And the return address, 0x121212DA, is the beginning of the memory block shown in blue below, immediately following the yellow value at 0x121212D6, where the exploit started off:
Launching the shellcode
When our attackers return from VirtualProtect(), they will effectively have regressed the protections in Internet Explorer to be much like they were under IE 6 on Windows XP2 and earlier.
They will no longer need ROP gadgets to execute code snippets inside hxds.dll: their own shellcode, shown in blue above, will run directly out of heap memory once DEP protection is removed.
And because their malicious executable code will run without triggering any dialog boxes or “are you sure” warnings that would tip off a well-informed user, they’re all set for arbitrary Remote Code Execution.
Does it also bypass EMET’s DEP and ASLR, but not ROP?
Thanks Paul for the breakdown.
Takes me back to my Motorola 6800 days. Though the code was far less complex and we worked without a disassembler, even then we played with stack manipulation.
As always it's regretful that their modern day skills have not been put to better use.
Excellent article. Really enjoyed reading it. A must read for anyone in secuirty. Please write more articles like this.
Thank you for so detailed forensics
I find myself wondering how hsdx.dll was loaded with the correct variable pointing to the entry point of VirtualProtect(). Does it queries kernel32.dll when loaded to get this information?
My point being, is there a mechanism that attackers could use to bypass ASLR by themselves querying for memory location variables of randomly-loaded DLLs? Or would that require execution, which is impossible because they haven't yet broken DEP due to working from the heap?
Thanks for the writeup — I look forward to part 3!
If DLL A wants to call a function F in DLL B, it needs to know the right address to use. (This address will probably end up stored somewhere in the memory in A.)
Finding and storing this address may be done by Windows when A is loaded , or by A itself at run time, using the system functions LoadLibrary() and GetProcAddress().
So you *might* be able to find out the addresses of key system functions, even without an unrandomised DLL lying around to abuse.
Those randomised addresses are there *somewhere* 🙂
Excellent write-up of this Paul! I wish I would come across more posts like this. Any recommendation on bootcamps or coursework I could read up on to become more familiar with malware bypassing DEP and ASLR? It's writings like this that really motivate me to keep studying up at this detailed level.
In the "Don't forget that the x86 and x64 Intel CPUs used in Windows computers are little endian…" section, shouldn't the example value be "0D D0 01 C0"–no parenthesis and with the "0C" reversed?
Very interesting read! This is something that I have long been interested in but did not know a lot about – at least not to this detail! It would be nice to learn a lot more in this area and develop exploits like this so that I can finally put my assembly knowledge to good use! I shall disclose said exploits to the ZDI or similar organisations, or even the vendors themselves of course.
I would like to see you write more articles like. Any other recommendations for stuff to read that is in a similar manner to this would be greatly appreciated.
“So please join us next time in Part Three, the final installment of this series, when we’ll take the attackers’ shellcode apart and explain the tricks they’ve use to make it harder to understand.”
Will there be a part three? And if when?
More more more! Part 3 please.
Oh , that was like reading Shakespeare :). Absolutely awesome article , waiting for part 3 like I was waiting for Game of the Thrones 🙂
Part 3 please! Lets start off the new year with a bang. PS> Happy New Year!
why it this possible to run code outside of the sandbox ? the ie9 has 2 process and the one that we exploit have lower priv to not be able to do those type of things.. why the IE9 sandbox is bypassed ? why can’t the sandbox enforce this to not execute ?