跳转至

动态调试

今天我们来尝试一下wasm的调试,我们以2019 ciscn初赛的一道题为例

题目下载地址

环境配置

目前主流的浏览器都已经支持wasm了,今天使用的是Chrome。

直接用Chrome打开where_u_are.html似乎只是打开了这个静态页面,并没有调用wasm。

而用Firefox打开则能看到输入窗口:

firefox_open.jpg

我们可以用python的SimpleHTTPServer本地起一个服务器:

1
$ python -m SimpleHTTPServer 8001

另外,将where_u_are.html复制一份,改名为index.html,因为index.html是一个web服务默认的主页。

在浏览器中访问localhost:8001,Chrome可以正常访问了。

这种方式起的服务与直接访问某个网址没什么区别。

BTW,如果在内网内想要互传文件,可以用这种方法在某个目录下开端口,当然不能有index.html。另一个用户访问本机ip加端口号,就能访问当前目录下的文件了,从而实现快速传文件。

image1

然而Firefox又不正常了= =

此外,如果我们得到的不是网页和wasm的源码,而是一个链接的话,则需要我们手动将wasm下载下来。

访问localhost:8001(假设我们没有源码,那题目给的一般是个网址,这里的localhost:8001就相当于题目给的网址),按f12 (接下来以chrome为主视角,其他浏览器可能略有不同)

在network中可以看到不同的资源文件,我们双击wasm文件,就可以下载了。

download_wasm

在sources中可以看到加载的资源。然而第一次打开时并不能显示wasm,只有index和js文件。可能这里只是显示按下f12后所加载的资源?

刷新一下页面,重新加载一下资源,可以看到wasm了,这里将是我们主要的调试区域。

调试

先让我们看一下调试界面:

debug_interface

左边是函数列表,中间是wasm汇编窗口

右边从上往下依次可以看到:监视,调用栈,变量区(全局变量和局部变量),断点窗口。剩下的有什么用我并不清楚,还需要查找些资料。

和一般调试器一样,右上角能看到基本的操作,继续步过步入运行到返回等,也有相应的快捷键。

好的,让我们开始调试。

例子

本文以2019 ciscn初赛的where_u_are为例,静态分析过程在前文已经进行过了。我们在此基础上开始动态调试。

首先根据静态分析得出的结论,f25是main函数,f99是输入,f98是输出。现在main设个断点,在f99后设个断点。

按F5刷新网页,相当于从头运行一遍。这时断在了main函数头,我们按F8继续(或是手动点击继续按钮,我知道不同调试器快捷键不一样,这很让人恼火)

弹出了输入框,似乎是等待输入结束才会继续。

我们随便输入点什么进去:

点击确定后并没有像我们想象的一样断在f99后面。在一番测xia试gao后发现,点了取消才会继续。可能是小bug?

让我们看看能不能想办法找到刚刚输入的数据:

scope

这个global有点吓人啊,似乎是吧整个内存(?是这么叫么,就当是类似内存的东西吧)都放上来了。

local里显然没有我们刚刚的输入

stack里只剩下了个1,显然也不是。

思考一下,调用f99的时候应该传了个参数进去,因此我们现在f99下个断点,重新运行一下。

果然,stack内有两个数据,4464和8608:

image6

肯定不是local的编号,先去global里看看是啥

image7

image8

似乎看不出来什么,继续输入完看看

然而这两个位置的值并没有什么改变,不过看看local内其他的地址有了发现

image9

这里能看到之前的输入'123123123'

接下来的函数f23很关键。根据之前的分析,f23把输入根据table映射到0-31的某个数,然后转二进制。

步入f23,能看到两个loop循环。找到中间的call 22,下断点继续。

可以看到,此时栈内元素是我们输入的第一个字符'0'(49)

image10

不出意外的话,直接步过这个函数,应该会返回table中'1'的下标

image11

也确实能找到类似 (a>>j)&1的语句:

image12

最终每一位会在107被保存,保存到4576开始的地址:

image13

f23结束后在4576开始的global内可以看到转为二进制的数组,这里每一位使用int按小端序储存的,我暂时没找到能不能把数据类型改成dword,所以看起来会没那么方便。

image14

可以看出一次是 00001 和 00010,对应table中的1和2。我们可以之后尝试更改输入,这里将变成对应的二进制串。

接下来的f24函数则是对二进制数组的计算操作了,可以跟进查看每一步是如何计算的,这里就不演示了,步过f24后可以在栈内找到结果的指针,指向global的4784。此时4784开始的八个字节是0, 0, 0, 152, 148, 23, 86, 192,再往后八个字节是0, 0, 0, 19, 45, 149, 94, 192。把他们强制类型转换转成浮点数,能得到两个浮点数:-88.368444和-122.330876,这将是最终的输出。

如此一来,可以确定整道题的算法。

我在做这题的时候,光靠静态并没有搞懂算法。动态调时候,通过local和global一些变量的变换,慢慢的搞懂算法的。

所以动态调试能帮我们更快搞懂一个程序在做什么,是逆向中不可或缺的技能。

小结

初步分析了一下wasm的调试过程,对wasm汇编语句有了初步认识。调试过程中大概可以分析出wasm的特点:

  • wasm是基于栈结构的汇编语言,在一个函数作用域内首先定义了大量局部变量。
  • 操作这些变量时并不是直接运算或赋值,而是先入栈后,对栈内元素操作,再出栈。
  • 调用函数时一般以指向global变量的指针为参数,返回时也返回指针。
  • 用i32.store和i32.load等指令完成对global变量的赋值或引用。

如果分析过python的opcode会发现一定的相似性。

评论