Advanced
Exploitation of Internet Explorer Heap Overflow Vulnerabilities (MS12-004)
Hi
everyone,
2012 has just begun with its bunch of very interesting CVEs. One of them is CVE-2012-0003,
a critical vulnerability affecting Windows Multimedia Library and related to MIDI file handling.
It was patched last week by Microsoft as part of the MS12-004
security bulletin.
Due to the criticality of this vulnerability, we highly recommend applying the
patch as soon as
possible!
From Wikipedia we can read: "MIDI is an industry-standard
protocol, first defined in 1982". So basically, 30 years after its creation,
this format is still causing troubles to software vendors such as Microsoft.
By itself, the vulnerability is quite common but exploitation is not
trivial.
When an application such as Windows Media Player or Internet Explorer parses a
MIDI file, a static heap buffer is allocated but up to 0x440 bytes can be
written to. Nevertheless, due to the large allocation size, common exploitation
techniques for Internet Explorer might not work with this vulnerability which
makes exploitation really challenging.
In this blog we will demonstrates the criticality of this vulnerability by
showing how it can be reliably exploited via Internet Explorer 9/8/7/6 to achieve code execution by bypassing ASLR/DEP.
1. Technical Analysis of the Vulnerability
Here are the key
points of this vulnerability. A MIDI file mainly contains two types of chunks,
one named MThd and the other MTrk. Here follow the structures of
both chunks:
|
Offset |
Field |
Size |
|
0 |
Type = 'MThd' |
4 |
|
4 |
Length = 6 |
2 |
|
6 |
format |
2 |
|
8 |
tracks |
2 |
|
10 |
division |
2 |
|
Offset |
Field |
Size |
|
0 |
Type = 'MTrk' |
4 |
|
4 |
Length |
var |
|
var |
delta_time |
var |
|
var |
event |
var |
Before processing
the file, Windows Media allocates two buffers in "mseOpen()" in winmm.dll:
.text:76B5CDB1 mov edi, [ebp+arg_4]
.text:76B5CDB4 mov eax, [edi+10h]
.text:76B5CDB7 lea eax, ds:94h[eax*8]
.text:76B5CDBE cmp eax, 10000h
.text:76B5CDC3 mov [ebp+var_4], 7
.text:76B5CDCA jnb loc_76B5CED7
.text:76B5CDD0 push ebx
.text:76B5CDD1 push esi
.text:76B5CDD2 push eax
.text:76B5CDD3 call winmmAlloc(x)
// allocate a buffer
.text:76B5CDD8 mov esi, eax
.text:76B5CDDA xor ebx, ebx
.text:76B5CDDC cmp esi, ebx
.text:76B5CDDE jz loc_76B5CED5
.text:76B5CDE4 push 400h
.text:76B5CDE9 call winmmAlloc(x)
// allocate a second buffer of size 0x400
|
The second buffer will be noted
b1 in the following. This specific vulnerability
lies in the way certain events from the MTrk chunk are parsed. These events are
first read in "smfReadEvents()", defined in quartz.dll:
.text:74903483 loc_74903483:
.text:74903483 push [ebp+arg_C]
.text:74903486 lea eax, [ebp+var_14]
.text:74903489 push eax
.text:7490348A push esi
.text:7490348B call smfGetNextEvent(x,x,x) // read an event to var_8
.text:74903490 test eax, eax
.text:74903492 jnz loc_749035B1
.text:74903498 mov ecx, [ebp+var_8]
.text:7490349B cmp cl, 0F0h
.text:7490349E jnb short loc_749034EC
|
An event is identified by its first byte and noted
e1 e2 e3 in the following, so
that ECX = 0x00e3e2e1. Only events where e1 < 0xF0 are of interest. In the next
piece of code, the event is written at offset 8 in an array previously allocated:
.text:749034B4 loc_749034B4:
.text:749034B4
.text:749034B4 mov eax, [esi+10h]
.text:749034B7 add eax, [ebp+var_14]
.text:749034BA movzx ecx, cl
.text:749034BD mov [edi], eax
// write a first dword
.text:749034BF and dword ptr [esi+10h], 0
.text:749034C3 add edi, 4
.text:749034C6 and dword ptr [edi], 0
// write 0
.text:749034C9 movzx eax, byte ptr [ebp+var_8+2]
.text:749034CD movzx edx, byte ptr [ebp+var_8+1]
.text:749034D1 shl eax, 8
.text:749034D4 or eax, edx
.text:749034D6 shl eax, 8
.text:749034D9 add edi, 4
.text:749034DC or eax, ecx
.text:749034DE
.text:749034DE loc_749034DE:
.text:749034DE mov [edi], eax
// write the event
.text:749034E0 add edi, 4
.text:749034E3 add dword ptr [ebx+8], 0Ch // increment the entry counter
.text:749034E7 jmp loc_749035A2
|
This array is next handled by "midiOutPlayNextPolyEvent()", in winmm.dll:
.text:76B5D0B2 mov eax, [ebp+wParam]
.text:76B5D0B5 mov ecx, [ebx+eax]
// read an event to ecx
.text:76B5D0B8 add ebx, 4
.text:76B5D0BB mov eax, ecx
.text:76B5D0BD mov [esi+24h], ebx
.text:76B5D0C0 shr eax, 18h
.text:76B5D0C3 and ecx, 0FFFFFFh
// ecx = e3 e2 e1
|
At this moment, the application distinguishes whether or not e1 > 7Fh:
.text:76B5D1B6 loc_76B5D1B6:
.text:76B5D1B6 cmp [ebp+hmo], ebx
.text:76B5D1B9 mov esi, [edi+84h]
.text:76B5D1BF jz loc_76B5D276
.text:76B5D1C5 test cl, cl
// cl = e1
.text:76B5D1C7 mov al, cl
// al = e1
.text:76B5D1C9 mov ebx, ecx
.text:76B5D1CB js short loc_76B5D1E3 // jump if 80h <= e1 <= FFh
[...]
.text:76B5D1E3 loc_76B5D1E3:
.text:76B5D1E3 mov edx, ecx
.text:76B5D1E5 shr edx, 8
// dl = e2
.text:76B5D1E8 mov [edi+54h], cl
.text:76B5D1EB mov byte ptr [ebp+wParam+3], dl
.text:76B5D1EE shr ebx, 10h
// bl = e3
|
As we can see in the following lines, Windows Media specifically processes
events where e1 & F0h = 80h or 90h:
.text:76B5D1F1 loc_76B5D1F1:
.text:76B5D1F1 mov dl, al
// dl = e1
.text:76B5D1F3 and dl, 0F0h
.text:76B5D1F6 cmp dl, 90h
.text:76B5D1F9 mov [ebp+var_1], dl
.text:76B5D1FC jz short loc_76B5D203
.text:76B5D1FE cmp dl, 80h
.text:76B5D201 jnz short loc_76B5D25F
|
For such cases, an offset is computed according to e1 and e2 to write data to
the buffer b1 allocated above:
.text:76B5D203 loc_76B5D203:
.text:76B5D203 movzx edx, byte ptr [ebp+wParam+3] // edx = e2
.text:76B5D207 and eax, 0Fh
// eax = e1 & 0Fh
.text:76B5D20A shl eax, 7
.text:76B5D20D add eax, edx
.text:76B5D20F cdq
.text:76B5D210 sub eax, edx
.text:76B5D212 sar eax, 1
.text:76B5D214 cmp [ebp+var_1], 80h
.text:76B5D218 jz short loc_76B5D244
|
At the end, EAX = ((e1 & 0Fh) * 2^7 + e2) / 2.
This is where the data in b1 is
going to be altered. Note that for particular values of e1 and e2, it is
possible to get EAX > 400h. For example, e1 = 9Fh => 0Fh * 2^7 = 780h. Then if
e2 > 7Fh, e1 + e2 > 800h which makes EAX >= 400h and the program writes past the
bounds of the allocated buffer.
Depending on whether e1 & F0h = 90h or 80h, or even e3 = 1, it is possible to
increment or decrement an arbitrary byte. For example with e1 & F0h = 90h:
.text:76B5D21E add esi, eax
.text:76B5D220 test byte ptr [ebp+wParam+3], 1
.text:76B5D224 mov al, [esi]
// read a byte in b1
[...]
.text:76B5D23E inc al
.text:76B5D240 mov [esi], al
// increment that byte
|
If e1 & F0h = 80h:
.text:76B5D248 lea edx, [eax+esi]
.text:76B5D24B mov al, [edx]
// read a byte in b1
[...]
.text:76B5D25B dec al
.text:76B5D25D mov [edx], al
// decrement that byte
|
Since b1 is 0x400 bytes long, a heap overflow occurs when
e1 = 8Fh or 9Fh and when e2 > 7Fh. In practice, it becomes possible to corrupt the
0x40 bytes
following b1, which is enough to achieve arbitrary code execution.
2.
Advanced Exploitation With ASLR/DEP Bypass
Since the vulnerable
module can be loaded in Internet Explorer, it is
possible to achieve a reliable exploitation of
this flaw.
As we can see in "DllProcessAttach()" in
winmm.dll and "_DllMainStartup()" in
mshtml.dll, both libraries use the same heap
for their allocations:
In "DllProcessAttach()":
.text:76B43F8F mov eax, large fs:18h
// GetProcessHeap
inlined
.text:76B43F95 mov eax, [eax+30h]
.text:76B43F98 mov eax, [eax+18h]
.text:76B43F9B mov _hHeap, eax
|
In "_DllMainStartup()":
.text:3CEAC930 call GetProcessHeap()
.text:3CEAC936 push eax
.text:3CEAC937 mov _g_hProcessHeap, eax
|
As a result, it is possible to exploit this
vulnerability to corrupt an Internet Explorer object.
Usually, exploitating such vulnerabilities consists
in finding an object whose size is equal to the
vulnerable buffer's size. In that case,
exploitation can be achieved in the following way:
allocate contiguously the vulnerable buffer, a
string and an object. Once done overwrite the
string's length to disclose the vTable and deduce
mshtml.dll's base address:

Allocate then a new buffer and
trigger the vulnerability a second time to
overwrite the object's vTable:

At that point, spray the heap with
a dynamic ROP for mshtml.dll and call a function
from the corrupted object to redirect the execution
flow.
(Un)fortunately, this method requires the
vulnerability to trigger twice and only works as long
as it is possible to allocate an object of a given
size. For sizes < 0x100 bytes, IE provides enough
objects for that method to work but in the
particular case of the MS12-004 vulnerability, the buffer is
0x400 bytes long, and doing an XREF on 3FCh or 400h in
mshtml.dll does not provide any interesting object
allocation.
A reliable exploit for this vulnerability can
however be created by taking advantage of a
particular CImplAry object. The idea consists in
creating an array of size 0x400, and precisely
altering its content to disclose an arbitrary vTable and redirect the execution flow.
Such an array can be created for instance when an
element is cloned. As we can see, "CElement::Clone()" leads to call
"CElement::CloneAttributes()" and eventually
"CAttrArray::Clone()" to clone the attributes. The following lines belong to
"CAttrArray::Clone()":
.text:3D06A356 call CAttrArray::operator new(uint)
// allocate a new CAttrArray object
.text:3D06A35B cmp eax, edi
.text:3D06A35D jz short loc_3D06A368
.text:3D06A35F mov ecx, eax
.text:3D06A361 call CAttrArray::CAttrArray(void)
// initialize the object
.text:3D06A366 mov edi, eax
.text:3D06A368
.text:3D06A368 loc_3D06A368:
.text:3D06A368 test edi, edi
.text:3D06A36A mov esi, [ebp+arg_4]
.text:3D06A36D mov [esi], edi
.text:3D06A36F jz loc_3D0F5DE5
|
At that point, the application checks whether the
original element contains an attribute:
.text:3D06A375 mov eax, [ebx+4]
.text:3D06A378 shr eax, 2
// eax represents the
number of attributes
//
associated with the original element
.text:3D06A37B js loc_3D053663
.text:3D06A381 cmp eax, [edi+8]
.text:3D06A384 jbe loc_3D0F5DF1
.text:3D06A38A push 10h
.text:3D06A38C call CImplAry::EnsureSizeWorker(uint,long)
|
If this number is not NULL, IE allocates 10h *
#attributes
in "CImplAry::EnsureSizeWorker()". As a result, if the
original element has 0x40 attributes defined, IE
allocates exactly 0x400 bytes for the new array.
These attributes are next copied by "CAttrValue::Copy()":
.text:3D06A3D9 mov byte ptr [esi+1], 0
.text:3D06A3DD call CAttrValue::Copy
|
Consider now the following script, which creates a
new Select element, associates various attributes
and clones the node:
var test = document.createElement("select")
test.obj0 = "AAAAAAAAAAAAAAAAAAAA"
test.obj1 = this
[...]
test.obj8 = alert
[...]
test.obj12 = new Date()
var cl0ne =
test.cloneNode(true)
|
Once cloned, the CImplAry looks like this:
 Actually an attribute is
identified by three fields and uses 0x10 bytes in
memory. Pay attention to the bytes in red on
Figure 3. They represent the variant types as
defined in
MSDN: 0x08 indicates a string, 0x09 an object,
0x03 an integer etc.
The following figure shows obj0, obj8
and obj12 as represented in memory:

Since the application
specifically relies on the variant type to
determine the type of the attribute, it becomes
possible with a heap overflow to corrupt the array
and force the browser to confuse types. Figure
5 shows the CImplAry after incrementing
and decrementing two bytes:

On Figure 5,
obj0 is
now an object while obj1 is a string. It is
then possible to bypass ASLR/DEP by leaking the
object's vTable using JavaScript. This gives the
following result:

Then the corrupted string
is used to
trigger the vulnerability
in "CAttrValue::GetIntoVariant()"
and reach a CALL instruction leading to
arbitrary code execution despite ASLR/DEP:

Two bytes need to be altered for that method to work.
If you manage to do it with only one, please let
us know! Note also that this method works reliably with
all Internet Explorer versions including 9/8/7 and even IE6,
since heapAlloc is used for sizes > 0x230.
As demonstrated in this blog, this vulnerability
is really critical thus we highly recommend
applying the patch
as soon as
possible!
© Copyright VUPEN Security
|