如何在 x86 32 位上识别剥离二进制文件中的函数?

逆向工程 拆卸 部件 二元分析 静态分析
2021-06-26 21:18:31

我正在尝试基于从 x86 32 位平台上的二进制文件反汇编的一些汇编代码生成粗粒度的调用图。

基于asm代码生成一个精确的Call Graph是非常困难的,考虑到各种间接控制流转移,所以我现在只考虑直接控制流转移

因此,首先我试图functionsx86、32 位上剥离二进制文件中识别(开始和结束地址)反汇编代码

现在,我的计划是这样的:

至于开始地址,我可能会保守地认为任何汇编代码看起来像这样

    push %ebp

表示函数的起始地址。

而且,我可能会扫描整个问题,call用目标识别所有指令,将这些函数调用的目标视为所有函数开始地址

问题是:

  1. 一些在二进制文件中定义的函数可能永远不会被调用。

  2. 一些call已经被jump编译器优化(考虑尾递归调用)

至于结束地址,事情变得更加棘手,因为ret一个函数中可能存在多个......

所以我想我可能会保守地考虑任何最近的函数开始地址之间的范围,作为一个函数..

我对吗?有没有更好的解决方案..?

2个回答

反转二进制的调用图或控制流图并不适合胆小的人,并且仍然是研究人员的热门话题。

你的方法看起来很有希望;但是,不幸的是,您会遇到很多障碍。

一,call如果静态分析二进制文件,遵循说明最有可能得到很好的结果。唯一的问题是,有时,您会进行间接调用/跳转。意思是,操作数将是一个包含目标地址的寄存器。例如,如果目标二进制文件原始源代码是用C++(虚拟函数)编写的,这种情况将经常发生在这种情况下获取目标地址的一种方法是模拟或运行计算它的代码块。另一个是启发式地评估它的价值(启发式是地狱)。

第二,您可以使用多个输入数据集运行二进制文件并动态提取调用图(这可以通过检测来执行)。然后,您可以交叉引用所有获得的调用图...

第三,我会推荐一种以基本块为中心的方法,而不是一种功能方法。主要是因为一个函数本身就是一个基本块,与尝试匹配可以从一个编译器更改为另一个编译器或从一个编译器版本更改为另一个版本的模式相比,通过这种方式找到函数会更幸运。

以下出版物非常有趣:[1] [2] [3] 也是我会鼓励你检查DynInstcallgrind如果您想了解更多的问题。

一般来说,这个问题的解决方案可以分为:

  • 模式匹配启发式。就像你提议的那样。例如,在push二进制中搜索es 可以提供函数开始的(相当)粗略的近似值。但是,如果您想定位函数结束,事情会更加困难。

  • 机器学习。模式匹配可以使用机器学习实现自动化。文献中有几个提议,如 [ 1 ]、[ 2 ] 和 [ 3 ]。所有这些都试图学习函数开始和结束的字节级特征。然而,现代编译器优化使得此类方法难以泛化到训练集以外的二进制文件。

  • 基于CFG的技术。这是基于 [ 4 ](披露:此处为第一作者)和同时 [ 5 ]的最有前途的方法基本上,它进行 (1) 直接调用目标分析,(2) CFG 遍历以定位函数结束,以及 (3) 尾调用目标分析。

  • 调用帧信息 (CFI) 记录。在做任何花哨的事情之前,请.eh_frame查看 部分中的 CFI 记录很有可能已经在那里定义了函数。为了转储 CFI 记录,您可以使用类似readelf --debug-dump=frames /bin/ls.

我最近重温的功能识别的问题,在这个博客,我提供更多的细节。