JS逆向实战:调试 wasm 的小技巧
大家好,今天分享一个可以动态调试 wasm 的小技巧,方便在逆向 wasm 算法的时候快速查看内存。
wasm
wasm 大家应该比较熟悉了,如果不了解的小伙伴可以先看这篇和这篇文章。
在调试 wasm 时,由于 wasm 不支持字符串传参,wasm 传参时都是使用指针地址+字符串长度的方式来传入。这里有点类似于 C 语言,将字符串提前写入到指定的地址,然后传入指针地址,再加上长度,即可在 wasm 中通过地址和长度将字符串读取出来。
wasm 的内存布局是线性的,可以理解为 wasm 的内存就是一个超长的一维数组,通过索引来读取数据,索引就是刚才提到的指针地址。
那么在实际调试 wasm 时,如果 wasm 遇到不是很复杂或者本地加载报错的情况,可以尝试逆向一下 wasm 的算法,在这里顺便分享一个可以快速查看内存的小技巧。
获取内存内容
这里使用猿人学 match20 题为例,它的参数很简单,一个 page
页数,一个时间戳 t
和一个签名 sign
。
实际调用也比较简单,传入一个字符串,返回一个字符串,返回的字符串就是签名。但是这个函数在实际调用 wasm 函数的之前,做了一些操作,一起来看一下。
这里是添加到栈指针。
这个函数比较长,但是只做了一件事,那就是将传入的字符串计算出长度,然后将其编码为 ASCII,写入到 mem
变量中,然后返回字符串所在位置的指针,mem
变量就是 wasm 的内存。
这里对内存进行了初始化,如果不存在的话则新建一个。
到这里就准备进入到 wasm 调用 sign
函数了,可以看到右侧的 ptr0
这个地址就是刚才写入字符串的地址,但是写入了什么内容呢?如何才能看到写入了什么内容?这里有个小技巧,让程序先往下继续执行。
到这里可以看到,通过 getStringFromWasm0
这个函数可以读取到程序的返回值,输入参数是地址和长度。r0
就是计算结果的地址,r1
则是计算结果的长度。参数的长度是 15
,返回值的长度是 32
,看到 32
这个数字,有经验的小伙伴可能会第一反应,MD5!的确是 MD5,但是不是单纯的 MD5。
回到刚才的问题,那么既然这个函数可以根据地址和长度读取返回值的数据,那么是不是可以使用它来读取参数呢?答案是可以的。
尝试一下,发现是可以的,从这里其实可以推断,getStringFromWasm0
这个函数应该可以读取任意的内存地址,换一个地址试一下。
这里有很多的地址,随便选择一个读取试试,长度填短一点,发现报错了,为什么呢?其实是因为当前的上下文在 wasm 中,刚才调用的函数是在 JS 中定义的,所以在这里调用肯定会报错的。让 wasm 执行完成,回到 JS 上下文中再试试。
尝试了几个地址,有的报错了,因为不是正常的 ASCII码,无法解码所以报错,但是还是有可以读取的地址的,所以说基本上可以验证,这个函数可以读取任意的地址。
查看任意地址
但是到了这里问题又来了,只能在调用完 wasm 或者没有调用 wasm 的时候才能查看内存地址的数据,只能在 JS 环境中才能查看,那动态调试 wasm 的时候如何查看呢?因为有的地址可能在运行以后数据会被删除或者覆盖,等到执行完 sign
函数可能就读取不到数据了。
别着急,还有办法,Chrome 为我们提供了更加好用的工具。
还记得刚才分配内存的函数吗,在断点的状态下,找到它,把鼠标放到变量上面,会自动显示出来这个变量的内容,在这里可以查看这个超大的数组的所有内容,但是太长了,挨个点是不可能的。看到一个类似于内存条的小图标了吗,用鼠标点击它,会自动打开一个叫内存检查器的东西。
就像上图的样子,这个对于长期看汇编代码或者使用 frida 的小伙伴应该不会陌生了,它的左侧是内存地址,右侧是对应的 ASCII 的值,最右侧可以切换大端序和小端序。
还记得刚才返回值的地址吗,把它填到上面的地址框中敲回车试试。
是不是直接跳到了对应的内存位置,并且正好就是计算的结果,在右侧可以直接看到。和 getStringFromWasm0
函数的计算结果核对一下,完全正确。
好,现在我们学会了如何查看 wasm 任意位置的内存,而且不用借助 JS 函数,那现在重新来调试 wasm 试试。
直接在下面输入对应的内存地址,Chrome 会自动转成十六进制,并且显示出对应地址的内存值。
接下来就是无聊的调试环节了,如何知道 wasm 中都做了什么?我也不知道,那就只能猜。
经过漫长的调试,发现一个长度为 31
的字符串,而且是在传入的参数中拼接了一些字符,猜测可能是加盐的 MD5?尝试一下:
对比一下结果,发现完全正确,现在 wasm 的算法就有了,是一个加盐的 MD5,搞定收工。
总结
这次的题目不算难,算法也比较简单,但是 wasm 和 JS 之间有相互调用,会导致在本地模拟有些困难,正好可以逆向看看算法。我们的运气不错,算法不是很复杂,并且还保留了函数名,动态调试起来来着简单,好多函数都可以看懂。并且还学会了使用 Chrome 的内存检查器功能来查看wasm 的超大内存,方便在动态调试 wasm 的时候查看内存地址的内容。
本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
更多文章请关注公众号 (LLLibra146):
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!