题目

标题类型
watmanre

image-20210330200708967

解题

访问该地址

image-20210330200758480

F12查看源码

image-20210330200855915

可以看到页面代码较为简单,主要为一张图片一个whtml还有一个wasm文件,所以这题主要考验wasm逆向

首先我们从html代码中,可以看出,flagChecker是由checker.wasm导出的方法,点击按钮后,执行该方法

flagChecker接收三个参数,分别是由输入框内容经encode变换后的数组、128、257

WebAssembly(缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。Wasm被设计为可编程C / C ++ / Rust等高级语言的可移植目标,可在Web上部署客户端和服务器应用程序。

wasm是基于堆栈的虚拟机的二进制指令格式。与x86架构的汇编又很大的区别。

进一步的说明后续补充

我们可以从开发者工具中看到cheker.wasm的相关指令,下图为flagChecker的代码

 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
(func $flagChecker (;0;) (param $var0 i32) (param $var1 i32) (param $var2 i32) (result i32)
    (local $var3 i32) (local $var4 i32) (local $var5 i32) (local $var6 i32) (local $var7 i32)
    i32.const 1
    local.set $var3
    local.get $var0
    i32.load
    local.tee $var4
    local.get $var1
    i32.mul
    local.get $var2
    i32.rem_s
    i32.const 86
    i32.eq
    if (result i32)
      i32.const 0
      local.set $var3
      loop $label0
        local.get $var3
        local.tee $var5
        i32.const 1
        i32.add
        local.tee $var3
        i32.const 28
        i32.ne
        if
          local.get $var4
          i32.const 1
          i32.and
          local.set $var6
          local.get $var0
          local.get $var3
          i32.const 2
          i32.shl
          local.tee $var7
          i32.add
          i32.load
          local.tee $var4
          i32.const -1
          i32.const 1
          local.get $var6
          select
          local.get $var1
          i32.add
          local.tee $var1
          i32.mul
          local.get $var2
          i32.rem_s
          local.get $var7
          i32.const 1024
          i32.add
          i32.load
          i32.eq
          br_if $label0
        end
      end $label0
      local.get $var5
      i32.const 27
      i32.lt_u
    else
      local.get $var3
    end
  )

动态调试时可通过Chrome的调试功能,下断点进行调试。

相关指令集

可参考以下指令集

基本指令

这里简单介绍一下大量用到的基本指令:

local.getget_local

从函数的参数中读取内容,示例和下一条一起(

local.setset_local

基本的用途是向本地变量中写入值,如下例:

1
2
3
4
(func (arg i32) (local i32 i32)
  local.get 0 ;; 从 local#0 中取出数据并压入栈中
  local.set 1 ;; 从栈中弹出数据并存入 local#1 中
}

local.tee

它同样是用来向本地变量写入值的,但它写入之后原值仍然保留在栈中,相当于省了一步 local.get

i32.const

这个之前也提到过了,向站内压入一个数字,对应的还有其他三种类型的 .const,这里就不再重复了。

i32.add

这里的等指的是一系列算术运算,我们只是单独拿出 add 来举例。它弹出栈顶的两个元素并将其相加,最终将结果压入栈中。

其中包括i32.mul(乘)、i32.rem_s(取余)

i32.loadi32.store

顾名思义,从 Memory 中取出/向 Memory 存入数据的指令。

load 接受一个参数,就是地址;store 接收两个参数:先弹出需要存入的值,再弹出存入的地址。

这二者还经常带有特殊的属性:offset 表示 n 位的偏移,align 表示 2^n 位的偏移。

控制指令 – 分支

分支最常使用是 if,如下例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
(func (arg i32 i32) (result i32)

  local.get 0

  local.get 1

  i32.ne

  if (result i32)

​    local.get 0

  else

​    local.get 1

  end

)

这里需要注意的是,if 的判断过程都是在 if 之前进行的,if 本身只能从栈中弹出一个元素,并且判断其是否为 0,非 0 则成立。

这里需要介绍的一条指令是 if_br,它可以跳出当前的块。if_br 1 就相当于跳出当前的 if,回到 if 开头重新判断。

还有一种判断指令:selectselect 接收三个参数:abcondition。在判断时,condition 位于栈顶被首先弹出,当 condition 成立时,向栈中压入 a,否则就压入 b

控制指令 – 循环

循环使用的是 loop,和 if 一样,它也并没有实际的意义,跳转需要通过 br 进行。


基于以上指令和动态调试的结果

可以还原flag验证脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import string

verify=[86,155,95,131,222,190,69,101,233,177,81,207,127,213,113,53,170,94,25,218,217,158,25,249,72,218,67,219]



ss= string.printable
ff="U"
t=128
for i in range(1,len(verify)):
    print(len(ff),i)
    a=-1 if ord(ff[i-1])&1 else 1
    t=t+a
    for s in ss:
        if ord(s)*t%257==verify[i]:
            ff=ff+s
print(ff)
#output:UDCTF{wh0a_w4sm_i5_lit_y4l1}

image-20210330193426958