The cool part is that we've got full control over the ESI register and there's an indirect call based on it, so that means remote code execution (assuming that we can find a way to bypass ASLR); the bad news is that, before that indirect call, there's a call to the Control Flow Guard validation function. So I created this simple Python script, which generates a file called "
some_data", which is the one requested using
XMLHttpRequest, containing our specially crafted fake
with open("some_data", "wb") as f:
f.write(struct.pack('<H', 0x0009)) # varType == vbObject
f.write(struct.pack('>H', 0x5051)) # dummy
f.write(struct.pack('<L', 0x41414141)) # dummy
# this is interpreted as a pointer and dereferenced twice to call a function pointer.
# On Windows 8.1, dword[0x7ffe0270] == 0x00000003 --> call dword[0x00000003]
f.write(struct.pack('<L', 0x43434343)) # dummy
Note that the first word of our data has value 0x0009, thus defining our fake object as being of type
vbObject. This way we can reach the code path that leads us to arbitrary code execution:
CALL DWORD [ESI]. Also, the dword at offset 8 of our data is the one being interpreted as a pointer and dereferenced twice to call a function pointer. In this case, for demonstration purposes, our arbitrary pointer has value
0x7ffe0270, which is the fixed address of the
NtMinorVersion field of the
nt!_KUSER_SHARED_DATA data structure. That address holds the value 0x00000003 on Windows 8.1. If you run this PoC with the debugger attached to the browser process, you'll see that IE crashes here, when the CFG stub tries to load into ECX the function pointer stored at address 0x00000003, right before calling the CFG validation function:
vbscript!VAR::ObjGetDefault+0x6f: 64af9179 8b0e mov ecx,dword ptr [esi] ds:0023:00000003=???????? 0:006> u @eip vbscript!VAR::ObjGetDefault+0x6f: 64af9179 8b0e mov ecx,dword ptr [esi] 64af917b ff1534e3b364 call dword ptr [vbscript!__guard_check_icall_fptr (64b3e334)] 64af9181 ff16 call dword ptr [esi] 64af9183 3bfc cmp edi,esp 64af9185 0f8529590000 jne vbscript!VAR::ObjGetDefault+0x7d (64afeab4) 64af918b 85c0 test eax,eax 64af918d 0f883d230100 js vbscript!VAR::ObjGetDefault+0x123c6 (64b0b4d0) 64af9193 8d442410 lea eax,[esp+10h] Clearly, at this point we need to deal with ASLR first, and then CFG in order to get code execution.
Trying to use the same vulnerability to bypass ASLR
To make a long story short, I tried to take advantage of this vulnerability to both bypass ASLR and gain code execution. By playing with the type of our fake VBScript object (the one requested using
XMLHttpRequest) it is possible to, for example, convert the dword stored at an arbitrary address to a string with that value in decimal representation. So the vulnerability looked very promising for ASLR bypassing purposes; however, being able to access leaked memory contents from our VBScript code requires us to return from
vbscript!rtFilter with a non-negative value, otherwise the VBScript's
Filter function won't return the results containing our leaked information. According to my tests, there's an (almost) unsatisfiable condition that needs to be satisfied in order to return from
vbscript!rtFilter with a non-negative value: due to the type confusion issue,
vbscript!rtFilter will loop through the number of elements of our fake array, assuming that the size of each element of our array is 0x10 bytes (the size of
vbVariant); however, the size of each element of our array is 1 byte, since it's an array of
vbByte elements. Let's say that we have a crafted array of 10 bytes; element count is 10, element size is 1, so total size is 10 bytes. However, for
vbscript!rtFilter, element count is 10, but it assumes that element size is 0x10, so it will loop beyond our array, thinking that we've provided 160 bytes of data, trying to process that out-of-bounds data as
Variant objects. There are some chances to overcome that seemingly unsatisfiable condition, like using heap manipulation techniques in order to put specially crafted data after our array of
vbByte elements, so when
vbscript!rtFilter runs beyond the end of our array it still manages to parse that data as well-formed
Variant objects. However, this idea looked like a significant effort to me, so I decided to focus my energy on trying to exploit a second vulnerability from the same MS15-106 bulletin in order to bypass ASLR.
This type confusion vulnerability affecting the VBScript engine can be leveraged to gain code execution in the context of Internet Explorer in a straightforward way; by providing the vulnerable
Filter function with a VBScript array with element type different than
vbVariant as its first argument, it's possible for attacker-controlled data to get interpreted as a VBScript object, which ultimately leads to an indirect call which gets fully controlled by the attacker. Beyond clearly allowing for remote code execution, this vulnerability also looked promising for bypassing ASLR. However, due to the difference between expected array element size (0x10) and provided array element size (1), I wasn't able to make the vulnerable function return with no error, and that prevented my exploit code from being able to access leaked memory contents. That's why I decided to move on and try to exploit a second vulnerability from the same MS15-106 security bulletin in order to bypass the first hurdle: Address Space Layout Randomization. Oh, and we'll still need to bypass Control Flow Guard if we are hoping to take this bug all the way to remote code execution... So stay tuned for the second part of these blogposts installments, in which we are going to discuss how to exploit a second vulnerability in the MS15-106 bulletin, this time a memory disclosure one, in order to bypass ASLR! Do you want to take smarter steps to reduce the vulnerabilities in your network? Core Impact Pro is packed with hundreds of unique CVEs with more added on a continuous basis. To further expand library of CVEs, Core offers the ability to integrate with open source frameworks, further increasing the amount of unique CVEs offered.