基于运行特征监控的代码复用攻击防御∗

2019-12-11 04:27张贵民李清宝程三军
软件学报 2019年11期
关键词:调用指令代码

张贵民 , 李清宝 , 张 平 , 程三军

1(解放军信息工程大学,河南 郑州 450001)

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

3(信息保障技术重点实验室,北京 100072)

4(河南省人民检察院,河南 郑州 450000)

代码复用攻击(code reuse attack,简称CRA)正严重威胁着网络系统的安全.CRA 并不向内存中植入任何恶意代码,而是首先在内存中的已有代码中寻找可用的指令序列(即gadget),然后劫持控制流,使这些gadget 得到执行从而实现攻击.因此,CRA 能绕过数据执行阻止技术(data executive prevention,简称DEP)[1]和W⊕X 等内存保护机制,典型CRA 包括return-into-libc(RILC)[2]、ROP[3]、JOP[4]、COOP[5]等.

CRA 防御方法总体可分为3 类:代码随机化防御方法、控制流完整性保护方法和CRA 特征检测方法.

(1)代码随机化防御方法

代码随机化防御方法通过增加攻击者获取gadget 的难度,来防御代码复用攻击.ASLR[6]通过随机化动态链接库和可执行文件的加载地址,使攻击者无法定位gadget.之后,不同粒度的随机化方法[7,8]被先后提出,以增强ASLR 方法的安全性.但攻击者仍可通过内存泄漏[9]找到gadget,例如JIT-ROP 攻击[10].为了应对内存泄漏+代码复用攻击,研究者提出了动态随机化方法.Isomeron[11]和Remix[12]分别利用双函数副本执行和函数内基本块变换的方式实现对函数内部的随机化,但都无法防御函数级复用攻击;TASR[13]实现了对程序整个代码段内存位置的随机变化,但代码段内容不变.因此,一旦攻击者定位到该程序所在内存即可掌握其所有代码特征.另外,该方法仅对C 语言程序实施保护,应用范围受限.

(2)控制流完整性保护方法

控制流完整性保护方法通过阻止攻击者篡改控制流,使 gadget 序列得不到执行.CFI[14]基于控制流图(control-flow graph,简称CFG)防御所有控制流劫持攻击,但开销较大,实用性差.为了降低开销,CCFI[15]、Contextsensitive CFI[16]和CFI-KCraD[17]等方法通过结合对程序语义的分析,保证控制流在一定层度上的完整性,提高了方法的实用性.但控制流被劫持的风险依然存在[18],例如CFI-KCraD 中的方法只能防御指令片段的复用,而无法防御函数级复用.

(3)CRA 特征检测方法

CRA 特征检测方法通过监控当前是否存在代码复用攻击的执行特征来检测和阻止攻击的实现.ROPecker[19]通过检测是否执行了超过某个数量(阈值)的gadget 来检测代码复用攻击,其问题在于难以确定合理的阈值,且无法防御利用长gadget 实施的攻击[20].ROPdefender[21]基于call 指令与ret 指令的匹配来防御ROP攻击,但call 和ret 指令并不总是成对出现,且该方法无法防御除ROP 外的其他代码复用攻击,如JOP、COOP等.文献[22]提出通过检测ret 指令的执行频率来推断ROP 攻击,但无法防御JOP、CPROP[23]等攻击.

除了上述防御方法外,SoftBound[24]、Baggy Bounds Checking[25]等方法通过检测内存错误,从源头上来防御攻击,但开销太大;文献[26]基于编译器来消除用于构建gadget 的指令,该方法依赖源码且必须对所有程序模块进行处理,实现复杂;文献[27]通过阻止对代码的读操作使攻击者无法通过直接扫描内存获取gadget,但攻击者仍可通过其他内存泄漏方式获取到gadget 并构造攻击;CPI[28]通过保护代码指针的完整性来防御代码复用攻击,但该方法已被证明是可以被绕过的[29];DFI[30]基于静态分析的到达定义集合对数据进行保护,但需要源码且需要处理所有相关库和模块.

另外,还有通过代码和数据隔离的方法[31],可防御内存泄漏攻击,却无法防御return-into-libc 攻击.

通过分析当前代码复用攻击的防御方法,发现依然还存在以下几个方面的问题.

1)代码随机化方法通过阻止攻击者获取有效的gadget 信息来预防代码复用攻击,尤其是动态随机化,但攻击者仍可借助侧信道攻击[32]等方式绕过该类防御方法.

2)控制流完整性保护方法阻止攻击者执行gadget 串,但研究表明,即使在细粒度控制流完整性保护下,攻击者仍可劫持控制流执行特定代码.

3)基于CRA 特征检测的方法在代码复用攻击运行阶段检测和阻止攻击的进一步执行,但无法涵盖当前各种新型代码复用攻击的所有特征,防御能力有限.

代码随机化方法和控制流完整性保护方法能够在一定程度上增加CRA 实现的难度,但总会出现能够突破防护的新型攻击;基于CRA 特征检测的方法不关注如何阻止攻击的发起,而是着眼于如何尽快在攻击发起后检测并阻止其进一步执行,但问题在于该类方法无法跟上代码复用攻击技术更新的速度,对新型攻击无能为力.针对上述问题,本文提出一种基于程序运行特征监控的代码复用攻击防御方法(running characteristics monitoring,简称 RCMon).该方法首先对CRA 的基本原理进行分析,并在此基础上定义了程序运行特征模型(running characteristics model,简称RCMod).该模型由一系列程序关键节点处的运行特征模式组成,即各关键节点之间调用的关键系统调用的类型以及执行的总次数等统计特征.另外,还基于RCMod 设计了防御CRA 的安全验证自动机模型;然后,利用二进制插桩技术直接向被保护程序(目标程序)可执行文件中的所有关键节点处植入监控代码,使目标程序运行到关键节点时陷入到Hypervisor;之后,由Hypervisor 在训练阶段构建目标程序的运行特征库;最后,Hypervisor 按照安全验证自动机模型对目标程序的实时运行特征进行监控,从而实现对代码复用攻击的防御.

攻击者实施代码复用攻击或其他攻击的目的都是为了执行某些特定操作.RCMon 的研究基于这样一种假设,即攻击者在实施恶意操作时难免要通过系统调用和操作系统进行交互.由于不借助任何系统调用的攻击所能实施的操作有限,如非控制数据攻击[33],这类攻击并不在本文的讨论范围.

RCMon 的设计目标是:

1)不需要程序源码和定制的编译器,不改变系统配置和模块,可兼容于遗留代码(legacy code);

2)能够防御当前已知的各类CRA(详见第3.1 节表5),对未知的新型CRA 也具有一定的防御能力;

3)在对目标程序进行保护时,要使目标程序、操作系统和其他应用程序的开销均保持在可接受范围.

本文第1 节描述RCMon 的总体架构及相关模型设计.第2 节论述RCMon 的具体设计和实现方法.第3 节实现RCMon 原型系统并对其进行测试.第4 节讨论RCMon 的局限性.第5 节对全文工作进行总结.

1 总体架构

RCMon 的总体架构如图1 所示,由目标程序插桩阶段(Instrumentation phase)、训练阶段(Training phase)和运行阶段(Running phase)这3 部分组成.

Fig.1 Overall architecture of RCMon图1 RCMon 方法总体架构

插桩阶段直接处理目标程序的可执行文件,由插桩模块(instrumentation module)将特定指令序列(即监控指令)植入到目标程序中的某些关键节点,其中,监控指令中含vmcall 指令[34]以及标识程序关键节点的重要信息,最终生成一个新的可执行文件;训练阶段是在一个已开启了虚拟机监控器Hypervisor 的客户机环境中运行由插桩阶段生成的新可执行文件,该Hypervisor 能够监控程序运行过程中执行的所有关键系统调用和vmcall 指令,从而获得系统调用执行信息和目标程序的关键节点信息,然后由运行特征统计分析模块(RCs statistic module)根据这些信息建立目标程序的运行特征库,并通过加密和度量模块(encryption and measurement module)处理后存储在特征文件中,作为下一步验证目标程序行为特征是否正常的依据,最后通过多次训练以提高特征库的完备性;运行阶段,首先由度量和解密模块(measurement and decryption module)负责验证目标程序特征文件的完整性并解密该文件,然后将该文件加载到Hypervisor 内存中,并加载运行目标程序.在目标程序运行过程中,Hypervisor 根据监控得到的关键系统调用执行信息,在每次监控指令标识的关键节点处验证目标程序在该节点处的运行特征是否正常:若不正常,则表明程序遭到攻击,立即终止程序运行;若正常,则根据运行特征库中对下一步要执行的关键系统调用的信息设置每个关键系统调用对应的看门狗计数器(watchdog counter,简称WDC),由它实现对每次关键系统调用的合法性进行验证.通过这两种安全机制,实现对代码复用攻击的有效防御.

1.1 程序运行特征模型

CRA[2-5,10,23,35,36]的本质都是通过串连以间接跳转(indirect jump)指令、间接调用(indirect call)指令以及ret指令结尾的gadget 来实现对内存中已有代码的复用,而根据复用代码的粒度不同,存在指令片段复用和函数级复用两种类型,如图2 所示.在图2 中,实线箭头表示正常的控制流,当攻击者发现当前内存中存在gadget1~gadget5 这5 个可利用的指令序列时,则通过篡改栈、寄存器或者虚函数表指针等方式实现代码复用攻击;图中虚线箭头表示该攻击的执行过程,图中instruction1i和instruction2i+k为indirect jump 指令,该攻击示例中既包含了对程序自身函数func1 和func2 中指令片段的复用,也包含了对库函数func4 的函数级复用.根据前文假设,gadget1~gadget5 中必定存在某个gadget 调用了相关系统调用以实现攻击目标,而且由于gadget 间的跳转执行改变了程序的原有执行过程,这势必将改变目标程序原有的系统调用执行过程.因此,通过对异常系统调用的及时检测和阻止可实现对攻击的有效防御,RCMon 正是基于该论断进行研究的.

Fig.2 An example of CRA图2 CRA 示例

为了检测异常行为,必须首先界定什么是正常行为.如果借助若干个程序执行过程中的关键节点将程序的整个执行过程分成多个不同的执行域,那么每个区域都有自己需要的系统调用的类型和数量,将其称为该区域的运行特征.当关键节点的选取恰当时,通过验证每个区域的运行特征,即可实现对代码复用攻击的检测和阻止.基于对CRA 实现方式的分析,由关键节点划分的区域必须能够覆盖CRA 采用的以indirect jump,indirect call和ret 指令为结尾的指令片段,且要尽量实现就近覆盖.最直接的想法是以所有的上述3 类指令作为关键节点进行划分,但考虑到监控效率和实现复杂度,RCMon 选择采用indirect call 和ret 指令作为关键节点,并辅以调用点前后位置节点,实现对所有已经用于以及将来可能会用于CRA 中的转移指令进行覆盖.图2 中标出了各个关键节点的信息,即对应indirect call 和ret 指令的每个函数的入口(function enter,简称FEN)节点和出口(function exit,简称FEX)节点,以及函数调用点前(before callsite,简称BC)节点和调用点后(after callsite,简称AC)节点.之所以引入BC 和AC 节点,是由于RCMon 为了达到不改变系统环境和模块的目的,没有对动态链接库中的函数(如图2 中的func3 和func4)进行插桩,因此无法监控库函数的入口和出口以及库函数中又间接调用的其他库函数的入口和出口.通过增加BC 和AC 节点,RCMon 将库函数中的所有执行过程抽象为一个整体,不仅达到了不改变系统环境和模块的目的,也实现了对库函数执行行为的有效监控.通过上述4 类关键节点的划分,除indirect call和ret 指令外的其他可能指令(包括indirect jump 指令)构建的所有gadget,都只能存在于由FEN 和BC、FEN 和FEX、AC 和FEN 以及BC 和AC 划分的区域中.

基于上述分析,构建了程序的运行特征模型RCMod 来描述程序的正常运行特征.为了描述RCMod 的构建方法,首先给出描述程序的运行特征的模式定义.

定义1.运行特征模式(running characteristic pattern,简称RCP)是一个4 元式(procID,fID,NT,SCCA),其中,

•procID:该运行特征所属程序的ID 编码,即标识某个被保护程序的整数值,由用户指定.

•fID:关键节点所处函数体的ID 编码,即标识某个被保护程序中不同函数体的整数值,由算法自动分配.

•NT:关键节点类型.当NT为1~4 时,依次表示当前关键节点为BC,FEN,FEX 和AC 节点.

•SCCA:系统调用特征序列.假设运行特征中涉及的系统调用共有Nsc(Nsc≥1)种,则SCCA={scca[i][j]|i∈[0,Nsc-1],j∈[0,1]},其中,scca[i][0]和scca[i][1]分别表示程序到达本节点时第i种系统调用已经执行了的总次数和在该节点到下一节点之间的区域内将要执行的次数.

由定义1 可知,一个RCP 给出了某个程序在某个关键节点处的运行特征.一个程序往往由多个函数实现,包括对某些库函数的调用,必然引入一定数量的关键节点.假设一个程序共含有k个关键节点,则至少含有k个RCP,因为当存在对某个函数的循环多次调用时,每次循环时与该函数相关的各个节点的scca[i][0]都将发生变化,即生成新的RCP.该程序的运行特征模式空间Ω={RCP1,RCP2,RCP3,…,RCPn}(n≥k,n∈N+),利用程序一次运行中的运行轨迹经过的所有关键节点的RCP 的集合即可描述该程序本次运行过程的运行特征,详见定理1.

定理1.设F为Ω的子集族且为C 族(即F为Ω的所有子集构成的子集族),那么程序一次运行的运行特征一定可由集合R表示,且R∈F.

证明:从程序的所有运行轨迹中任选一条轨迹,假设该条轨迹中含有的运行特征模式为RCPi,RCPj,RCPk,…,RCPm(1≤i≤j≤k≤…≤m≤n;i,j,k,…,m∈N+),此时:

集合R={RCPi,RCPj,RCPk,…,RCPm}即表示了该程序本次运行的运行特征;

因为RCPi,RCPj,RCPk,…,RCPm∈Ω,所以R⊆Ω;

因为F为Ω的子集族且为C 族,所以R∈F.定理1 得证.

R描述了程序一次执行过程中的所有运行特征,因此,R即是RCMod 模型的具体表现形式.□

1.2 安全验证自动机模型

在运行阶段,RCMon 采用两种安全机制:基于关键节点陷入事件触发的后向安全验证(backward safety verification,简称BSV)和基于WDC 的前向安全验证(forward safety verification,简称FSV).

BSV 是在由监控指令导致的关键节点陷入时进行的第1 类验证,将陷入节点的procID、fID、NT以及当前各个关键系统调用执行的总次数scca[i][0](0≤i≤Nsc-1)与事先训练获得的特征库中的值进行匹配验证.如无匹配项,则说明该节点之前的运行过程已经遭到攻击,此时终止该程序的执行;否则,继续执行FSV.BSV 机制保证了当前关键节点的到来是合法的,只有通过BSV 机制验证过的指令序列(或整个函数)才能成功执行.

但BSV 存在一定的局限性.假设目标程序遭到代码复用攻击并将控制流劫持到动态链接库中的代码,如果之后控制流不再回到目标程序的自身代码执行,即一直到攻击完成都不会再到达含监控代码的下一个关键节点,那么BSV 是无法防御该类攻击的;另外,如果攻击者劫持一段时间控制流后再次到达另一个关键节点,虽然此时BSV 能够检测到攻击,但攻击可能已经执行了想要执行的恶意操作.因此,采用另外一种验证机制FSV 来弥补BSV 的不足.

在论述FSV 机制前,首先对本文提出的WDC 进行说明.看门狗也叫做看门狗计时器(watchdog timer,简称WDT),是单片机中的一种安全机制.WDT 是一个计时器电路,在单片机开始工作后启动,微处理器控制单元(microprocessor control unit,简称MCU)正常情况下会定期输出一个信号给WDT,称为喂狗,目的是使WDT 清零.而一旦受到干扰导致程序跑飞时,MCU 将无法在规定时间内执行喂狗操作,最终导致WDT 超时.此时,WDT就会给MCU 一个复位信号,使MCU 复位,从而防止MCU 死机.本文提出的WDC 正是借鉴了WDT 的思想.WDC在这里是一个计数器而不是计时器,它接受一个数值作为临界值.当启动后,WDC 的数值通过事件触发的方式增长,所关注事件(本文为所监控的关键系统调用)每触发一次,计数器加1.WDC 与WDT 的不同点还包括:WDC只有在恰好等于临界值时接收到喂狗操作才说明系统的运行正常,否则都会发出异常警告.

FSV 在经过后向验证后执行,将找到的匹配项中的各个scca[i][1](0≤i≤Nsc-1)的值设置为对应的各WDC的临界值.RCMon 为每个受保护的目标进程维护着Nsc个WDC,它们分别由相应的关键系统调用的执行作为计数器加1 的触发事件,即所关注的系统调用每执行一次,WDC 的值加1,并以关键节点陷入事件作为喂狗操作.若WDC 在达到临界值前接收到了喂狗操作,即下一个关键节点提前到达,则说明在本应执行该WDC 对应的系统调用的某处指令并没有得到执行,程序遭到攻击;若WDC 超过临界值,则说明此时执行了多出正常范围的系统调用,程序遭到攻击;而只有当WDC 正好在临界值时接收到喂狗操作,才表明程序运行过程符合其运行特征.

FSV 的作用是保证目标程序在该节点到下个节点之间执行的所有关键系统调用符合运行特征,可实时检测攻击的发生.但FSV 无法防御某些函数级复用攻击,例如,当攻击者复用程序本身具有的函数时,由于所有自身函数的入口和出口节点及其内部的其他节点都是对该函数自身行为的约束,因此当该函数完整执行时,FSV无法检测到该类复用攻击.而当结合BSV 之后,RCMon 则可对所有函数执行的合法性进行验证,此时,复用函数无法在攻击者设定的位置执行,即可实现对该类攻击的有效防御.

RCMon 的安全验证机制BSV 和FSV 可抽象为一个带输出的有限自动机模型M=(Q,Σ,Δ,δ,λ,q0),其中,

•Q={S0,S1,S2,S3,S4}为该模型的所有状态;

•Σ={get information of every critical system call,verification failed,verification successful,continue execution,exceed the critical value,below the critical value}为该模型的输入字母表;

•Δ={vmcall in critical node,kicking the dog}为该模型的输出字母表;

•δ为状态转移函数,δ:Q×Σ→Q;

•λ为输出函数,λ:Q→Δ;

•q0为初始状态,q0=S0.

模型中的S0表示经过插桩处理后的目标程序的正常执行状态;S1表示执行后向验证状态;S2表示执行前向验证状态;S3表示关键系统调用的执行状态;而S4则表示异常状态,即目标程序遭到攻击.

模型M对应的状态转移过程如图3 所示.当程序执行到定义的关键节点时,监控指令得到执行,当其中的vmcall 指令执行时使程序陷入到Hypervisor 中,并同时将节点信息(例如procID,fID以及NT)传递给Hypervisor,此时,状态机从S0进入S1,并在状态S1执行后向验证.若后向验证失败,则由状态S1直接进入状态S4,产生警告,中断目标程序的运行;否则,由状态S1进入状态S2,在S2中执行前向验证.只有当出现WDC 超过临界值或在喂狗操作提前到达时,才由状态S2进入状态S4,产生警告,并中断目标程序的运行;否则,由状态S2进入状态S0,目标程序继续运行.

Fig.3 Model of safety verification automaton图3 安全验证自动机模型

2 监控指令植入与关键系统调用监控方法

2.1 监控指令植入

向目标程序中植入监控指令,是构建程序运行特征库和检测程序异常行为的重要一环.根据模型RCMod,需要在目标程序中函数调用位置的前后以及所有本地函数的入口和出口处植入监控指令,如图2 所示.为了使RCMod 的实现不依赖于目标程序源码和编译器,且实现一次插桩、永久监控,RCMon 基于静态二进制插桩框架Dyninst[37]实现上述功能.

基于Dyninst 实现的监控代码植入算法如图4 所示.首先,通过Dyninst API 获取目标程序中所有能够进行插桩的函数信息,同时为每一个可插桩函数分配一个fID,用于标识不同函数;然后,在每一个函数区域内分别定位函数入口、出口和函数调用点;之后,构建4 类关键节点监控代码snippet;最后,分别将构造的监控代码植入到对应的关键节点并生成新的可执行程序.

植入的监控代码需要实现两方面功能:一是报告当前节点的标识信息,包括procID,fID和NT;二是在执行到关键节点时产生陷入.为了提高信息的传递效率,研究中采用寄存器作为传递信息的媒介,分别将procID,fID和NT保存到寄存器EAX、ECX 和EDX 中,然后,通过执行特权指令vmcall 实现陷入,Hypervisor 通过读取3 个寄存器获得当前关键节点的信息.为了避免在使用3 个寄存器时对它们内容的保存和恢复的复杂性,实现时,将监控指令置于独立的函数中实现.

4 类不同节点处调用的监控函数的具体定义如图5 所示,其中,参数functionID即图4 所示算法中为每个函数分配的fID,procID则设定为唯一标识该程序的某个整数值,NT直接根据对应的节点设置为1~4.最后,将上述监控函数封装在一个动态链接库(算法1 中的mylib.so)中,利用loadLibrary函数将该库加载到目标程序的内存空间,供目标程序调用.

通过上述处理后,最终生成一个新的含监控代码的可执行文件.该文件可完全脱离Dyninst 独立运行.监控代码植入后,程序控制流的变化情况如图6 所示(注:图6 是以funcX为本地函数为例进行说明的,当funcX为库函数时,则只有BC 和AC 节点,不存在BEN 和BEX 节点以及与user2vmmenter,user2vmmexit函数之间的控制流转移).可见,目标程序的执行过程已经完全处在RCMon 的监控之下.

Fig.4 Algorithm for instrumenting monitoring code图4 监控代码植入算法

Fig.5 Definitions of instrumented functions图5 植入函数的定义

Fig.6 Control flow before and after instrumenting code图6 代码植入前后的控制流

2.2 关键系统调用监控

实现RCMon 必须对系统调用进行监控,从而构建模型RCMod,实现对目标进程的运行时监控.Linux 操作系统中存在数百个系统调用,若对所有系统调用都进行监控,会导致较大的时间开销和空间开销(更多内存用于存储scca 数组和WDC).在保证监控强度不变的条件下,为了降低监控带来的开销,RCMon 选取了只与系统安全和各类攻击行为相关性较高的关键系统调用进行监控.

文献[38]为了实现对恶意软件行为的描述,通过对472 种Linux 下的恶意软件进行分析,总结提炼出了恶意软件经常使用的64 种系统调用;TASR[13]为了更好地防御内存泄漏,在总结分析各类攻击实现机制的基础上提出了最小攻击间隔理论,并将所有的输入类和输出类系统调用作为关键系统调用进行监控.RCMon 在借鉴已有研究成果的基础上,对各类系统调用进行了如下安全性分析.

(1)是否能够用于改变系统内某类资源(含硬件资源和文件、进程等软件资源)的状态;

(2)是否能够用于获取更高操作权限;

(3)是否能够用于查看或传输各类信息.

通过上述分析,RCMon 最终选择监控82 种(NSC=82)与安全相关的关键系统调用,见表1,分别对应于模型中的第0 种~第81 种系统调用.

Table 1 Critical system calls表1 关键系统调用

为了高效地监控上述关键系统调用,且不改变操作系统内核,RCMon 采用一种基于动态指令替换的监控方法实现该功能[35],即在系统运行过程中,通过底层的Hypervisor 修改内核代码内存,将要监控的关键系统调用对应的处理函数入口处(可通过事先分析操作系统的导出符号表system.map 得到各个关键系统调用处理函数的入口地址)的若干指令替换为vmcall 指令,使得关键系统调用执行时产生陷入,从而达到监控的目的.同时,还需要在Hypervisor 中对被vmcall 替换的那些指令进行模拟,保证系统调用的正常执行.

以Ubuntu12.04 使用的3.2.0-29-generic-pae i386 的内核为例,该内核代码中每个关键系统调用处理函数的入口处均为长度为3 个字节的“push %ebp;movl %esp,%ebp”两条指令,由于vmcall 指令的长度也为3 个字节,因此对于该发行版内核来说,只需将函数入口处的头3 个字节替换为vmcall 指令即可.

指令替换后,所有关键系统调用处理函数在执行时都会由于vmcall 指令而陷入Hypervisor,此时获取客户机指令寄存器的值并进行分析:若该值为某个关键系统调用处理函数的入口地址,则说明当前进程调用了该系统调用;否则,vmcall 指令来自于对目标程序关键节点的监控指令,此时执行运行特征库构建或安全验证.最后,在Hypervisor 中仿真执行被替换指令的功能.上述监控机制的实现过程如图7 所示.

Fig.7 Monitoring mechanism of critical system calls图7 关键系统调用监控机制

2.3 监控优化

由RCMon 的实现过程可知,在Hypervisor 中执行的安全验证过程是该方法最核心和最复杂的部分,同时也引入了较大开销,对这部分的优化,对于降低RCMon 的监控开销具有重要作用.当采用第2.1 节和第2.3 节中的方法完成对目标程序运行特征的构建后,通过分析获得的运行特征,发现存在以下特点:a)目标程序正常运行过程中往往不会包含对所有82 种关键系统调用的使用,而是存在某些未被使用的系统调用;b)相邻关键节点RCP 中的scca[i][0](0≤i≤Nsc-1)局部或全部相同(在两个关键节点间执行所有82 种关键系统调用的可能性很小).基于上述特点,提出两种对RCMon 运行时监控过程的优化方法以降低其监控开销.

(1)全局优化

对于目标程序正常运行过程中不会使用的关键系统调用,只在目标程序第1 个函数的入口节点处对这些关键系统调用对应的WDC 进行设置(全部设置为0),从而保证目标程序一旦执行这些系统调用即可导致WDC超过临界值而报警.而在程序其他节点处执行BSV 和FSV 时,则不再对这些系统调用对应的SCCA中的项进行对比验证,也不再重置其对应的WDC,即在程序入口处设置的WDC 将作用于程序的整个运行过程.目标程序未使用的系统调用可通过分析由训练阶段得到的运行特征获得,并通过运行特征文件告知Hypervisor 中的运行特征监控模块.

(2)局部优化

当存在相邻两个或多个关键节点中的scca[i][0](0≤i≤Nsc-1)完全一致的情况时,表明在这些关键节点之间未执行任何关键系统调用.此时,将这些连续的节点看作一个整体,仅在第1 个和最后一个节点处执行BSV 和FSV,中间节点则不再进行任何验证.为实现该项优化,首先在运行特征中找到每个符合条件的连续节点的起始节点和最终节点,然后反汇编插桩后的新可执行程序,并根据中间关键节点的fID和NT的值定位到在这些节点处植入的调用监控函数的代码,然后使用nop 指令将其替代,从而避免目标程序在这些节点处产生陷入.

通过上述处理,在未降低RCMon 方法安全性的条件下,减少了目标程序陷入Hypervisor 的次数,并降低了Hypervisor 中执行安全验证的复杂度,实现了对RCMon 性能的提升和优化.由第3.2 节中的测试可知,实施优化后,RCMon 的性能提升了约29%(即(31%-22%)/31%).

3 实验与结果分析

本节首先验证RCMon 方法的有效性,即能否有效防御代码复用攻击,然后再测试该方法的监控开销.实验环境如下:主机CPU 为Intel CoreTMi7-5500U CPU@2.40GHz;内存大小为4GB;客户机操作系统采用的是3.2.0-29-generic-pae i386 内核版本的Ubuntu12.04;Hypervisor 是基于Intel VT 技术设计的一个轻量级的虚拟机监控器,该监控器仅对无条件陷入事件[31]进行处理,目的是最大化地降低监控开销;另外,采用二进制文件插桩框架Dyninst-9.1.0[37]实现对监控指令的植入.

3.1 有效性测试

为了测试RCMon 的有效性,首先以一个具体实例exam 程序来验证方法的可行性.

exam 的源码如图8 所示,为了便于实现代码复用攻击,该程序func函数中含缓冲区溢出漏洞.首先编译得到可执行文件exam,然后经过代码植入模块植入监控指令并生成新的可执行程序exam_rcmon,并赋予该程序编号为128(可任意指定,能够区分不同的被保护程序即可),即将图5 中所示的全局变量procID初始化为128.然后经过训练过程,获得exam_rcmon 的运行特征库,由于内容较多,表2 仅列出了其部分运行特征.其中,(128,5,2,SCCA1)为程序exam_rcmon 中func函数的FEN 节点的RCP,(128,5,1,SCCA2)为func中对read函数调用的BC节点的RCP,(128,5,4,SCCA3)为func中对read函数调用的AC 节点的RCP,(128,5,3,SCCA4)为func的FEX 节点的RCP.

Fig.8 Source code of exam图8 exam 源码

Table 2 Running characteristics database of exam_rcmon表2 exam_rcmon 的运行特征库

在进行攻击测试时,通过内存泄漏分别找到在攻击中经常使用的libc库中的read、write和system函数的地址以及程序exam_rcmon 中func函数的地址,然后利用缓冲区溢出漏洞篡改控制流,分别对上述函数进行复用攻击.上述4 个测试的结果见表3.

根据测试结果,4 种代码复用攻击均以失败告终.攻击中,将func的ret 指令作为第1 个gadget 以实现向其他gadget 的转移,因此直到func的FEX 节点之前都是正常的.而在该节点后,由于对ret 指令的目标地址进行了篡改,分别调用了read,write,system和func函数这4 类指令序列.当执行write、read和system函数时,会分别调用sys_write、sys_read和sys_execve系统调用,这3 类系统调用常用于各类攻击中.但由于根据(128,5,3,SCCA4)设置的3 类系统调用对应的WDC 的临界值均为0,因此当复用上述函数时,WDC 超过临界值,导致FSV 失败.当复用func函数时,由于func函数中会调用read函数,此时scca[0][0]增加1 变为8,但特征库中并没有符合scca[0][0]=8 且procID、fID和NT分别为128、5 和1 的RCP 存在,因此BSV 失败.另外,测试过程中没有发现任何误报.通过上述测试,验证了RCMon 方法检测代码复用攻击的可行性.同时,借助RCMon 还可以对攻击的发生位置进行定位,这有助于攻击发生后的软件安全漏洞和攻击实现机制的分析.

Table 3Results of attacking tests表3 攻击测试结果

为了更好地验证RCMon 的有效性,除上述测试外,我们还对4 类真实的应用程序(nginx,proftpd,mcrypt 以及TORQUE)进行了测试.测试中,分别采用来自http://www.elis.ugent.be/~svolckae 的4 种不同的ROP 对上述程序实施攻击.

•攻击1 针对Web 服务器nginx,它首先读取栈中的canary 值以及漏洞函数栈帧底部的返回地址,利用该地址计算出nginx 的基地址,然后基于对nginx 的已有知识构造ROP 链,并利用nginx 中的栈缓冲区溢出漏洞(CVE-2013-2028)使ROP 链获得执行.

•攻击2 针对ftp 服务器proftpd,它首先通过扫描proftpd 的可执行文件和libc 库定位构造ROP 链所需要的gadget,然后从/proc/pid/maps 中读取proftpd 和libc 的加载地址来确定gadget 的绝对地址,最后通过一个未授权的FTP 链接将含有ROP 链的buffer 发送到proftpd,并利用proftpd 中的栈缓冲区溢出漏洞(CVE-2010-4221)使其得到执行.

•攻击3 针对mcrypt(一个用于替换crypt 的加密程序),它首先从/proc 接口获得mcrypt 和libc 库的加载地址来构造ROP 链,然后通过一个pipe 将ROP 链发送给mcrypt,并利用mcrypt 中的栈缓冲区溢出漏洞(CVE-2012-4409)执行该ROP 链.

•攻击4 针对TORQUE 资源管理器服务器,它首先读取pbs_server 进程的加载地址并构造ROP 链,然后通过一个未授权的网络连接将该ROP 链发送到TORQUE,并利用TORQUE 中的栈缓冲区溢出漏洞(CVE-2014-0749)使攻击得到执行.

测试前,首先分别处理各目标程序(植入监控代码),然后通过将处理后的程序在不同输入和操作条件下分别运行20 次、40 次、60 次和80 次(每次运行30min)来构造程序运行特征库.然后,在关闭和开启RCMon 特征监控两种情况下,分别使用上述4 个ROP 攻击对目标程序进行攻击.根据攻击是否成功,验证RCMon 防御代码复用攻击的有效性.需要注意的是,由于植入监控代码的影响,原有的4 个攻击在构造ROP 链时所使用的地址都需要更新为代码植入后的新地址.最后,再在开启RCMon 特征监控的情况下运行各目标程序20 次(每次仍然运行30min),以测试是否存在误报.最终的测试结果见表4.

Table 4Results of real applications tests表4 真实应用测试结果

由表4 可知,RCMon 能够有效防御针对nginx 等应用程序的4 种ROP 攻击,无漏报;但RCMon 存在误报,尤其是nginx 误报情况较为明显.主要原因在于,该类程序运行特征的影响因素较多,不同服务状态和客户链接数量都会对服务器的执行过程产生影响.另外,从测试结果可以发现,在训练阶段的训练越充分,RCMon 产生误报的可能性就越小.

另外,本文总结了当前已知的各类代码复用攻击技术,并根据其实现机制判断RCMon 是否能够有效阻止这些攻击.分析过程中均假设这些攻击中含有对某些关键系统调用的执行过程或者这些攻击的实施改变了程序原有的关键系统调用执行情况,而这个假设对大多数攻击都是适用的[13,38].对于函数级复用攻击来说,不含任何关键系统调用的情况极少,另外,即使对于本身不含任何关键系统调用的指令片段复用攻击,在gadget 跳转串联过程中也极有可能会干预原有的系统调用执行过程,如跳过某些执行关键系统调用的指令等.最终的分析结果见表5.通过上述测试和分析可知,RCMon 可实现对已知各类代码复用攻击的有效防御.同时,还在表5 中将RCMon 与其他相关防御方法进行了防御能力的对比,对比结果进一步表明了RCMon 的有效性.

Table 5 Analysis of RCMon’s defensive ability表5 RCMond 的防御能力分析

3.2 性能测试

为了测试RCMon 对整个系统性能的影响,本文采用SPEC CPU 2006 的CFP 2006 基准测试集中的部分测试程序对系统开销进行了测试.鉴于RCMon 基于Hypervisor 实现,本文分别测试了裸机、Hypervisor、Hypervisor下开启关键系统调用监控、测试程序部署未优化的RCMon 以及部署优化后的RCMon 这5 种条件下的性能开销,测试结果如图9 所示.

Fig.9 Results of performance overhead measurement图9 性能开销测试结果

由测试结果可知,本文所设计实现的Hypervisor 由于采用了硬件辅助虚拟化技术Intel VT,并且尽可能地减少不必要的监控,使得该监控器在不监控内核函数时开销很小.当增加对关键系统调用的监控后,由于所有对关键系统调用的执行都会产生陷入,并在陷入后执行对当前关键系统调用执行状态的更新和对函数入口原有的两条指令的模拟,因此开销略有增加.在对测试程序部署RCMon 之后,由于不仅要监控系统中的所有关键系统调用,还要监控程序执行到每个关键节点处的陷入,并在陷入后根据当前节点信息到运行特征库中查找是否存在匹配项,当存在匹配项时,还要根据运行特征设置WDC 对关键系统调用进行实时监控,因此,RCMon 引入后,测试程序的运行开销增加较多.与裸机(Linux 纯净系统)相比,当采用未优化的RCMon 时,平均开销约为31%(其中,434.zeusmp 开销最小,为6%;而465.tonto 开销最大,为58%);当采用优化后的RCMon 时,平均开销降至22%,这也进一步验证了第2.3 节中优化方法的有效性.

RCMon 的开销小于传统的内存安全保护方法(例如,SoftBound[24]的平均开销为 67%,Baggy Bounds Checking[25]的平均开销为60%)、数据流完整性保护方法DFI[30](平均开销高达104%)以及控制流完整性方法CCFI[15](平均开销52%)等多类防御方法,略高于CFI[14](平均开销16%)以及ASLR[6](的平均开销为10%)等方法,但RCMon 除了能够有效防御代码复用攻击以外,对所有含有系统调用恶意执行的攻击都具有一定的防御能力,防御的攻击类型更广,能够比这些防御方法提供更高的安全性.

4 RCMon 的局限性

由于RCMon 不针对某个或某些具有特定特征的代码复用攻击类型进行防御,而是基于程序的系统调用运行特征防御代码复用攻击,是基于攻击的某种共性(即攻击者在实施恶意操作时难免要通过关键系统调用和操作系统进行交互)实施的检测和防御,因此无论是已知的还是未知的代码复用攻击,只要其实现过程包含对关键系统调用的使用或者其实现过程对原程序的关键系统调用执行过程造成了影响(即破坏了原程序的运行特征),RCMon 就能检测和防御.但RCMon 仍然存在一些局限性.

(1)如果一个代码复用攻击不使用任何关键系统调用且攻击实施过程不对原程序的关键系统调用执行过程产生任何影响,则可以绕过RCMon 的检测,从而产生漏报.但这种情况下,代码复用攻击的发挥空间是非常有限的.首先,不借助任何关键系统调用的攻击所能实施的操作非常有限;另外,代码复用攻击的核心仍是对程序控制流的改变,而控制流改变的情况下要保证原程序关键系统调用执行过程不发生任何改变,这也使攻击变得更加难以实现.可见,虽然RCMon 存在漏报某些攻击的可能,但该类攻击的危害性和实现的可能性都非常小,对RCMon 的整体安全性的影响很小.而在第3.1 节的测试中未发现任何漏报,这也在一定层度上验证了上述分析.

(2)当在训练阶段构造的目标程序运行特征不够完备时,RCMon 可能会产生误报.由表4 的测试结果可知,通过不断加大训练阶段的训练量,能够在较大程度上降低RCMon 误报的可能.但这种方法效率低,而且存在很多不确定性.要在最大程度上消除误报,必须保证在训练阶段能够遍历目标程序所有的执行路径来构造完备的程序运行特征.拟在下一步工作中,利用动态符号执行技术来解决这一问题.动态符号执行能实现对程序的高路径覆盖,它以某个具体的输入来执行目标程序,并获取当前路径中的所有路径约束条件;然后通过对这些约束条件的修改,生成一条新的路径的约束条件,并用约束求解器获得一个新的输入;然后再以该输入执行目标程序.通过反复执行上述过程,即可实现对被测试对象所有路径的动态遍历.随着训练过程中的路径覆盖率的提高,就可构建更加完备的程序运行特征,从而消除误报.

5 总结

针对当前代码复用攻击防御方法中存在的防御类型单一等问题,本文提出一种基于程序运行特征监控的代码复用攻击防御方法RCMon.该方法首先定义并构造了程序的运行特征模型RCMod(该模型包含了在程序中的函数调用位置前后和函数出入口这4 类关键节点处的关键系统调用的运行特征信息),并基于该模型向目标程序的关键节点处植入监控代码,以实现基于Hypervisor 的对目标程序运行过程的监控;然后,通过训练过程得到目标程序的运行特征库;最后,在实际运行过程中监控程序运行特征是否与事先得到的运行特征库中对应特征一致,来判断程序是否遭到代码复用攻击,从而实现对代码复用攻击的有效防御.

RCMon 的验证过程包含基于特征库匹配的后向安全验证BSV 和基于WDC 的前向安全验证FSV,分别实现了阶段性的安全验证和一个阶段内的实时安全验证,能有效防御函数级和指令级代码复用攻击,加强了目标程序的安全性.测试结果表明,RCMon 能够有效防御各类含关键系统调用执行过程的代码复用攻击,且实现过程不需要源码,实用性较好.另外,方法的平均性能开销也在可接受的范围内,约为22%.

猜你喜欢
调用指令代码
基于 Verilog HDL 的多周期 CPU 设计与实现
《单一形状固定循环指令G90车外圆仿真》教案设计
核电项目物项调用管理的应用研究
系统虚拟化环境下客户机系统调用信息捕获与分析①
关于ARM+FPGA组建PLC高速指令控制器的研究
神秘的代码
一周机构净增(减)仓股前20名
一行代码玩完19亿元卫星
近期连续上涨7天以上的股
MAC指令推动制冷剂行业发展