Premise
It is about time for another bug release. It's Opera's turn, again in October, like three years ago when I released that Opera Stored Cross Site Scripting vulnerability. It must be something with Opera, the month of October and myself, but not sure what :-). Usually, I mirror content from Security-Assessment.com web site but I have decided to release the bug under my blog only, since I could not find an exploit for it and Opera does not consider this as a "browser security issue".
Also, according to Opera, the bug is unlikely to be exploited (see last paragraph of this post for more information) and so I am releasing what I got: a crash PoC.
Below, you will find the same bug analysis I have shared with Opera. I need to admit that I have limited experience in debugging (especially heap related issues) and lack of debugging symbols did not really help in this case. However, I have tried my best to analyse the bug and provide as much feedback as possible to Opera. I would also take the opportunity to thank my colleagues Scott Bell and Nick Freeman for their help when looking at possible ways to exploit this bug. Any feedback is also greatly appreciated and welcome and I would love to hear if the bug is actually exploitable :-). You never know.
Bug Details
Name: Opera – Use After Free - Crash PoC
Vendor Website: www.opera.com
Initial Notification Date: October 4th , 2011
Affected Software: Opera v11.51 and previous versions
Researcher: Roberto Suggi Liverani
CVE: CVE-2011-4152
Description
The PoC (Proof of Concept) provided below causes a crash in the latest Opera v.11.51. Previous Opera versions (down to v.11.00) also crash with variations of the included test case. The attached test case crashes the Opera v11.51 browser in both Windows XP and Windows 7 platforms. The bug causes a memory access violation as a result of dereferencing a dangling pointer. The test case includes a JavaScript crash() function which performs DOM operations on the <em> and <strong> HTML elements. Such operations result in a dangling pointer. The crash only occurs when a quick and large memory allocation in the heap, e.g. heap spray (spray() function) immediately follows the object removal from the DOM. As Opera attempts to reallocate previously freed memory during the heap spray, it dereferences the dangling pointer and it crashes.
Crash PoC |
<html> } |
Debugging
For simplicity, the analysis below is based on testing on a Windows XP virtual machine using Windbg and Opera 11.51:
Testing Environment |
0:000> vertarget Windows XP Version 2600 UP Free x86 compatible Product: WinNt, suite: SingleUserTS kernel32.dll version: 5.1.2600.0 (xpclient.010817-1148) |
Analysis – Opera process running under debugger
When Opera is started with Windbg, the crash occurs at 67453c00. Note that the registers values change at each crash. In 90% of the cases, eax register ends up with a value of feeefeee or with a different but invalid memory address value. During the testing, Opera 11.51 always crashed with the provided test case.
Crash – Opera process running under Windbg |
This exception may be expected and handled. eax=feeefeee ebx=00000000 ecx=01634938 edx=01634938 esi=03dc9c30 edi=01ccd26c eip=67453c00 esp=0012e14c ebp=0012e17c iopl=0 nv up ei ng nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010286 *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Opera\Opera.dll - Opera!OpSetLaunchMan+0x172840: 67453c00 8b481c mov ecx,dword ptr [eax+1Ch] ds:0023:feeeff0a=???????? |
The feeefeee indicates the free-fill pattern added by the heap manager to trap any memory accesses to a heap block after it has been freed. A quick look at the bottom of the stack reporting the access violation indicates use of the heap manager.
Stack at the crash |
0:000> kL ChildEBP RetAddr WARNING: Stack unwind information not available. Following frames may be wrong. 0012e17c 679ff270 Opera!OpSetLaunchMan+0x172840 0012e260 6742247c Opera!OpWaitFileWasPresent+0x26d323 0012e27c 67404148 Opera!OpSetLaunchMan+0x1410bc 0012e290 77fabb68 Opera!OpSetLaunchMan+0x122d88 0012e2c4 77fac859 ntdll!RtlpValidateHeap+0x1e 0012e334 00000000 ntdll!RtlDebugFreeHeap+0x204 |
During backtracing analysis, it was found that eax register at 67453c00 points to the actual heap block pointer to be allocated (or freed). The spray() function in the template performs a heap spray at address 0x0c0c0c0c of 177 blocks with a memory use of approximately: ~178Mb. Such heap spray creates a condition which makes Opera use previously-freed memory. When Opera attempts to dereference the dangling pointer, an access violation occurs and Opera crashes.
By setting the breakpoint below at 67453c00 (eip value at the crash), it is possible to verify the above by analysing Opera behavior with and without the heap spray. Note that Opera only crashes when the heap spray is performed.
Breakpoint to follow eax values |
bp 67453c00 "reax;.printf\"\n\";g" |
Without spray() - No crash condition
It appears that eax register points to the UsrPointer, which is the actual pointer of a heap block to be allocated (or freed).
Last 6 eax values – No Heap Spray – No Crash |
eax=03e8cc18 eax=03e8cc60 eax=019c1b98 eax=01a06088 eax=03d600d8 eax=03d600d8 |
Let's take the last value of eax and analyse it. This is the UserPtr of a heap block (_HEAP_ENTRY+0x8 offset).
Valid UserPtr |
0:009> !heap -p -a 03d600d8 address 03d600d8 found in _HEAP @ a50000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 03d600d0 0009 0000 [07] 03d600d8 00030 - (busy) 0:009> dd 03d600d0 03d600d0 00140009 00180705 01c2d888 00000000 |
The pointer seems valid and refers to: 01c2d888 The same thing occurs for the previous eax values, which are always UserPtr pointers.
With spray() - Crash Condition
At some stage, the UserPtr pointer points to previously-freed memory.
Last 4 eax values – with heap spray – crash condition |
eax=019abd18 eax=043b23b0 eax=049c0f38 eax=FEEEFEEE |
However, let's take a look to a previous eax value: 043b23b0.
Valid Pointer |
0:000> !heap -p -a 043b23b0 address 043b23b0 found in _HEAP @ a50000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 043b23a8 0009 0000 [07] 043b23b0 00030 - (busy) 0:000> dd 043b23a8 043b23a8 000b0009 00180705 01a26c20 00000000 |
043b23b0 appears a valid pointer. Performing the same analysis with previous eax register values obtained from the breakpoint, it is possible to always find a reference to a valid pointer (UserPtr). However, by looking at the eax value 049c0f38 which is next to the crash, it is possible to see that the pointer refers to an invalid memory location (0000001b).
Dangling Pointer |
0:000> !heap -p -a 049c0f38 address 049c0f38 found in _HEAP @ a50000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 049c0f30 000b 0000 [07] 049c0f38 00040 - (busy) 0:000> dd 049c0f30 049c0f30 000d000b 00180705 0000001b 000003c0 |
Attaching Debugger To Opera
Analysis by attaching a debugger differs from the previous one, since the heap is constructed without the debugger. The crash point changes from the one identified previously. The crash occurs at 67453be0, within the same function of the previous crash.
Crash – Attaching Debugger To Opera |
eax=0000065a ebx=00000000 ecx=0336b150 edx=0336b150 esi=0336b0e0 edi=01afefd4 eip=67453be0 esp=0012e14c ebp=0012e17c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000206 Opera_672e0000!OpSetLaunchMan+0x172820: 67453be0 83780400 cmp dword ptr [eax+4],0 ds:0023:0000065e=???????? |
By looking at the stack, it is possible to see a reference to the Front End Manager: RtlpLowFragHeapFree.
Stack – Reference To RtlpLowFragHeapFree |
0:000> kL ChildEBP RetAddr WARNING: Stack unwind information not available. Following frames may be wrong. |
Further analysis demonstrated that the value which ends up in eax register at the crash is set at this point:
Breakpoint |
77fae9dc ".printf \"RtlpLowFragHeapFree+0x88 |
The pointer that the above function uses is the one which appears in ecx and edx registers at the crash. This is demonstrated below.
The following debugging sequence in Windbg was used to reach such conclusion:
- Start Opera;
- Attach Windbg to Opera;
- Set following breakpoint:
Breakpoint 0 |
bp Opera_672e0000!OpWaitFileWasPresent+0x7fd73 6 ".printf \"===INTERESTING FUNCTION===: Opera_672e0000!OpWaitFileWasPresent+0x7fd73 has (EDI+44h) pointer is: %08x which refers to: %08x\\n\", poi(edi+44), poi(poi(edi+44));" |
Always 6 passes are needed past this breakpoint. Then:
- g
- Load the test case into Opera
At this point, the breakpoint should be hit. A further breakpoint should be set to break on write operation on the pointer of interest (in this example, the pointer value is 03bea1e0 which comes from edi+44).
Breakpoint 1 |
ba w 4 poi(edi+44) ".printf \"WRITE ON pointer (edi+44h) - \"; r eip; u eip - 10; r bx; kL;" |
Application should continue to run. The debugger will break again and the registers should look something like below with ebp, edx and esi set to 0.
Stack At Breakpoint 1 |
WARNING: Stack unwind information not available. Following frames may be wrong. 00000000 00000000 Opera_672e0000!OpSetLaunchMan+0x169cc7 eax=0360bda0 ebx=0360bd30 ecx=0360bd30 edx=00000000 esi=00000000 edi=0012e5f0 eip=6744b087 esp=0012e5c8 ebp=00000000 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 |
An additional breakpoint is required:
Breakpoint 2 |
bp 77fae9dc ".printf \"RtlpLowFragHeapFree+0x88 - edi+8 - poi(edi+8): %8x \", poi(edi+8); r edi+8; r bx;" |
At this stage, breakpoint 1 needs to be removed
- bc 1
Then application should continue execution.
The breakpoint 2 will be hit different times. However when poi(edi+8) = 0 for the first time, the register bx should be checked. The value in bx will be the last 2 bytes (ax) of the eax register value at the crash.
Breakpoint 2 Hit – poi(edi+8) = 0 |
0:000> g RtlpLowFragHeapFree+0x88 - edi+8 - poi(edi+8): 0 ^ Syntax error in '.printf "RtlpLowFragHeapFree+0x88 - edi+8 - poi(edi+8): %8x ' eax=05b20000 ebx=017205c7 ecx=00000156 edx=00000173 esi=036da8b8 edi=03bea1d8 eip=77fae9dc esp=0012e4a4 ebp=0012e4d4 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000206 ntdll!RtlpLowFragHeapFree+0x84: 77fae9dc 66895f08 mov word ptr [edi+8],bx ds:0023:03bea1e0=0000 |
Note: edi+8 = 0 and bx = 05c7.
Glancing at the stack below, it is possible to see two references to the heap manager (RtlFreeHeap+0x55 and RtlpLowFragHeapFree+0x84). Both have the same argument: 03bea1e0. This is the pointer that it was encountered before and on which breakpoint 1 was set to identify write operations. This pointer will end up in the crash in both ecx and edx registers.
Stack At Breakpoint 2 – poi(edi+8) = 0 |
0:000> kb ChildEBP RetAddr Args to Child 0012e4d4 77f84493 0012e5f0 03e54280 03bea1e0 ntdll!RtlpLowFragHeapFree+0x84 0012e598 67604d5a 00a50000 00000000 03bea1e0 ntdll!RtlFreeHeap+0x55 WARNING: Stack unwind information not available. Following frames may be wrong. 0012e5ac 674429b8 03bea1e0 0012e5f0 00000000 Opera_672e0000!OpGetNextUninstallFile+0x5d2 00000000 00000000 00000000 00000000 00000000 Opera_672e0000!OpSetLaunchMan+0x1615f8 |
Dump memory of 03bea1e0 returns 0000000.
Breakpoint 2 – Memory Dump of 03bea1e0 |
0:000> dd 03bea1e0 03bea1e0 00000000 00000000 00000000 00000000 03bea1f0 00000000 00000000 00000000 00020080 03bea200 00000000 00000000 00000000 00000000 03bea210 036da8b8 010801ff 01cbcd40 00000000 03bea220 03bea720 03bea250 03bea250 00000000 03bea230 00000000 40120356 00001300 05378640 03bea240 03c4457c 00000000 036da8b8 010801ff 03bea250 03bea218 00000000 00000000 00000000 |
At this stage, in order to skip directly to the crash all breakpoints need to be removed:
- bc 0
- bc 2
A first chance exception should be encountered, as shown below:
Crash |
0:000> g (24c.d0): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=000005c7 ebx=00000000 ecx=03bea1e0 edx=03bea1e0 esi=03bea250 edi=03778414 eip=67453be0 esp=0012e14c ebp=0012e17c iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00010202 Opera_672e0000!OpSetLaunchMan+0x172820: 67453be0 83780400 cmp dword ptr [eax+4],0 ds:0023:000005cb=???????? |
As it can be seen, both ecx and edx contain the pointer which was encountered initially and which was used by RtlFreeHeap+0x55 and RtlpLowFragHeapFree+0x84 as well. Dump memory of edx shows the value which eax has at the crash.
edx – Memory Dump |
0:000> dd edx 03bea1e0 000005c7 00000000 00000000 00000000 03bea1f0 00000000 00000000 00000000 00020080 03bea200 00000000 00000000 00000000 00000000 03bea210 036da8b8 010801ff 01cbcd40 00000000 03bea220 03bea720 03bea250 03bea250 03e00b90 03bea230 00000000 40120356 40000801 05378640 03bea240 03c4457c 01ba049c 036da8b8 010801ff 03bea250 03bea218 00000000 00000000 00000000 |
The initial pointer 03bea1e0 (now a dangling pointer) refers to an invalid memory address value. This value ends up in the eax register at the crash.
Statement from Opera:
"We have not been able to see any angles this can be exploited by. The dangling pointer was pointing to an HTML element object, which has no virtual interfaces. The crash happens reliably in a function traversing the tree and finding the next such element, while the stack leading to that function can vary, depending on the circumstances."
- permalink -