Besides using arithmetic calculations and array elements, another interesting technique to spray payloads is to use float values. Using these values as payload bytes has the advantage that an attacker’s shellcode resides continuous in memory at run time, because the float values are next to each other located in a constant pool. As the payload bytes are not interrupted by disturbing opcodes emitted by the ASM.JS compiler, all eight bytes of a double float constant are usable as payload.
If you are into (over)hyping and naming vulnerabilities you can call it: ConstantDesaster
So finally, I release the remaining code including our shellcode2asmjs (sc2asmjs.py) tool and exploits for CVE-2016-2819 and CVE-2016-1960. An exploit for CVE-2016-9079 is already available for some time. Most likely, sc2asmjs.py won’t be maintained anymore, however, you can still generate ASM.JS JIT-Spray payloads of different kinds and insert your favorite assembly or binary Metasploit payload to exploit vulnerable Mozilla Firefox versions.
Some last words on CVE-2016-2819 and CVE-2016-1960: There is a very detailed public exploit for CVE-2016-1960, which uses a bruteforce approach (see explanation). However, after a bit of diving into the vulnerable code and patches for both bugs, it was possible to change the inital crashing testcase to reach an easier to exploit code path. The goal is to reach line 1102 where we get EIP control when node->release() is called in nsHtml5TreeBuilder::pop():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
The code is equivalent for firefox-46.0.1/parser/html/nsHtml5TreeBuilder.cpp. As group is a constant on line 1096, we only need one exploit attempt to reach line 1102. This can be achieved when changing the last element from style to dd (see modification). And, as we only need EIP control when using ASM.JS JIT-Spray, exploitation becomes even more comfortable and easier. For more information take a look at the OffensiveCon 2018 slides.
Enjoy! Let me know if you find any mistakes, feedback is welcome!
Best,
Rh0
TL;DR: A simple PoC demonstrates combining three-byte payloads with two-byte payloads. If you want to skip this go to the patch for CVE-2017-5375 and its bypass.
As mentioned in part 1, we can hide x86 instructions within ASM.JS constant assignments such as
1 2 |
|
However, constant folding can easily break our hidden NOPs. If a compiler folds the constants it might emit one instruction, assigning directly the result of the addition with MOV EAX, 0x51212120. Hence, our code would be gone. An easy way to prevent this is to use the foreign function interface (ffi) of ASM.JS. Consider following module:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
The corresponding x86 code prepares the five integer parameters (0xa9909090) for the function ffi_func() before calling it:
1 2 3 4 5 |
|
Again, if we jump into the middle of the third instruction at offset 0x52, we hit our injected code (NOP-sled):
1 2 3 4 5 6 7 8 9 |
|
This way, we can hide again three-byte long instructions within ASM.JS constants without resynchronizing the original instruction stream during runtime.
But the space is limited. The instruction mov dword ptr [esp + 0x7c], 0xa9909090 is represented by c744247c909090a9. If the stack offset is higher, another opcode is used with four-bytes offsets instead of one-byte offsets: The instruction mov dword ptr [esp + 0x80], 0xa9909090 becomes c7842480000000909090a9 in order to keep the correct signedness. Hence, we can use the above trick to only hide 0x80/4 x 3 = 0x60 payload bytes.
Nevertheless, we can use two bytes of an ASM.JS constant as payload bytes and the two other bytes as a relative short jump (ebXX) to the next two payload bytes. This allows us to go beyond 0x80/4 = 32 parameters and 0x60 payload bytes. For example, we use 0x07eb9090 as constants to hide two NOPs and a JMP:
1 2 3 |
|
When we start executing from offset 7, our two NOPs and the subsequent JMP is hit to get the injected code running:
1 2 3 4 5 6 7 8 9 10 11 |
|
This is similar to the technique shown at HITB in 2010. At most, two-byte long instructions are used. It is still enough to build a stage0 payload which resolves VirtualAlloc, allocates RWX memory, copies the stage1 shellcode to it, and jumps to it.
Coming back to CVE-2017-5375. The patch mostly consists of two major changes.
1) The addresses of code allocations are randomized stronger:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
2) The amount of executable ASM.JS/WASM code per process was cut down to 128MB on 32-bit:
1 2 3 4 5 6 7 8 |
|
Shortly after the fix, the limit of 128MB was increased to 160MB in Firefox 51.0.1.
So far, so good. The first thing which caught my attention was the fallback code in the first change:
1 2 3 4 |
|
If VirtualAlloc() fails on a specified randomAddr, then we are back in the former “unprotected” code.
Additionally, I can allocate at most 160MB/64KB = 2560 ASM.JS modules, assuming that I keep one module under 64KB.
So that’s the plan:
And yes! This worked! A proof of concept demonstrates the issue. Again, predictable addresses contained the injected, JIT-sprayed code, and the only necessity to exploit a memory corruption bug is to set EIP to that address (i.e., 0x55550055). Although there’s room for improvement (e.g., being able to choose a very specific address), the mixture of Heap and JIT-Spray was used to bypass DEP and ASLR, once again. This resulted in CVE-2017-5400 and was fixed in Firefox 52.
Best,
Rh0
I always liked the idea of JIT-Spray since the first time I saw it being used for Flash in 2010. Just to name a few, JIT-Spray has been used to exploit bugs in Apple Safari, create info leak gadgets in Flash, attack various other client software, and has even been abusing Microsoft’s WARP Shader JIT Engine
@asintsov wrote in 2010:
No JIT-SPRAY in Flash 10.1. Pages with code are crypted )) But idea will never die, that i show on HITB in AMS)
— Alyosha Sintsov (@asintsov) June 11, 2010
Yes, the idea will never die, and from time to time JIT-Spray reappears…
It greatly simplifies exploiting a memory corruption bug such as an use-after-free, because the attacker only needs to hijack the intruction pointer and jump to JIT-Sprayed shellcode. There is no need to disclose code locations or base addresses of DLLs, and there is no need for any code-reuse.
JIT-Spray is usually possible when:
For example to achieve (1), we can inject NOPS (0x90) in ASM.JS code with:
1 2 |
|
Firefox’ ASM.JS compiler generates the following x86 machine code:
1 2 |
|
When we jump into to offset 01 (the middle of the first instruction) we can execute our hidden code:
1 2 3 4 5 6 7 8 |
|
Thus, in our four-byte constants, we have three bytes to hide our code and one byte (0xA8) to wrap the ADD EAX, … instruction into the NOP-like instruction TEST AL, 05.
To achieve condition (2), i.e., to create many executable regions containing our code we request the ASM.JS module many times:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Technically, ASM.JS is an ahead-of-time (AOT) compiler and not a just-in-time (JIT) compiler. Hence, the function asm_js_function() doesn’t need to be called to get your machine code injected into memory at predictable addresses. It is sufficient to load a web page containing the ASM.JS script.
Each time an ASM.JS module is requested, CodeSegment::create() is called which in turn calls AllocateCodeSegment() in firefox-50.1.0/js/src/asmjs/WasmCode.cpp line #206 (based on the source of Firefox 50.1.0):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
AllocateCodeSegment() further calls AllocateExecutableMemory() in line #67:
1 2 3 4 5 6 7 8 9 10 11 |
|
Finally, AllocateExecutableMemory() invokes VirtualAlloc() which returns a new RW (PAGE_READWRITE) region aligned to a 64KB boundary (0xXXXX0000) (firefox-50.1.0/js/src/jit/ExecutableAllocatorWin.cpp, line #190).
1 2 3 4 5 6 7 8 9 10 11 12 |
|
If we set a breakpoint on VirtualAlloc() in WinDbg, we get the following call stack during runtime (Firefox 50.1.0):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
After returning into method CodeSegment::create(), the ASM.JS compiled/native code is copied to the RW region (firefox-50.1.0/js/src/asmjs/WasmCode.cpp, line #223). And in line #229 the RW region is made executable (PAGE_EXECUTE_READ) with ExecutableAllocator::makeExecutable() invoking VirtualProtect().
1 2 3 4 5 6 7 |
|
Requesting one ASM.JS module many times leads to the creation of many RX regions.
Due to the allocation granularity of VirtualAlloc (64KB) we can then choose a
fixed address (such as 0x1c1c0000) and can be certain that the
emitted machine code is located there (containing our hidden payload).
The astute reader might have noticed that constant blinding is missing and allows to emit ASM.JS constants as x86 code in the first place.
Let’s see how a proof of concept looks in practice: we hide our payload within ASM.JS constants and request the ASM.JS module many times. Hence, we spray many executable code regions to occupy predictable addresses.
The payload consists of two parts:
The payload strictly contains at most three-byte long instructions excepts MOVs, which are handled differently. It was automatically generated by a custom transformation tool shellcode2asmjs which uses the Nasm assembler and Distorm3 disassembler. The payload is strongly inspired by Writing JIT-Spray-Shellcode.
As no memory corruption is abused in this PoC, you have to set EIP in your favorite debugger when you are prompted to ;)
Let’s take a real memory corruption (CVE-2016-9079) and see how super easy exploitation becomes when using ASM.JS JIT-Spray. This use-after-free has been analyzed thoroughly, so most of the hard work to write a custom exploit was already done. Note: We target Firefox 50.0.1 and not 50.1.0 as above.
Despite JIT-Spraying executable regions, following steps are conducted:
That’s all. The full exploit targets Mozilla Firefox 50.0.1, and we don’t need any information-leaks and code-reuse. Note that the Tor-Browser has ASM.JS disabled by default, and hence, ASM.JS JIT-Spray won’t work unless the user enables it.
I wonder if Endgames HA-CFI catches this exploit?
Above exploits contain “hardcoded” payloads within constants. That makes it kind of cumbersome to use different shellcodes. However, we can generate ASM.JS scripts on the fly and invoke them during runtime. A PoC where payloads are exchangeable uses the following:
This way, you can replace the payload with your favorite shellcode of choice (line #33). The PoC and especially the stage0 payload were also auto-generated with the custom shellcode2asmjs tool.
Mozilla fixed this issue in Firefox 51 on Jan. 24, 2017. However, the fix can be bypassed which resulted in CVE-2017-5400. This will be explained in part 2.
]]>Reading this article requires some familiarity with WinDbg, heap spray , and info-leaks.
Hope you enjoy it.
I discovered a vulnerability in an ActiveX Control with the rather oldscool tool COMRaider. The ActiveX Control is a video plugin from X360 Software. Let’s take a look with IDA Free 5.0 into it.
The vulnerability is a simple buffer overflow occuring in the data section of the VideoPlayer.ocx module when using the plugin’s exposed SetText method (sub_10002930). By supplying a string to that method, the code at .text:1000298A and .text:10002991 copies our string to a variable in the data section at .data:100916A8 without bound checks:
While there are no immediate control flow pointers like on the stack, maybe other pointers can be overwritten to achieve interesting things towards program manipulation and remote code execution. Exploitation on Windows XP may seem straightforward due to the absence of ASLR, but what if we want to target some Internet Explorer on Windows 7 or 8? At the end I decided to take that route.
To bypass ASLR, we need an information leak in order to disclose some interesting memory usable for further steps. After some experimentation with calling the SetText method and calling other plugin methods subsequently, some pointers catched my attention.
For example, the content at address .data:10092068 can be controlled via our buffer overflow. This pointer is used in sub_10058BAA which in turn is executed when the plugin’s exposed SetFontName method is dispatched.
When we call SetFontName with a string of size smaller or equal to 0x40 the following happens:
We hit the function sub_10058DAB which retrieves the string’s length and calls sub_10058BAA with the length as 1st argument:
In function sub_10058BAA the address .data:10092068 of our controlled content is moved to ECX at .text:10058BC7 and function sub_1000F775 is called. As the address is passed via ECX to the function, it most likely holds an object’s this pointer:
In sub_1000F775 the object pointer is moved into ESI (.text:1000F784). The object’s 4th DWORD [ESI+0xC] (which we control) is compared to 0, and when it is not 0, program flow continues at .text:1000F7CE. Afterwards, the 4th DWORD is moved to EAX and the function returns. So we now control the return value passed in EAX:
We return into sub_10058BAA from sub_10058DAB and we control EAX. Thus, we can already control WHERE we want to write, but not really WHAT we want to write. Our controlled value is used as pointer and the values 0x40, 0x1, 0x0 and the string length are written. Also, the controlled value is increased by 0xC and then written to memory pointed to by EBX:
This might be already enough to overwrite the length of a BSTR JavaScript string or a length field of an array to create an Info-Leak. During runtime ESI holds the same address as EBX. So we also control [ESI], and gain control over the destination argument of a memcpy when we return into sub_10058DAB from sub_10058BAA.
Back in sub_10058DAB, the string length in EDI is pushed as 3rd argument, the string pointer in EBX as 2nd, and our controlled value in [ESI] as 1st argument before _memcpy is called:
We can use the following to abuse the call to _memcpy, perform an arbitrary write and return without a crash into the JavaScript context. We spray the heap first and then write 0xcafebabe to address 0x1010102C using SetText and SetFontName:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
We can attach
WinDbg to our running Internet Explorer and view the modified
memory starting from 0x10101020 which was previously filled with 0x22222222:
As we can modify any memory arbitrarily (despite the “side effect” values and the appending NULL), we can use tech-niques to make the complete memory readable and writable from JavaScript.
Instead of spraying the heap with strings we use arrays. We create blocks of memory with a size of 0x10000 bytes which become aligned to 0xXXXX0000. The first 0xf000 bytes are filled with a generic array and typed array headers (objects) follow which fill the remaining page. As each typed array header has a size of 0x30 bytes, they become aligned after the generic array data to 0xXXXXF000, 0xXXXXF030, 0xXXXXF060 and so on:
There’s an awesome plugin tool for WinDbg out there called mona. Recently it got the ability to dump objects in a detailed way. We can see the different elements of the typed array header. Amongst other fields, each typed array header has a vtable pointer, a length field, a pointer to its arraybuffer object and to its arraybuffer. We enter !py mona do -a 0x1111f000 -s 0x30 to dump the typed array header at 0x1111F000:
We now trigger the vulnerability such that we overwrite a pointer to an arraybuffer with a desired value. We choose a value of 0x1111F030 and overwrite the pointer residing at 0x1111F010. Thus, we let it point to the subsequent typed array header at 0x1111F030. Additionally, we overwrite the length field of the typed array header with one of our “side effect” values (0x00000040).
Finding the modified typed array is easy: We iterate over all typed arrays and check if their first elements are unequal to zero. It is successfull when we hit the modified array, as its first element points to a typed array vtable. Then we use the modifed array to change the subsequent typed array header: We set the length at 0x1111F048 to 0x7fffffff and the arraybuffer pointer to the start of process memory, namely 0x0. And we can do this with array element writes ( arr[k][i][6] = 0x7fffffff and arr[k][i][7] = 0x0 ).
After the vulnerability and the subsequent manipulation has taken place, we can view the typed array headers in WinDbg:
At that point we have a typed array usable from JavaScript like any other array but with the ability to get and set the complete memory!
As we have a memory readwrite interface we can use it via array accesses to read and write arbitrary memory.
So we can use JavaScript code which asks you for an absolute address and returns the content at that address. If you test it, be aware to supply a mapped address, otherwise you get a crash.
We know that there’s a vtable at 0x1111F060. So let’s read at that address by supplying the value 0x1111F060 to the prompt box:
A message box should pop up showing the resulting content interpreted as DWORD:
This is consistent with the WinDbg output we saw before.
As the heap layout is predictable we can set any object as element into a generic array and leak its address. For example, we can put the ActiveX object as first element into the generic array residing below the page with the manipulated typed array headers. As the array is aligned to 0x11120000, we know that the object’s pointer is located at 0x11120020 (20 bytes are occupied by allocation and array metadata). We simply supply 0x11120020/4 as index to our memory interface array and get the object’s address. You can test it by uncommenting line #102 in the leaking script and supplying 0x11120020 to the prompt box. To verify it with WinDbg, enter dd 0x11120020 .
When we leak content at a specified address and know that the content is a pointer itself, we can use the retrieved content as an index into our memory interface array again. This way, we can subsequently dereference object fields in order to read and rewrite them.
Finally it’s time to pop some calculators. So there’s of course a PoC which achieves code execution and runs calc.exe.
Just a short description what is happening there:
Et voilà! We’ve bypassed full ASLR and DEP and got remote code execution with a buffer overflow in the data section. Fun!
]]>