ARM反汇编的健全性

逆向工程 拆卸 手臂
2021-06-11 00:38:17

我对二进制代码级别的软件形式验证感兴趣。显然,第一步是从二进制文件中恢复实际的汇编指令。

IDAPro 在 x86 的反汇编方面可以做得很好,但是,仍然有可能将某些数据解释为代码。因此,基于其中的分析仍然是不可靠的。

鉴于:

  • 我的应用程序域中的软件没有经过混淆和自修改,
  • 和 ARM 指令比 x86(2 或 4 字节长度)的可变性小。

ARM 二进制文件的反汇编是否合理?换句话说,反汇编器可以准确地恢复实际代码吗?

3个回答

经过一番研究,我认为我可以从理论的角度添加一些解释。请注意,我的讨论仅限于剥离(无调试信息)可执行文件。我们知道几种数据可以与可执行文件中的代码混合,包括填充字节和开关跳转地址计算。

为了将日期与代码分开,我们需要精确地提取属于代码的字节,由此我们可以假设其余的字节属于数据。从众所周知的入口点开始精确提取代码的主要挑战是间接跳转,即控制流重定向到在运行时计算的内存地址。DarthZirka 提到了几个例子,包括 C++ 中的开关表、函数指针和 vtables。

一个可以静态精确解决间接跳转的分析(不运行程序)也可以解决已知不可判定的停机问题。这个结果自 70 年代后期反汇编工作开始在学术界取得进展以来就已经确立,我可以参考以下早期论文以获取更多详细信息。

RN Horspool 和 N. Marovac。一种解决计算机程序反翻译问题的方法计算机杂志,23(3):223-229,1979。

基于此,精确确定间接跳转地址的问题一般也是不可判定的。因此,任何反汇编方法都应基于启发式方法,例如识别常见的编译器习语和/或过度(不足)近似从间接跳转可到达的地址集。后者通常基于抽象解释的框架通过静态分析来完成。与 ARM 相比,X86 中的可变大小指令会使事情变得更难(更松散的过度近似),但本质问题仍然存在。

覆盖率/完整性问题与反汇编引擎的“准确性”或指令长度的差异几乎没有关系。如果代码不是从以目标二进制文件的入口点为根的调用树静态引用的,问题就会出现。

其中包括:

  • 其他外部入口点(DLL 导出、TLS 回调等)
  • 切换表
  • 虚表
  • 非静态函数指针/表(例如 C 风格的对象)
  • 与异常处理等相关的 funclets/thunk。
  • CRT 初始化/退出表

准确性在切换表方面发挥作用,在 IDA 中,切换表由各个处理器模块负责。其他事情是加载器、内核、特定于编译器的插件(RTTI、异常函数)以及最终的人工操作员的责任。

x64 COFF 中的 SEH 信息涵盖了所有非叶函数,与 32 位 COFF 相比,这大大提高了覆盖范围。如果您可以控制构建过程,那么您可以通过调试信息(PDB、TDS 等)实现几乎完美的覆盖。让链接器生成详细的 MAP 文件也很有帮助。

如果二进制文件的重定位信息被剥离,则会出现额外的复杂情况。在这种情况下,反汇编程序可能无法确定给定的立即数是偏移量还是数字。同样的问题发生在基于指针的指针上,包括 IDA 期望 RVA 发生的地方之外的 RVA。

您将面临与任何反汇编程序相同的问题:您不知道事物的含义。

我上周在现实生活中的例子:当我看到0x00408800传递给函数的值时,我确定这必须是一个位掩码。经过大量跟踪,看到值存储在某个地方,复制到其他地方,在类构造函数中使用,并在子类的方法中访问,我发现这实际上是一个内存位置,它恰好是一个更大的数组,所以它本身甚至没有一个符号。

而且,如果它是间接调用而不是内存访问,那么该地址处的字节的含义就会从数据变为代码。

除非您的代码验证器/反汇编器生成程序和 RAM 的每个可能状态的列表,并且作为结果,知道每个文字值可能使用的位置,并且知道每个使用的值可能从哪里计算,否则您将无法自动找到这样的歧义。但是,这只是歧义的一个例子,我认为能够区分内存位置和位图对于任何类型的代码验证都非常重要。

因此,除非您做出更多假设(代码由特定编译器生成,该编译器发出有限数量的仅以特定方式组合的基本块),否则我认为这是不可能的。