赞
踩
论文:《Code is not Natural Language: Unlock the Power of Semantics-Oriented Graph Representation for Binary Code Similarity Detection》
仓库:https://github.com/NSSL-SJTU/HermesSim
二进制代码相似性检测(BCSD)是确定两个二进制函数之间的语义相似性的基本任务。
现有的BCSD方法主要分为两种:基于指令流的方法和基于控制流图(CFG)的方法。基于指令流的方法将指令流视为自然语言句子,并应用自然语言处理技术。这些方法虽然先进,但预训练过程昂贵,且侧重于学习代码的有限方面。而基于CFG的方法则侧重于利用图神经网络捕获控制流特征,但同样受限于对基于NLP方法的依赖。
作者提出,指令流与自然语言句子在语法上相似,但它们在结构、语义和约定上有本质的不同:
这些差异表明,将代码视为自然语言并不是最理想的。
作者建议开发一种二进制代码表示,能够:
作者列举了三种简单的、语义上等效的转换,这些转换对NLP模型来说是必须学习的:
- 交换指令顺序: 代码片段中的前两个指令可以交换而不改变代码语义。然而,这种转换会改变产生的嵌入集,因此模型需要学习哪些指令的顺序可以调整而不改变语义。(如(b))
- 改变代码片段的位置: 整个代码片段可能由于在序列开始处插入了一些虚拟指令而被放置在不同的位置。这会改变所有token的位置嵌入,导致一个完全不同的嵌入集,模型需要学习不同位置的相同子序列在语义上是等价的。(如(c))
- 替换寄存器使用: 使用的寄存器r0可以被以前未使用的寄存器r2替换。模型需要学习所选择的确切寄存器是与语义无关的。相反,通过寄存器传递的数据流则是代码语义的一个组成部分。(如(d))
故学习这些附加的语义使得基于NLP的方法变得更加昂贵且难以泛化。
作者先讨论了二进制代码的隐含结构,可以分为三个类别:指令内部结构、指令间关系和函数级别的约定。
指令内部结构(Intra-instruction Structures):指令有其内部结构。例如,MIPS架构中的指令add r1, r2, r3
可以解释为add
操作符使用存储在寄存器r2
和r3
中的值,并将产生的值存储在寄存器r1
中。同时,x86-64架构中的指令add rax, rdx
可以解释为add
操作符使用存储在寄存器rax
和rdx
中的值,但其输出存储在rax
和eflags
寄存器中。
指令间关系(Inter-instruction Relations):这些关系可以分为data (def-use relations), control (branches), and effect (execution order)。
Data relations:揭示了一些指令使用由其他指令定义的值。
Control relations:在基本块级别定义控制流。
Effect relations:之前的研究往往忽略了这一点,建立了指令间执行顺序的限制。(如Figure3(a)的第4行和第5行)
一种类似于传统控制流图表示的方法是将每个基本块中的指令序列化,以限制执行顺序。然而,这种表示方式施加了过多的限制。(第4行的指令不能与前面的两条指令交换,但第2行和第3行的指令可以交换而不影响代码语义)
为了追求效果和效率,作者的目标是在最终表示中仅包含必要的执行顺序。故采用the effect model:将额外的执行顺序限制建模为潜在的数据流。在上述示例中,STORE指令修改了一个内存槽,而CALL指令调用的子程序Foo可能读取或写入同一个内存槽,这构成了一个潜在的数据流。作者使用一个抽象的临时变量来代表每一组相关的内存槽。可能从该内存槽组读取值或写入值的指令被认为使用或定义相应的临时变量。
值得注意的是,构建的效果流与分析能力有关。例如,在(d)中,如果确定被调用的子程序不与内存交互,或STORE指令仅修改当前堆栈帧,而任何子程序都不会访问它,那么在它们之间就不需要引入效果关系。
图 3a 中代码片段的语义解释如下。第 1 行的标签 L1 和第 7 行的标签 L2 分别表示两个基本模块的开始。指令 r0 = LOAD 0x1000 加载地址为 0x1000 的内存槽值,并将其放入寄存器 r0。随后,r1 = ADD r1, 2 将表达式 r1 + 2 的值存储到寄存器 r1 中。STORE r0, r1 将 r1 的值存储到 r0 所指示的内存地址中。指令 r3 = CALL Foo, r0 以 r0 为唯一参数调用子程序 Foo,并将返回值存入 r3。BR L2 直接跳转到标签 L2 标记的基本程序块。最后,RET r3 将控制权返回给调用者,返回值设置为存储在 r3 中的值。
函数级别的约定(Function-level Conventions):调用约定定义哪些寄存器用作调用参数和返回值,这有助于恢复调用指令的定义使用关系。此外,函数将临时变量存储在自己的堆栈帧中,其他函数不会访问这些临时变量。这种理解有助于完善Effect relations。
故提出的SOG(Semantics-Oriented Graph)将通过处理三个关键方面,有效捕获二进制代码的语义:
SOG的构建包括三个步骤:
SOG能够编码各种函数级约定,例如调用参数和堆栈帧。例如,它可以通过从调用节点到参数节点添加数据边来揭示调用参数,并使用抽象临时变量来模拟堆栈帧的效果。
SOG 的构建可以通过与将线性 IR 转换为 SSA 形式类似的过程来完成。
将 SOG 分为三个子图:
control子图构建:
data子图构建:
effect子图构建:
此外,还在SOG中集成phi节点。SOG的构建自然与静态单赋值(SSA)形式一致,因为它为每个指令定义了一个节点。为了保持简洁,需要为data值和effect值引入必要的phi指令。
论文的Appendix A中提供了图构建算法的伪代码。
SOG的每个节点都有一个token,每条边都有一个类型label和一个位置label。
我们采用基于边际的成对损失[21]和距离加权负采样策略[41]进行训练。
通过首先对 N 个不同的函数符号进行采样,然后对每个符号具有不同编译设置的 2 个函数进行采样来收集小批量。
使用余弦相似度的负值作为距离度量。
图归一化和编码
每个节点都有一个token,直接
映射到可学习的嵌入向量。
每条边都有一个类型属性(data、control或effect)和一个位置属性(相应操作数的索引)。分别将类型和位置属性转换为两个嵌入,并将它们相加形成最终的边嵌入。
模型需要学习包括token和label在内的词汇表。一些token和label在词汇表之外,故:
局部结构捕捉
使用双向 GGNN 层来捕捉每个节点的邻接结构。
Multi-head Softmax Aggregator
采用Multi-head Softmax Aggregator,将所有节点嵌入聚合成图嵌入,它改编自 softmax 聚合器 [19]。
公式(11) :每个头的节点嵌入首先通过一个线性层转换到不同的表示空间,然后通过ReLU函数进行非线性变换。
公式(12) :通过Softmax聚合器对每个节点的特征进行选择性加权,其中每个头的输出 x^k_u 会减去平均值并除以标准差,实现了特征的归一化处理。ε 是一个小数,用来保证数值稳定性。
公式(13) :最终,所有头的输出嵌入向量被拼接起来,并通过另一个线性层来生成最终的图嵌入 g。
图构建模块基于 Ghidra
图嵌入模块: Pytorch 和 PyG 。
参数设置和结果图表详见论文。
数据集: 使用先前研究发布的Dataset-1,包含三种不同架构(x86、ARM和MIPS)、两种位模式(32位和64位)、5个优化级别(O0、O1、O2、O3和Os)的257K训练、13K验证和522K测试二进制函数,由两个不同的编译器家族(GCC和CLANG)编译。
**对比实验:**Table 1、Figure 7
**消融研究:**Table 2、Figure 8
运行时效率:
运行时成本:
图表示的大小和推断时间:
CFGs和ISCGs平均而言比SOGs小,这意味着在本地结构捕捉阶段具有更低的内存和时间成本,但会增加节点属性提取的复杂性
现实世界漏洞搜索:
研究人员收集了来自三个供应商(TP-Link、Mercury和Fast)的12个RTOS固件图像,进行了1天漏洞搜索任务。他们构建了一个包含62605个函数的存储库,跨两种不同架构(ARM32和MIPS32)。在TP-Link WDR7620固件中手动识别了5个CVE和10个易受攻击函数,并将这些易受攻击函数用作查询。
对于每个查询函数,研究人员将存储库中的函数分为三组:c1代表与查询函数具有完全相同源代码构建的函数,c2代表与查询函数具有相同符号但源代码略有不同的函数,c3代表其他从不同源代码编译的函数。
通过使用HermesSim和其他基准方法进行相似性搜索,并手动检查前20个结果。
理想情况下,BCSD 系统应将 c1 中的函数排在 c2 和 c3 中的函数之前,并将 c2 中的函数排在 c3 中的函数之前。
结果:HermesSim在失败次数、RECALL@1和MRR方面明显优于其他基准方法。它能够有效地识别和排名易受攻击函数,包括那些在源代码略有不同或不同架构之间的情况。
Appendix B 展示漏洞的详细信息
Dirty Effect Problem:
编译器引入与effect相关的指令
堆栈临时变量的数量取决于体系结构和优化级别,在effect flow中包含堆栈内存访问可能没有好处
解决方案:load-store elimination analysis
I/O Effect Model:**本文只研究了memory effect model。 ** I/O Effect 涉及到与I/O设备交互的指令。
Extra Information and Encoding Ability:本研究不处理对字符串、整数、外部函数符号和其他相关实体的引用。
Addressing Analysis Failures:
当前的传统程序分析算法可能无法完全恢复间接分支的控制流关系,这一限制影响了SOG表示。
探索如何向深度神经网络提供必要的信息(例如,引用的数据),以使它们能够推断间接分支的控制流,可能是值得的。
在本文中,作者提出了一种语义完整的二进制代码表示方法,即语义导向图(SOG),用于二进制代码相似性检测(BCSD)。这种表示方法不仅利用了代码结构、语义和约定的明确定义,而且还清除了嵌入在低级机器代码中与语义无关的元素。作者详细介绍了SOG的构建过程,并讨论了对这种表示方法的潜在改进。为了充分发挥SOG在BCSD中的潜力,作者提出了一种新颖的多头softmax聚合器,它允许有效融合图的多个方面。通过整合所提出的技术,作者构建了一个有效且高效的BCSD解决方案,HermesSim,该解决方案依赖于图神经网络(GNN)模型来捕获SOG的结构信息,并采用先进的训练策略。广泛的实验表明,HermesSim在实验室实验和现实世界的漏洞搜索中都显著优于现有的最先进方法。此外,评估证明了揭示二进制代码的完整语义结构和清除与语义无关的元素的价值。作者还展示了所提出的聚合器的有效性和HermesSim的高效性。
switch
语句可能被编译成使用跳转表的间接跳转,其中具体的跳转目标取决于switch
语句的条件变量。2: 输入是一个二进制函数f
,是汇编语言或线性中间表示(IR)形式的函数。
3: 创建一个新的DefState
对象,用于跟踪和管理在构建SOG过程中所有变量的定义状态。
4: 创建一个新的SOGConstructor
对象,这是构建SOG的工具。
5: 调用GetAbstractEffectNodes
函数获取所有抽象的效应节点,这些节点代表程序中可能影响状态的操作(如内存写操作)。
6: 计算函数f
的控制流图(CFG)的支配树(Dominator Tree),它是理解代码结构的一种方法,可以确定某个特定节点必须通过哪些路径才能被到达。
7: 在函数f
中插入Phi节点,Phi节点用于合并来自支配树中不同路径的变量定义。
8: 准备函数f
的控制流相关数据,可能涉及到设置控制流节点和边的关系。
9: 处理函数f
的每个基本块,使用defState
来跟踪定义状态,f.start
是处理的起始点,effectNodes
包含了所有需要考虑的effect节点。
10: 返回构建的语义导向图g
13: 遍历函数f
中的所有基本块(bb
)。
14-15: 检查每个基本块是否以分支指令结束。如果不是,添加一个虚拟的分支指令,这是为了确保每个基本块在图中都正确地表示其控制流。
16: 创建一个新的节点(node
)来表示基本块中的最后一条指令,这里假设基本块的最后一条指令是与控制流相关的。
17: 在defState
中记录这个基本块与新创建的节点之间的关系。
20: 提交当前的定义状态,这相当于在处理每个基本块之前保存当前的变量定义状态。
21: 调用BuildInBlock
函数,为当前的基本块bb
构建内部结构
22-23: 对于基本块的每个子节点,递归地调用ProcessBlock
函数来处理。
24: defState.revert()
回滚到最后一次提交的定义状态。这是为了在处理完当前基本块及其子代后,能够恢复到进入基本块之前的状态,以便准确地处理其他基本块。
28: 遍历基本块bb
中的每一条指令inst
。
29: 为每条指令创建一个对应的SOG节点node
。
30: 将创建的节点node
添加到SOG中。
data flow
32-34: 对于指令的每个输入操作数,创建或获取一个对应的inpNode
,并将其作为data-use添加到节点node
。
35-36: 对于指令的每个输出操作数,更新defState
以反映最新的定义状态
effect flow
38-39: 遍历所有的effect
,如果节点node
使用了这个effect,则添加一个effect-use关系。
40-42: 如果节点node
定义了某个效应,则更新defState
以反映这个effect的最新定义状态。
control flow
44-48:如果节点node
是一个控制节点(例如条件分支),则对其前驱节点进行处理,可能包括用MayWrapWithProj
包装多个后继的情况,并添加控制使用关系。
49-52:对于基本块的每个后继节点succ
,处理与Phi节点相关的控制流,这可能涉及到在Phi节点中设置正确的输入顺序。
最后,过程返回,完成对当前基本块内部结构的构建。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。