The purpose of this project is to obtain an initial access during Red Team exercise by gaining code execution through the exploitation of a vulnerability.
This first article describes the first steps of the exploit development regarding the CVE-2018-5093 explaining how to take advantage of the integer underflow vulnerability on Firefox 56&57 to control the instruction pointer when opening a specifically crafted web page.
The development of the proof of concept was done under a Windows 10 with 21H2 version environment and with the help of WINDBG debugger.
Currently, there is not any article with a functional exploit which was published. That is the reason why, the development of this exploit was performed with the help of the ExodusIntel’s write-up.
To understand this paper, you need to have some knowledge of assembly with the usage of the stack and the heap as well as some famous techniques in browser exploits like Heap Spraying.
The vulnerability was found in the WebAssembly code in the xul.dll library and reported in 2018 to Mozilla. This vulnerability is fixed in Firefox 58 and 59 since 2020.
In the Figure 1, as a proof of concept, here is the easy way to trigger the vulnerability.
Figure 1: proof of concept
This vulnerability is triggered when the get() function of the Table object is called. In fact, when the get() function is called, a call of the getImpl() function is executed. However, in the getImpl() function, there is a check of the index entered in the argument of the get() function. It is the ToNonWrappingUint32() function which checks if the index is between 0 and table.length() – 1, as we can see in the Figure 2.
Figure 2: underflow vulnerability
Triggering the bug
As presented in the screenshot above, when we create the Table object, we can specify a table size of 0 with the initial component in the Figure 1, so table.length() can be equal to 0. Then, when table.length() is equal to 0, the value table.length() – 1 is -1, but the function ToNonWrappingUint32() has the arguments type unsigned int 32, so the -1 value becomes the maximum value of the unsigned int 32, it is an underflow, so now the function cannot check the value of the index and all numbers coded in 32 bits can be used as an index.
In fact, a signed integer on 8 bits, that is equal to -1, has binary representation like following: 1111 1110.
So, the program will interpret this as the unsigned integer 254, which corresponds by the maximum value coded on 8 bits – 1.
Reversing the affected component
After analyzing with GHIDRA, the assembly code which allows exploiting the vulnerability is in Figure 3, because it is the line which permits to retrieve the object at the position index.
Figure 3: Vulnerable assembly code
So, after this line, we can control some registers and we can go where we want. For this reason, we should be able to control the EIP/RIP register if we can execute an assembly instruction which performs a call to register that we control.
Some functions have the instruction which allows us to perform a call on a register. So, after analyzing in depth, this instruction is located within the function FUN_107bc8d6 (See Figure 4).
Figure 4: Targeted function
With the control of some registers, we need to find a chain of calls to trigger the function and we identified a way to chain other functions as presented here:
Figure 5 : Way to call target function – our chain
Controlling the instruction pointer
Figure 6: Access violation
In this Figure, the program wants to retrieve an object at the address 0x169a40b0, but it does not exist because the position is bigger than the allocated Table size.
Precise Heap Spraying
At this point, if we use a precise Heap Spray, we can put a valid address that we can control in the location where the flow wants to access in order to permit us to use our fake object and have control on some useful registers.
The precise Heap Spray payload is crafted based on the Corelan’s article demonstrating this technique and how to spray the Heap more precisely resulting on the payload presented below.
Figure 7: Precise Heap Spray payload
Using this technique, we will spray the heap with the 0x10101010 address. Then, when the program takes the address of the object, it will take 0x10101010 value as we can see in Figure 8.
Figure 8: Address Corrupted
With the precise Heap Spray, the 0x10101010 address contains only null bytes until the 0x10101090 address, like wanted in Figure 9 and shown in Figure 10.
Figure 9: Precise Heap Spray with null bytes at the 0x10101008 address until the 0x101010A0 address
Figure 10: Content in the heap
Chaining function calls
From now, we want to execute the assembly program until the call of the function FUN_1234294b.
To do so, we have to modify some addresses in the precise Heap Spray in order to bypass all the checks which check if the object retrieved has a good format, so if the object has the object type requested in the getImpl() function (see Figure 2) before the call of the targeted function.
However, to identify the content and the location of the data, we will modify step by step the data in the Heap Spray with WINDBG until the execution flow reach the targeted function.
When the program is executed with the debugger, we have an Access Violation (Figure 11), because the program wants to access at 0x00000008, but this address does not exist.
Figure 11: Access violation
The program tries to access to that address because there is 0x00000000 at 0x10101018.
Figure 12: ESI value affected
That is the reason why we change the Heap Spray as presented in Figure 13 to have a good address at 0x10101018 that we can control. At this point, we can choose the address 0x10101040.
Figure 13: Precise Heap Spray with one modification
Following this principle, we continue to modify the Heap Spray to execute the assembly code until the function FUN_1234294b is called.
So, from now here is what our Heap Spray payload looks like:
Figure 14: Precise Heap Spray with some modifications to bypass all the checks
Now, we are in the function FUN_1234294b, and we want to call the function FUN_104e3000:
Figure 15: FUN_104e3000 call
Unfortunately, as we can see in the Figure 17, debugging step by step with WINDBG, the program will jump to 0x7a682a35 because ESI is equal to 0x00000000. However, if the program jumps to that address, it will execute a POP RET instruction (Figure 16) whereas we want to execute the instructions in the LAB_123429b2 in the same function, so we do not want to jump.
Figure 16: LAB_123429b2 function with POP RET instructions
Figure 17: Jump in the LAB_12342A35
Therefore, we change the value at the 0x10101044 address to have the ESI register different from 0x00000000 to avoid the execution flow to execute the unwanted jump.
As a result, we can see in Figure 18 that the program wants to access to the 0x101ffff0 address, so we need to adapt the Heap Spray like in Figure 19 to permit this and have 0x10101010 value at 0x101ffff0.
Figure 18: Access to 0x101ffff0 address
Figure 19: Heap spray adaptation to control the 0x101ffff0 address
Now, if we inspect the heap using our favorite debugger, we have the data presented in the screenshot below which permits to validate that our Heap Spray is correctly fixed, because we have added 101010101111111111 at the end of the precise Heap Spray payload.
Figure 20: Content of the Heap
We are in the function FUN_104e3000, and then the function FUN_104e33c0 is now called, our chain is now working!
Finally, the target function FUN_107bc8d6 is called and an access violation is triggered.
Figure 21: Access Violation
In fact, the cause of the access violation above is triggered because the program tries to call a function at the 0x00000010 address.
Achieving Instruction Pointer control
What we have to do for our finial step is to change this address by another one that we can control by adapting our payload.
For this last modification (but the last one!) we have to adapt the 0x10101068 address value because the EAX register contains what we need at this address.
In Figure 22, we change that value by 0x10101080 knowing that 0x10101080 + 0x10 = 0x10101090 (EAX+0x10 call) and we have added 0x41414141 at this address to obtain the 0x41414141 value in our instruction pointer register.
Figure 22: Final precise Heap Spray payload
In Figure 23, we can see that the instruction pointer is now equal to 0x41414141, so we succeeded in overwriting the EIP by our 0x41414141 value.
Figure 23: Controlling instruction pointer
At this point, technically we can execute what we want by controlling the execution flow. However, since it is real-life, the program implements some protections like ASLR, DEP/NX protection…
So, the second part of this article will explain how bypass these instructions to gain code execution.