wasm 学习笔记

Last updated on February 8, 2024 am

wasm 笔记

N1CTF Junior出了一道wasm的题目,前几天在国际赛TetCTF上也出了一道wasm的题,不过是属于签到级别,正好借此机会学习一下。

wasm简介

以下内容摘抄自MDN

WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能运行,并为诸如 C / C ++等语言提供一个编译目标,以便它们可以在 Web 上运行。它也被设计为可以与 JavaScript 共存,允许两者一起工作。

于网络平台而言,WebAssembly 具有巨大的意义——它提供了一条途径,以使得以各种语言编写的代码都可以以接近原生的速度在 Web 中运行。在这种情况下,以前无法以此方式运行的客户端软件都将可以运行在 Web 中。

WebAssembly 被设计为可以和 JavaScript 一起协同工作——通过使用 WebAssembly 的 JavaScript API,你可以把 WebAssembly 模块加载到一个 JavaScript 应用中并且在两者之间共享功能。这允许你在同一个应用中利用 WebAssembly 的性能和威力以及 JavaScript 的表达力和灵活性,即使你可能并不知道如何编写 WebAssembly 代码。

TetCTF

题目只给了一个html文件,用谷歌浏览器打开然后测试一下

就是简单的输入然后判断,在html文件中很容易找到关键地方。

此函数用于接收回车信息,并获取文本内容然后调用processInput函数

processInput函数中,先检查格式长度,然后调用wasm模块内容

wasm内容由前面的code而来

可以在Chrome等浏览器将文件下载下来

wasm.init()

1
2
3
4
func $init (;1;) (export "init") (param $var0 i32) (result (ref $type1))
local.get $var0
array.new_default $type1
call $1

调用$1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func $1 (;0;) (export "1")
global.get $global0
i32.const 19
i32.xor
global.set $global0
global.get $global1
i32.const 55
i32.xor
global.set $global1
global.get $global2
i32.const 32
i32.xor
global.set $global2
global.get $global3
i32.const 36
…………

这个函数就是对一些全局变量进行设定,全局变量在wasm开始有定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(type $type0 (struct (field $field0 (mut i32))))
(type $type1 (array (field (mut (ref null $type0)))))
(global $global0 (mut i32) (i32.const 96))
(global $global1 (mut i32) (i32.const 101))
(global $global2 (mut i32) (i32.const 20))
(global $global3 (mut i32) (i32.const 177))
(global $global4 (mut i32) (i32.const 155))
(global $global5 (mut i32) (i32.const 116))
(global $global6 (mut i32) (i32.const 108))
(global $global7 (mut i32) (i32.const 69))
(global $global8 (mut i32) (i32.const 84))
(global $global9 (mut i32) (i32.const 109))
(global $global10 (mut i32) (i32.const 103))
(global $global11 (mut i32) (i32.const 110))
(global $global12 (mut i32) (i32.const 111))
(global $global13 (mut i32) (i32.const 95))
(global $global14 (mut i32) (i32.const 116))
(global $global15 (mut i32) (i32.const 103))
(global $global16 (mut i32) (i32.const 97))
(global $global17 (mut i32) (i32.const 72))
(global $global18 (mut i32) (i32.const 20))
(global $global19 (mut i32) (i32.const 59))

写成对应的python则是

1
2
3
4
5
6
7
array1 = [96, 101, 20, 177, 155, 116, 108, 69, 84, 109, 103, 110, 111, 95, 116, 103, 97, 72, 20, 59]
array2 = [19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36]
global_arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

def init():
for i in range(len(array1)):
global_arr[i] = array1[i] ^ array2[i]

wasm.array_fill()

继续接着往下看,调用了wasm.array_fill函数

1
2
3
4
5
6
7
8
9
10
11
12
func $array_fill (;2;) (export "array_fill") (param $var0 (ref null $type1)) (param $var1 i32) (param $var2 i32)
local.get $var0
local.get $var1
local.get $var2
i32.const 19
i32.add
i32.const -64
i32.sub
struct.new $type0
i32.const 1
array.fill $type1

这个函数有两个参数,结合调用点可知,第一个参数是输入,第二参数是字符

值得一提的是,也可以通过打断点方式查看变量

可以发现wasm中参数传递是通过栈来进行传递的,local.get就是把参数放入当前这个函数的栈中,如果有调用其他函数,也先参数压栈,然后调用,这里调用约定是从左向右,即最右边的参数最靠晚压栈。

比如现在要调用i32.add函数,它需要两个参数,那么就从栈顶依次弹出两个参数,然后调用该函数,如果函数有返回值,那么就压入栈中

现在就可以看到参数9719已经弹出,而函数返回值97+19=116则压入栈中

写成对应的python

1
2
3
4
5
6
def array_fill(str):
str_re = []
for i in str[7:27]:
str_re.append(ord(i) + 19 + 64)
return str_re

以上两个函数都是对输入和全局变量做一些初始的处理,现在进入到check函数

wasm.check

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
func $check (;6;) (export "check") (param $var0 (ref null $type1)) (result i32)
local.get $var0
i32.const 0
i32.const 1
local.get $var0
i32.const 0
call $3
global.get $global0
i32.add
call $4
local.get $var0
i32.const 1
i32.const 2
local.get $var0
i32.const 1
call $3
global.get $global1
i32.sub
call $4

…………

local.get $var0
i32.const 3
i32.const 16
local.get $var0
i32.const 19
call $3
global.get $global19
i32.xor
call $4
local.get $var0
call $5

这个函数也很长,不过细看就会发现一个块在不断循环

1
2
3
4
5
6
7
8
9
local.get $var0
i32.const X ;;where X 0->1->2->3->0->1->2->3->0...
i32.const Y ;;where Y 0<=Y<=20 but multiple of 4s are -4
local.get $var0
i32.const Z ;;where 1<=Z<=19
call $3
global.get $globalZ ;;where 1<=Z<=19
i32.AAA ;;where AAA add->sub->mul->xor->add->sub...
call $4

这里涉及到$3函数

1
2
3
4
5
6
func $3 (;3;) (export "3") (param $var0 (ref null $type1)) (param $var1 i32) (result i32)
local.get $var0
local.get $var1
array.get $type1
struct.get $type0 $field0

这里稍微调试一下就知道是数组取值而已,重点是$4函数

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
func $4 (;4;) (export "4") (param $var0 (ref null $type1)) (param $var1 i32) (param $var2 i32) (param $var3 i32)
local.get $var1
i32.const 0
i32.eq
if
local.get $var0
local.get $var2
local.get $var0
local.get $var2
call $3
local.get $var3
i32.add
i32.const 32
i32.xor
call $array_fill
else
local.get $var1
i32.const 1
i32.eq
if
local.get $var0
local.get $var2
local.get $var0
local.get $var2
call $3
local.get $var3
i32.add
i32.const 36
i32.xor
call $array_fill
else
local.get $var1
i32.const 2
i32.eq
if
local.get $var0
local.get $var2
local.get $var0
local.get $var2
call $3
local.get $var3
i32.add
i32.const 19
i32.xor
call $array_fill
else
local.get $var1
i32.const 3
i32.eq
if
local.get $var0
local.get $var2
local.get $var0
local.get $var2
call $3
local.get $var3
i32.add
i32.const 55
i32.xor
call $array_fill
end
end
end
end

也比较长,但都是if else的操作块,根据参数不同选择不同的操作

结合调用前的堆栈情况,以及调试,可以比较轻松的写出对应的python代码

1
2
3
4
5
6
7
8
9
def four(array, operand, index, cons):
if operand == 0:
array[index] = ((array[index] + cons) ^ 32) + 19 + 64
if operand == 1:
array[index] = ((array[index] + cons) ^ 36) + 19 + 64
if operand == 2:
array[index] = ((array[index] + cons) ^ 19) + 19 + 64
if operand == 3:
array[index] = ((array[index] + cons) ^ 55) + 19 + 64

然后就是最后$5函数,不过这个函数就是比较而已

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func $5 (;5;) (export "5") (param $var0 (ref null $type1)) (result i32)
(local $var1 i32)
local.get $var0
i32.const 0
call $3
i32.const 38793
i32.eq
if
local.get $var1
i32.const 1
i32.add
local.set $var1
else
end
local.get $var0
i32.const 1
call $3
i32.const 584
i32.eq
…………

solve

因此整体加密函数如下

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
from ctypes import *

array1 = [96, 101, 20, 177, 155, 116, 108, 69, 84, 109, 103, 110, 111, 95, 116, 103, 97, 72, 20, 59]
array2 = [19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36, 19, 55, 32, 36]
global_arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]


def init():
for i in range(len(array1)):
global_arr[i] = array1[i] ^ array2[i]


def array_fill(str):
str_re = []
for i in str[7:27]:
str_re.append(ord(i) + 19 + 64)
return str_re


def four(array, operand, index, cons):
if operand == 0:
array[index] = ((array[index] + cons) ^ 32) + 19 + 64
if operand == 1:
array[index] = ((array[index] + cons) ^ 36) + 19 + 64
if operand == 2:
array[index] = ((array[index] + cons) ^ 19) + 19 + 64
if operand == 3:
array[index] = ((array[index] + cons) ^ 55) + 19 + 64


operands = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
indexes = [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12, 17, 18, 19, 16]
ipt = 'TetCTF{aaaaaaaaaaaaaaaaaaa}'

init()
ipt = array_fill(ipt)
# print(ipt)
# print(len(ipt))

op = 0
for i in range(20):
if op % 4 == 0:
# print(global_arr[i] + ipt[i])
four(ipt, operands[i], indexes[i], global_arr[i] + ipt[i])
if op % 4 == 1:
four(ipt, operands[i], indexes[i], ipt[i] - global_arr[i])
if op % 4 == 2:
four(ipt, operands[i], indexes[i], global_arr[i] * ipt[i])
if op % 4 == 3:
four(ipt, operands[i], indexes[i], global_arr[i] ^ ipt[i])
op = op + 1

print(ipt)


用z3一把梭

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
from z3 import *

global_arr = [115, 82, 52, 149, 136, 67, 76, 97, 71, 90, 71, 74, 124, 104, 84, 67, 114, 127, 52, 31]
flag = [BitVec('flag_%i' % i, 32) for i in range(20)]
enc = [38793, 584, 738, 38594, 63809, 647, 833, 63602, 47526, 494, 663, 47333, 67041, 641, 791, 66734, 35553, 561, 673,
35306]


def four(array, operand, index, cons):
if operand == 0:
array[index] = ((array[index] + cons) ^ 32) + 19 + 64
if operand == 1:
array[index] = ((array[index] + cons) ^ 36) + 19 + 64
if operand == 2:
array[index] = ((array[index] + cons) ^ 19) + 19 + 64
if operand == 3:
array[index] = ((array[index] + cons) ^ 55) + 19 + 64


s = Solver()

op = 0
operands = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]
indexes = [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12, 17, 18, 19, 16]

for i in range(20):
if op % 4 == 0:
# print(global_arr[i] + flag[i])
four(flag, operands[i], indexes[i], global_arr[i] + flag[i])
if op % 4 == 1:
four(flag, operands[i], indexes[i], flag[i] - global_arr[i])
if op % 4 == 2:
four(flag, operands[i], indexes[i], global_arr[i] * flag[i])
if op % 4 == 3:
four(flag, operands[i], indexes[i], global_arr[i] ^ flag[i])
op = op + 1

for i in range(20):
s.add(flag[i] == enc[i])

s.check()
m = s.model()

for i in range(20):
s = BitVec(f'flag_{i}', 32)
print(chr(m[s].as_long()- 19 - 64), end='')

#WebAss3mblyMystique

TetCTF{WebAss3mblyMystique}


wasm 学习笔记
http://example.com/2024/02/07/wasm/
Author
yring
Posted on
February 7, 2024
Licensed under