在逆向工程领域,IDA Pro 是一款广受赞誉的反汇编和调试工具,它支持多种主流的指令集,为开发者和安全研究人员提供了强大的分析能力。然而,一些特殊的指令集,如 VMP(Virtual Machine Protection)指令集,可能并不在 IDA 的支持列表中。目前,随着攻防对抗技术的发展,许多自定义的 VM 指令集被开发并应用在真实项目,迫切需要一种方法,扩展 IDA 的反编译器。
本文目的是介绍一种让 IDA 反编译支持新指令集的简单方法,能够一定程度上缓解新指令集无反编译器的困境。该方法是并非笔者原创,而是笔者对许多前辈的文章和材料做出的一个总结,起到一个抛砖引玉的作用,旨在帮助想在 IDA 中实现未知指令集反编译的朋友。
⚠️ 本文方法限定的指令集范围是一种 IDA 未知的指令集,读者应当具备该指令集的完整知识,至于如何获取该指令集的完整知识(对于 VM 来说就是 VM 指令集的逆向)的过程不在本文的讨论范围!
IDA Pro 虽然支持用户开发特定架构的处理器模块,却不能使用其反编译功能,并且编写特定架构的处理器模块过程复杂,参考资料少。
IDA Pro 8.4 仍然没有加入对 WASM 的反编译支持,然而有资料表明,IDA Pro 能够反编译 WASM 模块 [2][3]。目前让 IDA Pro 支持 WASM 反编译的方法是使用 wasm2c [4] 程序,将 WASM 模块转换成等价的 C 语言低级表示形式,再使用 GCC/CLANG 编译该代码,最后使用 IDA 分析最终产物。
为什么不直接阅读 wasm2c 的结果呢?wasm2c 确实能够将 wasm 模块转换成等价的 C 语言形式,然而这种形式是其 wasm 汇编的等价表示形式,可读性极差。因此,需要使用 C 语言编译器将其优化再编译成一种 IDA 能够识别的指令集,最后使用 IDA 逆向分析最终产物。
如下图就是用该方法反编译的一个效果图。
上图是一个 wasm 模块中 strlen 函数的反编译结果,strlen 所需的循环结构清晰可见,这种方法确实实现了让 IDA 反编译一种未知指令集的效果。然而,美中不足的是,wasm2c 并没有将内存访问 load 和 store 以一种原生 C 语言形式表示,这导致内存访问在 IDA 反编译结果中以函数形式呈现,这可能是由于 wasm2c 需要保证转换结果的正确性,然而,对于反编译来说,可以采用更加激进的内存访问行为,这将有利于提升 wasm IDA 反编译效果。
笔者受到已有方法启发后,认为为了给新指令集快速实现一个 IDA 中的反编译器,可以先将未知指令集转换成一种 IDA 能够识别的指令集,例如 x86 或 ARM。然而直接转换成 x86 或 ARM 仍然有太多细节处理,因此,可以采取先将指令集,以函数单位,转换成等价的 C 语言表示形式的方法来完成未知指令集向已知指令集的转换。
具体举例来说,如下图所示的例子,转换器输入未知指令集的汇编指令,将其转换成等价 C 语言低级层形式。
对于寄存器,可以映射成 C 语言中的局部变量或全局变量,对于内存模型可以映射成 C 语言中的 memory 数组访问或指针访问,对于赋值指令、运算指令可以简单转换成 C 表达式,对于内存访问指令,可以转换成 C 语言的数组访问或指针访问的形式,对于标志寄存器,可以定义一组相关的宏来根据结果修改标志寄存器(C 编译器的优化功能会将未使用的标志寄存器相关的代码作为 dead code 删除!)。
具体如何映射,还需要根据特定的指令集来设计,在编写转换器的时候,尽量保证转换的正确性,剩下的反编译效果,则交给 C 编译优化和 IDA 内部的反编译优化器来解决
笔者选择了 4 个比较有代表性的 VM 指令集例子,另外 wasm 的 IDA 逆向方法本身也是一个很好的例子,因为 wasm 的 IDA 反编译方法代表了 real world 大型复杂指令集。而笔者选择的指令集更加偏向于比赛场景,与 real world 中的指令集有一定程度差距,但是适合快速上手实践,同时笔者也希望读者能够在更多 real world 指令集上尝试这种方法。
这个例子的指令集来自 QWB S5 vmnote ,笔者将该 VM 指令集直接转换成 x64 汇编,使用 IDA 反编译转换结果,最后成功发现程序中的漏洞。
本文在前人工作的基础上,总结了一种快速实现在 IDA Pro 中反编译未知指令集的方法,该方法适用于 real world 的 wasm 指令集逆向。同时也在一些小众指令集上取得成功,实现了“小语种”的IDA 反编译器。该方法能够在最短时间内编写一个未知指令集的反编译,能够缓解未知指令没有反编译器的窘境。尽管如此,该方法仍然有许多不足待改进,例如笔者还没有在 VMP 商业保护以及流行于 Android 应用程序加固领域的 Java 虚拟化保护上测试该方法,笔者认为,该方法仅适合用于对未知指令集的初步探索逆向,若要开发一个成熟的反编译器,仍然需要根据实际情况,编写 Ghidra 反编译插件或者从零开发反编译(例如脚本类字节码 Python、Lua 等)。