在我们上一篇关于WebAssembly(Wasm)的博客中,我们初步了解了一个未知的Wasm二进制文件,并对其进行了一些行为分析。今天我们将继续深入研究相同的Wasm样本。我们将通过研究Wasm文本格式手动分析该样本。
为了能够手动分析Wasm文本,我们首先需要学习更多理论。我们之前的博文描述了如何处理内存和数据。在此基础上,我们将介绍一些对逆向Wasm有用的概念,然后应用这些知识来分析Wasm样本。
注意:这篇文章是系列文章的一部分。该系列的上一篇文章介绍了Wasm内存处理,所以可能需要阅读这些文章。
正如我们在今年早些时候讨论的那样,Wasm本身不能与外界联系。与外部环境的所有通信都需要通过JavaScript API调用。考虑到这一点,我们现在主要讨论那些Wasm中对实际计算一些有用的指令 ,而不是调用JavaScript的指令。
与x86或x64的指令集相比,Wasm的指令集非常小,有几组不同的功能:
以下是常见Wasm指令的几个示例。有关更全面的说明列表,请参阅参考手册。
上面给出是WebAssembly文本格式的说明。由于Wasm是二进制格式,因此这些文本指令将在编译文件中用字节码表示。
让我们分析一个简单的函数:
第一行表示一个名为'max'的函数,以两个整数作为参数,$0和$1,并返回一个 i32 (32-bit integer)类型的整数。
'select'指令有三个参数:第一个操作数(get_local $0),第二个操作数(get_local $1)和一个条件参数(在这种情况下是i32.gt_s指令及其相关的操作数)。如果条件操作数不为零,则'Select'返回第一个操作数,否则返回第二个操作数。
在select中,指令'i32.gt_s'检查第一个参数(get_local $0)是否大于第二个参数(get_local $1)。此检查的结果将保存在“select”运算符的条件操作数中。
因此,如果第一个参数大于第二个参数,则返回第一个参数,否则返回第二个参数。
请注意,不同的工具表示的Wasm文本格式可能会略微不同(就像不同的反汇编器一样)。例如,上面的例子也可以这样表示:
无论我们使用何种文本表示,它都对应于以下高级语言:
有关Wasm文本格式的更多信息,请访问此处。
现在,我们理解了堆栈机,局部变量,全局内存,数据存储(如我们以前的帖子提到的),指令集和Wasm文本格式的概念,接下来用所学知识深入分析之前的博客文章中的Wasm示例。
从之前的位置继续:最初的浅层分析和行为分析表明我们正在处理的是排序算法。对于这个样本,分析可能会在此时就完成了,具体取决于您可以在一个样品上花费多少时间。
如果我们处理的是一个功能不那么明显的样本怎么办?您可能经常需要查看源代码,接下来进行这项操作。
为了处理代码,我们将再次使用我们之前的博文中的wasm2wat工具。我们已经发现sort函数的函数号为1。下面是Wasm文本表示的该函数的第一部分:
它从函数定义开始,表示函数使用两个整数并返回一个整数。然后定义了一个局部变量local i32。用高级伪代码表示:
Wasm代码:
前两个指令get_local0/1分别获取第一个和第二个函数参数的值,并将它们压入堆栈。然后第三条指令i32.ge_s将对堆栈上的这两个值进行操作,通过隐式弹出它们然后判断第一个值是否大于或等于第二个值。比较的结果将被压入堆栈。如果堆栈顶部的值为非零值,则后续if语句将为true。换句话说,if语句的分支将取决于前面的三条指令。
如前所述,相同的代码可以用不同的方式表示。如果上面的if语句难以理解,这里有一个替代表示:
到目前为止我们所逆向的内容可以用高级伪代码表示,如下所示:
继续使用wasm2wat查看Wasm文本格式,发现sort函数:
这段代码进行数学计算。get_local 0/1指令获取传递给函数的两个参数值,随后的'i32.add'指令对这两个值进行操作,把它们相加并将结果压入栈中。
之后'i32.const 4'指令将4压入堆栈。后续指令i32.div_s将堆栈顶部第二项的值除以最顶部的值。换句话说,先前param1+param2的值将被除以4。在此之后,我们再次执行相同的模式,但这次除以2。同样,接下来的两条指令是i32.mul指令,操作数是4。
最终结果是目前获得的值乘以4。可以更简洁地表示为,代码执行以下计算:(param1 + param2)/4/2 * 4。
让我们看看随后的Wasm代码,右侧添加了我们的注释:
再一次,用高级伪代码表达:
如果这很难掌握,请尝试在Chrome中运行示例,单步执行并在调试器中查看(即DevTools)堆栈和局部变量值的变化,以及全局内存的构造。一篇关于Wasm分析的早期博文介绍了做法。当我们位于'call 0'指令时,在执行之前,局部变量和堆栈将如下所示:
继续查看Wasm代码:
用高级伪代码表示:
总而言之,我们的最终结果是:
我们发现这确实是一个QuickSort实现。如果您愿意,可以通过将上述伪代码与您在Internet上找到的某些现有实现进行比较验证。逆向 partition函数留给读者练习。
我们现在成功地逆向了一个完整的Wasm函数。首先,我们使用wasm2wat程序将Wasm二进制格式转换为其文本格式,并通过分析文本格式,我们能够创建算法的高级伪代码。
存在用于执行自动反编译的工具,一种比我们今天所做的更有效的逆向工程方法。虽然自动反编译可以节省时间,但它通常不完美,对手动分析的理解使我们能够解决这些不完善之处。
翻译:看雪翻译小组 欢歌笑语
校对:看雪翻译小组 Nxe
原文链接:https://www.forcepoint.com/blog/security-labs/manual-reverse-engineering-webassembly-static-code-analysis
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!