c++ 输出二进制_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法

a4b35fb6a48825f4c790fee51e8f63d3.gif

PPC和MIPS指令集下二进制代码中函数参数个数的识别方法

尹小康, 刘鎏, 刘龙, 刘胜利

数学工程与先进计算国家重点实验室,河南 郑州 450001

摘要:函数参数个数的识别有助于函数原型的恢复,是进行数据流分析以及其他安全分析的基础。为了提高对函数参数个数识别的准确率,提出一种依据函数调用关系的投票机制来确定函数参数个数的算法——Findargs。Findargs从PPC和MIPS指令集的函数调用特点出发,利用函数调用关系和参数传递分析,识别函数参数的个数,为函数原型的恢复提供帮助。为了评估Findargs的识别效果,选取大型的二进制文件进行了测试,并与radare2进行了对比。实验结果表明,Findargs具有更高的准确率。对于PPC指令集,其准确率达到90.3%;对于MIPS指令集,其准确率为86%。

关键词:静态分析 ; 函数调用分析 ; 参数个数识别 ; 投票机制

中图分类号:TP309

文献标识码:A

doi:10.11959/j.issn.2096−109x.2020047

1 引言

二进制分析在安全研究中具有重大意义,在安全分析中的应用主要有:二进制代码审计、控制流完整性分析、污点分析、符号执行、漏洞修复、代码重用、漏洞挖掘等。在高级语言中,函数名、函数的参数个数、参数类型、函数的返回值等信息能够有效帮助理解函数的功能。源代码经过编译器编译丢失了如高级语言C/C++中的数据类型、数据结构、语义以及控制结构等信息,这给研究者分析二进制代码造成了诸多障碍。因此,如何从二进制代码中恢复出丢失的信息成为二进制分析的关键。对二进制程序进行逆向分析的第一步是对函数的识别,确定函数的起始位置、结束位置、函数的调用方式,以及函数的参数个数。二进制代码逆向分析的准确率影响后续安全分析的正确性。 函数参数个数的准确识别是进行函数原型识别的基础,是进行数据流分析以及其他安全分析的前提。由于精简过的二进制程序中的符号表等信息被移除,无法直接获得函数入口等相关信息,增加了对二进制可执行文件进行分析的难度。Caballero等提出了X86指令集下基于动态分析的二进制函数接口识别方法。在无源代码和符号表的情况下,该方法对二进制代码进行动态分析,获取函数的原型,以达到重用二进制函数的目的。由于动态分析无法获取全面的信息,因此识别效果较差。radare2是最新的二进制分析工具,它拥有强大的分析功能,支持对不同架构语言的反汇编,在“aaa”命令中实现了对二进制函数分析的功能,在对PPC和MIPS指令集的二进制函数进行分析时,发现函数参数个数的识别效果比较差。 为提高函数参数个数的识别准确率,本文提出并实现了一种基于函数调用关系的函数参数个数识别算法——Findargs,用于识别PPC和MIPS指令集下二进制可执行代码中函数的参数个数。该方法结合PPC和MIPS指令集下函数参数的传递规则,通过分析函数调用时寄存器以及栈的使用情况,获取二进制函数的参数个数。为了提高识别的准确率,Findargs 分析获得所有的函数调用关系,然后对每个函数的调用者进行了分析,并通过投票机制,选取最有可能的参数个数。Findargs适用于对大型固件的分析,并且函数被调用的次数越多,准确率越高。本文的主要工作如下。 1) 分析了函数参数识别的问题,并提出了二进制可执行代码中二进制函数参数个数识别的概念。 2) 提出并实现了基于函数调用关系的函数参数个数识别算法——Findargs。Findargs 使用静态分析的方法,无须动态执行二进制文件,适用性较好。 3) 选取大型的嵌入式系统固件,对该算法进行了评估,并与现有的二进制分析工具 radare2进行了对比。实验结果表明,Findargs 具有更高的准确率。

2 问题描述

2.1 问题定义

二进制函数是在二进制可执行文件中,通过函数识别技术识别出具有函数入口和出口的代码片段。与高级语言中的函数类似,二进制函数同样具有函数名、函数参数、函数返回值等。 参数传递指令序列是在函数调用指令前用于传递函数参数等信息的一段代码片段。 函数参数个数识别的目的是识别二进制函数中函数传递参数的个数,用于分析在二进制函数调用时参数的传递情况,进而分析程序的数据流的传递情况。 对二进制文件中的函数参数个数进行识别的基础是函数边界的识别。函数边界识别是给定包含n个函数的二进制文件输出完整的函数起始地址集合 5843dd1957b8e4e67e7c87f15de4a3ea.png 。 函数参数个数的识别问题是对于给定的二进制函数指令序列和函数的起始地址,输出函数的参数个数。对于精简二进制程序而言,输出所有二进制函数的参数个数对 ab8baddf1da7d39769f2bd504b53098b.png

2.2 二进制函数调用特点

函数参数的传递一般有3种方式:栈传递、寄存器传递与全局变量进行隐式参数传递。通过栈传递参数,需要对参数的入栈顺序进行约定,并对栈的平衡方式进行约定,最终确定由调用者还是被调用者来恢复栈平衡。对于使用寄存器传递参数来说,同样需要确定哪些寄存器用于传递参数,以及参数传递的顺序,并且需要确定当参数个数大于可用于传递参数的寄存器数时,如何进行参数传递。对于全局变量进行隐式参数传递来说,通过设置全局变量来共享变量,在一个函数内对该变量的修改会影响其他函数中该变量的值。对于使用全局变量进行隐式参数传递,需要进行数据流分析,才能从二进制可执行代码中识别出全局变量。 函数参数调用中常用的调用约定共有4种:_cdecl(C 规范)、pascal、stdcall 与 fastcall。表1详细展示了各个调用约定的参数传递方式和传递顺序以及平衡栈者。由表1可知,_cdecl、pascal、stdcall均使用栈进行参数的传递,fastcall利用处理器中寄存器的优势使用寄存器和栈来传递参数。 下面对常用的调用约定的参数传递方式进行详细介绍。 (1)使用栈传递参数 栈作为一种先进后出的数据结构被广泛地应用。在调用函数时,通过将参数压入栈中将参数传递给子程序。函数调用结束后,对栈进行平衡操作,将栈恢复到函数调用前的状态。当函数具有多个参数时,需要对参数的传递顺序进行约定,以及确定由函数调用者或者子程序来恢复栈的平衡。当函数具有多个参数时,_cdecl和stdcall约定参数的传递顺序采用由右至左的顺序,pascal约定参数传递顺序采用由左至右的顺序。对于栈的平衡方式,_cdecl 是调用者对栈进行平衡,而pascal、stdcall 和 fastcall 使用子程序对栈进行平衡。图1为利用栈进行函数参数的传递,该参数传递方式是从右至左,首先将第二个参数压入栈中,然后将第一个参数压入栈中,在使用参数时通过ebp或者sp进行寻址。

b470f8e560c5a96814d1689b4738550e.png

98a5de628a2d5f02333027c85a977138.png 图1   利用栈进行函数参数传递 (2)使用寄存器传递参数 使用寄存器进行参数传递具有快速高效的特点,尤其当函数的参数个数较少时,直接将参数放入寄存器中进行读取,减少了一定的内存操作。处理器中拥有多个寄存器,可以选择部分寄存器用于函数参数的传递。由表1可知,fastcall采用寄存器和栈进行函数参数的传递。当函数参数较少时,直接使用寄存器传递参数,当函数参数超过可用参数寄存器时,则将多余的参数通过栈来传递。使用栈传递参数时,约定了参数的传递顺序和栈平衡操作者。对于fastcall,参数的传递顺序因编译器的不同而不同,但都是通过子程序来平衡栈。

2.3 PPC和MIPS指令集下的函数参数传递

表2列举了PPC和MIPS指令集中约定可用于传递参数的寄存器。在PPC指令集中,寄存器r3作为函数的第一个参数,剩余的参数分别依次放入r4 ~ r10,由此可知,PPC指令集中可用寄存器最多传递8个参数。在MIPS指令集下,分别使用编号为4 ~ 7的寄存器来传递参数,名称分别为a0a3,因此,MIPS指令集下可通过寄存器来传递参数的个数最大为 4,多余的参数需要通过栈进行传递。

9ea8dcf46a2725665b167d1ce082d2aa.png

2.4 运行实例

‍‍‍‍‍‍‍‍‍‍

下面以sub_801FDFA4和sub_600746B8函数参数的传递方式为例,具体分析‍‍PPC和MIPS指令集下函数参数的传递过程。在PPC指令下用于传递参数的寄存器为 r3~r10,在调用函数sub_801FDFA4时使用了r3~r8寄存器,因此函数的参数个数为6。对于函数sub_600746B8,则为MIPS 指令下的函数,用于传递参数的寄存器为a0a3,在调用函数sub_600746B8时,在这段指令中使用了a1,a2,a3寄存器以及进行了两压栈 88f4a60740cae55e0ab26ed9eacdd363.png708538f4d1c23fd365106de103088e30.png 。参数寄存器的使用具有以下事实:参数寄存器是按照顺序使用的,当后一个寄存器用于传递参数时,前一个寄存器必定用于传递参数。可知a0 也用于传递参数,共有 6 个参数。PPC 指令、MIPS 指令下函数调用参数的传递分别如图2、图3所示。 70674f1a3f6002d91d5b004541ae9215.png 图2   PPC指令下函数调用参数的传递 46986eceb1bc833b380132e4e9f53f9c.png 图3   MIPS指令下函数参数调用的传递

3 二进制函数参数个数识别

本节对二进制函数参数的识别算法进行详细介绍。二进制可执行文件的函数参数个数识别算法——Findargs的流程如图4所示。 0081cac26e6df93300e850a552f05f00.png 图4   函数参数识别算法——Findargs

3.1 函数边界的识别

对函数边界的识别是二进制分析的基础,需采用的函数边界识别方法是 FRwithMBA,通过分析二进制可执行代码,提取出函数的边界,包括起始地址和结束地址,用于函数调用关系的分析。

3.2 二进制函数调用关系的建立与转换

函数调用关系的分析在二进制逆向分析中具有重要作用,如用于恶意代码分析与分类、计算代码相似性等,本文将函数的调用关系用于函数参数个数的识别。函数调用关系的分析是函数控制流图生成以及研究数据流的基础,函数调用关系图(FCG,function call graph)对理解大型程序有很大帮助,FCG反映了函数的结构、可扩展性等信息。 按照分析目标的不同,函数调用关系的分析可以分为源码层面和二进制层面;按照分析方式的不同,函数调用关系分析可以分为静态分析和动态分析。静态分析依赖于对目标语言的理解水平,对目标语言中的函数调用指令分析的全面性,能够遍历分析所有分支,但是无法获取间接函数调用的关系。动态分析是借助函数运行时的信息进行分析,在程序运行时,能够获得相对于静态分析更多的信息,但是对于程序未执行到的分支则无法进行分析。本文主要研究的是使用静态分析方式分析二进制可执行代码中的函数调用关系,获取比较全面的函数直接调用关系,用于后续的函数参数的识别。 与函数的边界识别一样,对二进制代码中函数关系的分析可以采用线性扫描的方式或者递归下降回溯扫描的方式。对后续的参数识别只需要取函数调用的第一层关系即可。因此,函数调用关系的分析采用静态分析的方式,结合函数的边界,采用线性扫描的技术对整个代码段进行分析。函数的边界为函数的起始和结束地址,在每次解析函数调用关系时,将整个函数的可执行代码取出进行解析,通过解析生成格式为{函数地址:子程序1,子程序2,子程序3,…,子程序m}的形式。在对所有的函数分析完成后,将格式转换为{子程序:调用者1,调用者2,调用者3,…,调用者n}的形式用于后续的函数参数的识别。

3.3 基于调用关系的函数参数个数识别算法

函数调用前的一段指令是对参数进行传递的指令,为了分析函数参数传递的情况,只需对函数调用前的一段长度的指令进行分析,这段代码为参数传递指令序列。函数调用前,父函数会将调用子程序时需要的参数根据函数调用约定放入寄存器中或者压入栈中。对二进制函数指令序列进行扫描分析,识别出其中的函数调用指令。选取函数调用前的一段指令序列作为函数参数传递指令序列,对参数传递指令序列进行分析,主要包括两个方面:对参数寄存器的使用情况和栈操作的情况进行分析。对参数寄存器的使用情况分析主要是分析参数传递指令中使用了哪些参数寄存器,对于PPC指令主要分析r3 ~ r10寄存器的使用情况,对于MIPS指令则主要分析a0a0 " role="presentation" style=" box-sizing: border-box; list-style: none; text-indent: 0px; text-align: left; letter-spacing: normal; word-spacing: normal; overflow-wrap: normal; float: none; direction: ltr; max-width: none; max-height: none; min-width: 0px; min-height: 0px; border-width: 0px; border-style: initial; border-color: initial; left: 0px; clip: rect(1px, 1px, 1px, 1px); user-select: none; display: inline; transition: none 0s ease 0s; vertical-align: 0px; line-height: normal; height: 1px !important; width: 1px !important; overflow: hidden !important; "> a3寄存器的使用情况。对于栈操作的指令分析,主要分析在参数传递指令中是否进行了压栈操作来传递参数。 对于函数参数的传递,有以下情况:对于未使用栈进行参数传递的函数,函数参数个数不会超过参数寄存器的个数,因为当参数寄存器足够时,必定会使用参数寄存器来传递参数;并且当后一个参数寄存器用于传递参数时,前一个参数寄存器必定也用来传递参数,如在PPC指令下进行参数传递时,如果r5寄存器被用于传递参数,那么前面的r3和r4寄存器也一定被用于传递参数。 由于编译器的优化,函数参数的传递位置可能发生改变,函数参数的传递会远离函数调用指令,因此仅对单个的函数调用进行参数识别会出现识别错误的情况。为解决参数识别错误的问题,本文提出采用多函数调用分析的方法,即当该函数存在多个调用者时,对每个调用者都进行函数参数传递分析,获得该函数的参数个数,然后使用基于投票的方法,选择函数参数个数出现频率最多的为函数参数的个数。 通过对函数的调用关系的分析,能够得到{子程序:调用者1,调用者2,调用者3,…,调用者n}的函数调用关系。为了更准确地获得函数参数的个数,本文首先分别计算函数的调用者中传递的参数个数;然后基于投票的机制,对函数的参数个数进行投票,获得票数最多的为函数的参数个数;最后运用函数参数个数识别算法进行函数参数个数的识别。 函数参数个数识别算法——Findargs如下。 函数参数个数识别算法——Findargs如下。 输入 二进制代码序列B和函数调用关系集 78ec3d9a5abe1f853c748def8bd04722.png 输出 函数参数个数对 9658e6eb847d3981cd0d21686cfb30d8.png 1) 对参数变量进行初始化,设置函数参数个数数组S[],并对数组S[]中元素初始化为0; 2) 从函数调用关系集 M 中取出一个元素,进行函数参数个数的识别,当取出所有元素后,结束执行,进行步骤5); 3) 从函数关系中,依次取出函数调用者地址,并在二进制代码序列B中取出该函数的二进制指令Fi,若已经取出所有调用者,则转步骤4); 对函数指令序列Fi进行线性扫描解析,查找函数调用指令,取出函数调用指令前m条指令进行参数寄存器和栈使用情况的分析,计算出函数的参数个数n,则S[n]←S[n]+1,继续步骤3); 4) 查找数组S[]中最大数,则最大数的下标j即函数的参数个数,将{calleri:j}添加到函数个数对集合P中,继续步骤2); 5) 输出集合P中的函数参数对。

3.4 运行实例

调用函数 Func_37326dc8 的函数关系如图5所示。 由图5可知共有9个函数调用了该函数,为了准确识别函数Func_37326dc8的参数个数,对调用函数Func_37326dc8的这9个函数进行参数传递分析,最终选取出现次数最多的数为函数的参数个数。其中,根据函数Func_3092dabc传递的参数分析函数 Func_37326dc8 的参数个数为2,而对剩余的8个函数进行参数传递分析获得的函数Func_37326dc8的参数个数为3,因此函数Func_37326dc8的参数个数为3。经验证结果正确。 884eda6922af8418aa0330fcb876294b.png 图5   调用函数Func_37326dc8的函数关系

4 实验评估

4.1 实验设置

本节对Findargs的函数参数识别效果进行评估,并与现有的工具radare2进行对比。实验所用计算机的配置为Intel R CoreTM 6-core 3.7 GHz i7-8700K CPU and 32 GB RAM。radare2的版本为radare2 2.7.0-git 18197 @ linux-x86-64 git.2.6.032-g3c8d7d53f。

4.2 实验结果

4.2.1 库函数的识别效果 为了验证函数参数个数的识别效果,需要选取拥有大量函数并且调用关系多样的二进制代码。本文选取了路由器固件 C2600-IPBA SEM-12.3(6f).BIN(PPC 指令集)和 C2900UNIVERSALK9 - M-15.7(3)M2.BIN(MIPS指令集)为测试样本,逆向分析得到二进制中的C语言库函数,对函数参数个数的识别效果进行验证, C语言部分库函数的参数个数识别对比如表3所示。从表中可以看出该方法能够准确识别出函数的参数个数。

21e5e20e90b3b965ad28e3932ef0418e.png

“—”表示未在PPC指令集的二进制文件中发现此函数 由表3 可知,Findargs 对于常用库函数的识别的效果较好,原因是库函数调用的次数比较多,并且函数的参数个数较少,使用参数寄存器能够满足所有参数的传递。 4.2.2 未知函数的识别效果 为了能够更准确地评估 Findargs 的识别效果,本文从路由器固件中 C2600-IPBASEM-12.3(6f).BIN和C3725-I-M-12.3(1a).BIN逆向分析获得函数的参数个数,分别使用 Findargs 和radare2进行了参数个数的识别,识别结果如表4所示。实验结果表明,与radare2相比,Findargs具有更高的准确率。对于PPC指令集,Findargs准确率达到 90.3%;对于 MIPS 指令集,识别的准确率较低,达到86.0%。在实验中,Findargs的输出结果为函数的起始地址和参数个数对。而radare2 首先通过命令“aaa”对二进制可执行文件进行分析,然后通过“afll”命令输出到文本中,并使用 Python 脚本对输出的结果进行提取以获得函数的参数个数。 4.2.3 函数调用次数对识别效果的影响 为了更好地展示Findargs的识别效果和函数调用次数对参数个数识别准确率的影响,本文依据函数的调用次数对这些函数进行了分类统计。对PPC和MIPS指令集分别统计了上述的300个函数,分类统计结果为:对于PPC指令集,函数调用次数大于或等于2次的共有139个,函数调用次数大于或等于3次的共有77个,函数调用次数大于或等于4次的共有54个;对于MIPS指令集,函数调用次数大于或等于2次的共有144个,函数调用次数大于或等于3次的共有105个,函数调用次数大于或等于4次的共有76个。函数调用次数对函数参数个数识别的影响如表5所示,其中,多调用分析是对所有的调用者进行分析识别准确的函数个数,单调用分析是只分析其中的一个函数调用进行分析识别准确的函数个数。 表6展示了不同调用次数下的函数参数个数的识别准确率。由表中数据可知,函数调用次数越多,函数参数个数的识别准确率越高,并且多调用分析的效果好于单调用分析。 4.2.4 函数参数个数对识别效果的影响 为了研究函数参数个数对参数识别准确率的影响,本文对不同参数个数的函数进行了统计(如表7所示),分别计算了识别的准确率(如表8所示)。由表可知,函数的参数个数一般小于 6 个。整体上来说,在此范围(参数小于6)内函数识别的准确率较高,由于PPC有8个参数寄存器,基本能够满足函数进行参数的传递。对于MIPS指令而言,在参数个数大于4时,需要借助栈进行参数的传递,使用栈进行参数的传递比使用参数寄存器传递复杂,因此识别的准确率略低。后续将考虑结合数据流分析追踪参数的使用情况来提高MIPS指令集下函数参数个数识别的准确率。此外,经过分析函数调用的位置同样影响函数参数个数的识别准确率,位于函数头部的函数调用由于借用了上层函数的调用参数,没有明显的传参行为,因此对存在此类函数调用的函数的识别准确率较低。

24daf2b029f3cfe6427f01d1c0f2563d.png

64e182bba941f8f2a44c713d31d0e63a.png

ceecb9381f70f3b81964953e751f67bc.png

2b3ecf9a5b3860b228f52f6bab48b083.png

6c804b1d589d84348e350873c9cdbac0.png

5 结束语

函数的参数个数作为函数原型中的一个重要特征,能够辅助后续的二进制程序分析。本文提出了二进制函数参数个数分析算法——Findargs,来识别PPC和MIPS指令集下二进制代码中函数的参数个数。实验结果表明,Findargs 具有较高的准确率,对于PPC指令集的二进制可执行文件,识别准确率达到 90.3%;对于 MIPS 指令集的二进制可执行文件,识别准确率稍低,为 86.0%。并且,本文与现有的工具radare2进行了对比,结果表明Findargs具有更高的准确率。 由于Findargs依赖于对参数寄存器的分析,对于_cdecl、pascal、stdcall仅使用栈进行参数传递的方式适用性较差,但对多函数调用关系依据投票来选择最准确的参数个数的机制可以应用到其他3种参数传递规范。这3种参数个数分析方法可以应用到对 fastcall 参数传递方式的分析。当使用 fastcall进行参数传递时,函数调用传递的参数个数超过参数寄存器个数时,参数传递的方式与上述3种方式相同。后续将考虑通过数据流分析追踪参数的使用情况,来提高参数个数识别的准确率。

作者简介

尹小康(1994-),男,河南周口人,数学工程与先进计算国家重点实验室博士生,主要研究方向为网络空间安全和逆向工程 。 刘鎏(1998-),女,安徽合肥人,主要研究方向为网络空间安全 。 刘龙(1983-),男,河南尉氏人,数学工程与先进计算国家重点实验室副教授,主要研究方向为网络空间安全和机器学习 。 刘胜利(1973-),男,河南周口人,博士,数学工程与先进计算国家重点实验室教授,主要研究方向为网络空间安全。 网络与信息安全学报

  《网络与信息安全学报》是由工业和信息化部主管,人民邮电出版社有限公司主办的信息安全领域的学术刊物,现为中国网络空间安全协会会刊,中国科技核心期刊、CCF推荐中文科技期刊。办刊宗旨:汇聚安全创新思想,传播学术研究成果,提升科学研发实力,服务国家信息安全。

8c796dbb942922ab089e68fe321a90ff.png

中国网络空间安全协会会刊

中国科技核心期刊

CCF推荐中文科技期刊

关注我们,查看更多内容

e22095a92af5c5a88d3800f775849a2d.gif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值