Text

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 VBScript object:

import struct 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', 0x7ffe0270)) 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: VbsFilter -> rtFilter -> VAR::BstrGetVal -> VAR::PvarGetVarVal -> VAR::ObjGetDefault -> 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.

Conclusion