一种基于ROP技术的代码混淆方法

2019-09-13 03:38巩道福刘粉林
计算机应用与软件 2019年9期
关键词:等价攻击者代码

向 飞 巩道福 刘粉林

(信息工程大学 河南 郑州 450000)(数学工程与先进计算国家重点实验室 河南 郑州 450000)

0 引 言

随着软件产业的迅猛发展,软件在发挥巨大社会和经济效益的同时,安全问题也随之产生。通过对商业软件进行逆向分析和破解,进而盗用软件或剽窃核心算法,将对软件的知识产权造成严重侵害,破坏市场的公平正义。因此,软件安全已经成为软件产业界的研究热点。Collberg等[1]最早提出代码混淆的软件保护技术,通过对程序代码实施混淆变换,在保持程序语义功能等价的基础上,使其形式变得更加复杂和难以理解,能够抵抗静动态逆向分析,提高软件破解的时间成本,进而增强软件安全性能[1]。

攻击者对目标程序进行逆向分析时,通常重点关注实现特定功能的核心代码。通过静态反汇编或动态跟踪执行,能够获取和分析核心代码,进而理解程序功能或实施篡改破解。因此,研究保护程序核心代码抵抗攻击者逆向分析,对于保护程序安全具有重要意义。Kulkarni等[2]提出将程序重要代码进行切片,针对每个代码片段克隆多个等价片段且生成多条随机执行路径,增加代码执行路径的随机多样性,使得攻击者难以恢复和分析原始代码。Xie等[3]提出针对程序代码设计重叠指令和嵌入跳转指令,函数执行过程跳转到重叠指令中间执行,能够降低代码反汇编的准确程度。Behera等[4]提出针对函数代码进行篡改并嵌入自修改指令,程序执行时动态恢复执行原始代码,能有效扰乱静态反汇编。陈喆等[5]利用随机森林分类的思想混淆程序重要代码的路径信息,将分析路径分支的难度转化为提取随机森林规则的难度。苏庆等[6]提出基于混沌映射和二次映射的混沌不透明表达式构造方法,进而构造不透明谓词插入到重要代码的混淆,能够明显提升软件代码的复杂度。Falcarin等[7]提出将程序重要代码放置于攻击者不可控的远程可信实体,利用网络数据的流动性使程序执行过程动态获取代码,减少攻击者对程序代码的可见度。上述方法实现了不同角度的程序代码保护,然而保护后代码依然存在于目标程序,容易被逆向分析获取,或是通过网络传输执行代码严重依赖于网络环境,可用性大打折扣。

针对传统的直接在进程栈空间注入可执行代码的缓冲区溢出漏洞攻击方式,操作系统实现诸多防御机制,如:通过修改可执行文件的内存布局使得堆栈不可执行;通过硬件支持使得进程映像的内存空间不能同时可写入和可执行;通过设置不可执行页限制程序生成可执行代码;通过进程地址空间布局随机化技术(ASLR)使堆栈首地址随机化等。因此攻击者又提出新型的代码复用攻击方式,不再直接向进程栈空间注入可执行代码,而是充分利用内存空间已有的代码进行执行,主要有RIL(Return-into-libc)技术和ROP(Return-oriented-programming)技术等。RIL技术直接复用内存空间的库函数,通过连续调用特定的库函数可以实现复杂的攻击。ROP技术是RIL技术的提升,其以内存空间更细粒度的指令片段为复用对象,能够实现任意图灵完备的攻击。具体来说,ROP技术通过在栈空间预设返回地址。使得目标指令片段结尾处的返回指令(RET指令)直接返回到指定内存地址继续执行下一个指令片段,进而实现指令片段的动态连续执行。两种代码复用攻击技术执行的都是进程内存空间正常的代码,因此不会被系统检测出异常,能够突破现有的安全防御机制。

由于ROP技术的代码组织方式和调用过程跟传统的代码顺序执行有很大的不同,能够以较为隐蔽的方式驱动代码执行,使得ROP技术在代码保护方面能够发挥一定的作用。已有相关研究将ROP技术应用于代码保护,实现较好的保护效果。Ma等[8]实现了基于ROP的动态软件水印技术,通过秘密输入触发执行精心设计的ROP指令片段,实现目标水印功能。Mu等[9]实现了混淆工具ROPOB,将函数基本块转化为ROP指令片段,通过ROP返回指令将直接控制转移转化为间接控制转移,能够抵抗攻击者静态分析函数控制流。ROPOB主要针对基本块间的控制转移指令进行混淆,没有考虑基本块内部的普通指令。Lu等[10]实现了混淆系统RopSteg,将目标指令代码转化为隐蔽的ROP指令片段,然后构造ROP生成器实现动态计算ROP指令片段地址和返回地址。同时构造ROP跳板实现将ROP返回地址压栈再转到ROP指令片段执行,能够有效隐藏目标指令代码,抵抗静态逆向分析。RopSteg主要使用单个等价的ROP指令片段进行混淆,没有充分结合ROP技术在攻击场景的应用特性,实现多个ROP指令片段组合执行。

本文提出一种新的基于ROP技术的代码混淆方法,用以保护程序内部核心代码避免暴露给逆向攻击过程。利用程序内存空间随机分布的若干gadget指令片段的动态组合执行,实现目标代码的等价功能,进而实现隐藏目标代码的目的,抵抗攻击者通过静动态手段获取和分析目标代码功能。

1 ROP技术

程序执行过程中,每个函数调用具有独立的栈帧结构,其内存空间布局如图1所示。缓冲区溢出漏洞的产生是因为在函数内部针对拷贝给局部变量的输入字符串缺少安全检查,使得攻击者能够通过外部输入注入二进制可执行代码到栈空间而且把函数返回地址覆盖为可执行代码地址,导致函数执行完毕返回到攻击者注入的代码进行执行,进而实现恶意攻击目的。

图1 函数栈帧结构的内存布局

ROP攻击技术不直接向函数栈空间注入可执行代码,而是另辟蹊径,充分利用内存空间共享库代码中以ret指令结尾的指令片段(称为gadget)。ret指令的功能是弹出栈顶的4字节数据并赋给指令指针寄存器EIP作为下一条执行指令的地址。ROP技术将若干gadget的地址和数据按照固定顺序注入到函数栈空间,通过执行前一个gadget结尾的ret指令可跳转至栈中设定的后一个gadget继续执行,进而实现gadget的动态连续调用,不动声色地达到攻击目的。下面举例说明ROP技术的实现过程,图2为实现0x10与0x20的加法操作。

图2 ROP技术示例

图2左侧为ROP技术利用缓冲区溢出漏洞向函数栈空间注入的数据,其中gadget1、gadget2和gadget3分别为内存空间共享库代码的3个gadget的地址,gadget1所在位置指向函数返回地址。图2中间分别为地址gadget1、gadget2和gadget3指向的gadget指令片段。实施ROP攻击后的程序执行过程为:函数执行完毕返回到gadget1执行,pop eax指令取出0x10到eax寄存器,ret指令取出gadget2作为返回地址执行;然后pop ebx指令取出0x20到ebx寄存器,ret指令取出gadget3作为返回地址执行,add eax,ebx指令实现0x10与0x20相加;结果保存到eax寄存器,ret指令退出当前执行过程。图2通过精心构造数据注入到函数栈空间,实现3个gadget的连续调用执行,最终实现0x10与0x20的加法操作。相比传统的直接注入可执行代码的攻击方式,ROP技术通过复用程序内存空间代码,不仅减少了注入的数据量,而且提高了漏洞攻击的隐蔽性,能够绕过操作系统的安全防御机制。考虑到ROP技术在驱动大量代码片段连续执行等方面具有独特的实现方式和较好的隐蔽效果,因此本文将ROP技术应用到程序核心代码的混淆,进而提出新的基于ROP技术的代码混淆方法。

2 方法描述

借鉴ROP攻击技术的代码复用思想,本文提出一种基于ROP技术的代码混淆方法,具体的流程框架如图3所示。方法提取目标程序基本块内部需要保护的核心代码,利用内存空间实现等价功能的gadget指令序列予以混淆替换,进而达到隐藏核心代码和抵抗静动态分析的目的。其中gadget指令序列通过搜索系统共享函数库或等价指令变换构造等方式进行获取。

图3 基于ROP技术的代码混淆方法流程框架

首先定义指令代码的完全语义等价和条件语义等价。

定义1(完全语义等价):设目标指令I,指令序列IS={I1,I2,…,In},若IS能够实现I的等价功能,则称IS与I完全语义等价,记作I≌IS。如针对目标指令mov eax,1,执行push edx, mov edx,1, mov eax,edx, pop edx能够实现相同的语义功能,则有mov eax,1≌{push edx, mov edx,1, mov eax,edx, pop edx}。

定义2(条件语义等价):设目标指令I,指令序列IS={I1,I2,…,In},数据集DS={d1,d2,…,ds}。动态执行时,DS预先存储在栈空间,d1到ds自栈顶开始向栈底方向连续分布,若IS通过调用DS能够实现I的等价功能,则称IS与I条件语义等价,记作I∽IS@DS。如针对目标指令mov eax,1,栈顶数据为1时,执行pop eax能够实现相同的语义功能,则有mov eax,1∽{pop eax}@{1}。

混淆方法通过调用完全语义等价或条件语义等价的指令序列实现核心代码的等价功能,下面描述混淆实现过程。

图4 程序执行路径

算法1ROP混淆实现过程算法

1.functionRopObf (K)

3.forifromnto1do

4.forjfrommito1do

8.endfor

9.endif

11.endfor

12.endfor

13. InsertInstruction(ret)

14.endfunction

混淆后程序执行路径如图5所示。

图5 混淆后程序执行路径

经过前述混淆过程,指令代码K混淆成连续调用执行的gadget指令序列。为了增强混淆效果,针对指令代码K的每条指令Ii,同时选取ri组实现等价功能的gadget进行混淆,程序动态执行时随机选择一组gadget执行,进而实现程序执行路径和执行代码的随机多样化,增加逆向分析的难度。随机多样化混淆后的程序执行效果如图6所示。

图6 随机多样化混淆效果

3 gadget指令序列获取

混淆方法利用程序内存空间的gadget指令序列替换实现原始核心代码的等价功能。提出两种方法获取混淆所需要的gadget:一种是在目标程序代码或所调用的系统共享函数库代码中,搜索以ret指令结尾的gadget;另一种是有针对性的构造以ret指令结尾的gadget,嵌入到目标程序或自定义共享函数库。下面分别介绍两种gadget获取方法。

3.1 搜索gadget

程序执行时,在内存空间驻留的大量的可执行代码,包括程序自身代码以及系统共享函数库代码,都可以被直接调用执行。共享函数库通常在程序运行时加载到内存空间,提供函数给程序调用,如Windows操作系统的kernel32.dll、user32.dll等动态链接库。共享函数库含有大量丰富的代码,能够用来提取混淆需要的gadget。因为目标程序或共享函数库的代码段具有可执行权限,故而指定在代码段搜索gadget。下面给出逆序搜索获取gadget的算法。

在代码段定位所有的ret指令,ret指令对应的十六进制字节是0xC3,因此直接搜索全部字节0xC3。从每个字节0xC3的位置开始,逆向搜索若干字节,若是有效的机器指令则进行记录,然后从当前位置继续逆向搜索若干字节,如此循环往复,直到不能得到有效的机器指令,或者搜索深度长度超过指定阈值。将按上述过程搜索得到的指令组织成以ret指令为根节点的指令序列树。每个节点都是一条指令,每个子节点指令是父节点指令前面紧邻的指令,每个父节点指令是子节点指令后面紧邻的指令。每个节点可能具有多个子节点,因为对于一条指令前面紧邻的字节,不同长度的字节可能翻译成不同的指令。每个非根节点到根节点的路径经过的所有指令按顺序串联起来即构成一个gadget指令序列。

设ret指令对应的十六进制字节为b0,以b0结尾的字节流为BS=(…b2b1b0),采用递归算法构造指令序列树T,初始化T的根节点为ret指令。定义递归函数GenerateTree(Instruction ins, Index pos, Short depth),参数pos是指令ins的首个字节bpos的索引编号,depth是指令ins位于指令序列树T的深度,设根节点的深度为0。函数实现的功能是判断若指令ins的深度depth小于或等于阈值threshold,则把位于指令ins前面的字节流(…bpos-2bpos-1)构造成以ins指令为根节点的指令序列树,作为T的子树,否则直接退出。输入二进制字节流BS,构造指令序列树T的伪代码如算法2所示。

算法2指令序列树构造算法

INPUT: (…b2b1b0) whereb0is disassembled into ret

OUTPUT:T

1. let ret be the root node of T

2. GenerateTree(ret, 0, 0)

3.functionGenerateTree(Instruction ins, Index pos, Short depth)

4.ifdepth≤thresholdthen

5.forstepfrom1tomax_lendo

6.if(bpos-step…bpos-1) can be disassembled into a valid instruction childinsthen

7. letchildinsbe the child ofinsinT

8. GenerateTree(childins,pos-step,depth+1)

9.endif

10.endfor

11.endif

12.endfunction

指令序列树T构造完成后,把每个非根节点(包括叶子结点和内部节点)到根节点的路径所经过的所有节点指令按顺序串联起来,即得到以ret指令结尾的gadget指令序列。T的叶子结点和内部节点的数量等于在字节流BS中提取到的gadget的数量。

图7为指令序列树T,设I1=(b2b1),I2=(b3b2b1),I3=(b5b4b3),I4=(b5b4),I5=(b6b5b4),I6=(b7b6),I7=(b7b6),可以提取7个gadget指令序列,分别为{I1,ret},{I2,ret},{I3,I1,ret},{I4,I2,ret},{I5,I2,ret},{I6,I3,I1,ret},{I7,I4,I2,ret}。

3.2 构造gadget

虽然通过搜索能够获取到丰富多样的gadget,但是难以保证混淆过程需要的gadget都能匹配到,因此提出采用构造的方法获取gadget。通过对目标指令进行等价变换得到完全语义等价或条件语义等价的指令序列,在末尾添加ret指令即生成gadget,然后嵌入到目标程序的空闲空间或新增区块,或者嵌入到自定义共享函数库的导出函数内部。程序运行时加载到内存空间的gadget能够被调用执行。

针对目标指令直接应用等价指令变换能够生成完全语义等价的指令序列。生成条件语义等价的指令序列的步骤如下:提取目标指令的常量操作数并假定常量操作数已经存储在栈顶空间;构造pop指令依次提取栈顶的常量操作数保存到寄存器;构造指令使用寄存器操作数替换常量操作数实现目标指令的等价功能;应用等价指令变换即生成条件语义等价的指令序列,如表1所示。

表1 目标指令的等价指令序列

下面给出几种等价指令变换方法,如表2所示。

表2 等价指令变换方法示例

等价指令替换:使用单条指令实现目标指令等价的语义功能。

指令寄存器替换:针对目标指令的通用寄存器进行替换而不影响语义功能,进而变换出等价指令序列。

垃圾指令插入:在目标指令的前后位置分别插入若干垃圾指令而不影响语义功能,进而变换出等价指令序列。

指令位置变换:若目标指令序列存在若干指令没有执行依赖关系,则变换指令前后位置不会改变指令序列执行功能,进而变换出多样化的等价指令序列。

通过应用多样化的等价指令变换方法进行迭代变换,能够生成更加复杂的完全语义等价或条件语义等价的指令序列,增加逆向分析的难度。算法3给出针对单条指令的基于迭代的等价指令变换算法实现过程的伪代码。其中:Match(I,R)表示根据等价指令变换规则R匹配指令I的等价指令序列;Replace(EIS,I, Match(I,R))表示把指令序列EIS的指令I替换成匹配到的等价指令序列。

算法3基于迭代的等价指令变换算法

INPUT: Target instructionI, equivalent instruction transformation ruleR, iteration depth thresholdD

OUTPUT: Equivalent instruction sequenceEIS

1.functionTransform(I,R,D)

2.EIS={I}

3.ifD==0orMatch(I,R)==NULLthen

4.returnEIS

5.elsethen

6.EIS=Repalce(EIS,I, Match(I,R))

7.foreachiinEISdo

8.EIS=Replace(EIS,i, Transform(i,R,D-1))

9.endfor

10.endif

11.returnEIS

12.endfunction

图8 隐蔽嵌入gadget效果

4 方法分析

本文提出的基于ROP技术的代码混淆方法,能够在一定程度上保护程序内部代码避免暴露给逆向工程。下面分别从有效性、空间开销和时间开销三个方面分析和评价方法的性能。

4.1 有效性

攻击者通过静态分析能够轻易获取程序内部核心代码,通过动态分析能够跟踪核心代码的执行功能。混淆方法利用ROP技术复用内存空间的指令代码,能够有效隐藏程序内部原始核心代码,导致攻击者难以通过静态分析判断核心代码存在及获取核心代码。

通过使用多样化的等价指令变换方法针对目标指令进行迭代变换,能够生成形式多样、长度膨胀、执行复杂的等价指令序列,增加攻击者分析代码的难度,同时通过隐蔽嵌入gadget指令序列,能够有效抵抗静态反汇编分析。

程序动态执行时,混淆指令序列随机分布在程序内存空间的不同位置,使得程序执行轨迹在内存空间不断跳变,执行过程的上下文环境不断变化,能够增加攻击者动态跟踪分析的难度。

原始核心代码K只有唯一执行路径,而经过随机多样化混淆后,代码执行路径数P如下:

程序每次动态执行的路径动态随机变化,攻击者想要分析代码的执行功能,需要把全部的P条执行路径分析清楚,因此随机多样化的混淆策略能够进一步增加逆向分析的难度和时间成本。

综上分析,混淆方法能够有效隐藏和保护程序内部核心代码,抵抗多种特定的逆向分析手段,在静态和动态两方面能够发挥较好的保护作用。

4.2 空间和时间开销

混淆后程序相比原始程序增加数据和代码,使得程序的空间开销和执行时间开销相应增大。混淆过程给目标程序增加的空间开销主要包括如下几部分:首先是通过等价变换生成的gadget指令代码,混淆阶段嵌入到程序内部或自定义共享函数库;其次是连续的压栈操作代码,实现gadget指令序列地址及数据依次压栈;最后是实现加载函数库以及获取内存地址等功能的辅助代码。设各部分混淆代码的大小分别为S1、S2、S3,则混淆后程序增长的空间开销约为:

S=S1+S2+S3

混淆过程给目标程序增加的时间开销主要是代码S1、S2、S3的执行引起的。设混淆代码S1、S2、S3的平均执行时间分别为T1、T2、T3,则混淆后程序增长的时间开销约为:

T=T1+T2+T3

根据混淆过程可知,混淆增加的空间开销应主要由构造嵌入的gadget指令代码所影响,在处理器运算速度极快的条件下,混淆代码的执行不会增加过多时间开销。可以推算,混淆不会给程序造成严重的空间和时间开销,应具有可接受的开销性能,后续通过实验进一步验证和分析。

5 实验分析

本节通过实验验证方法的有效性并评价方法的性能。实验环境为32位Windows 7操作系统,3.10 GHz Intel Core 5处理器以及4 GB内存。实验首先选取5款常用的系统共享函数库,在代码段递归搜索以ret指令结尾的gadget指令序列,分别设置搜索深度为1到4,记录搜索到的gadget数量,如表3所示。可以看到,系统共享函数库包含大量的gadget,混淆过程可以根据需要随机选择可用的gadget进行混淆。为了保证总能获得混淆需要的gadget,实验同时通过等价变换构造大量实现目标功能的gadget。

表3 gadget指令序列搜索结果

实验选取6款测试程序进行混淆,分别为在VS2010环境下编译生成的3款Debug版本程序,包括冒泡排序程序Bubble.exe、汉诺塔程序Hanio.exe和八皇后程序Queue.exe,以及Windows7操作系统下的3款轻量级系统应用程序,包括计算器程序Calc.exe、网络测试程序Ping.exe和记事本程序Notepad.exe。实验选取测试程序的部分功能代码进行混淆,例如选取的冒泡排序程序代码以及针对其中两条指令的混淆示例。如图9所示,通过在栈空间预设指令地址和数据,实现gadget指令序列的连续调用执行。

图9 冒泡排序程序混淆示例

混淆实验结果如表4所示,分别列出混淆的目标代码数,混淆前后程序体积大小及混淆后程序的执行情况,其中冒泡排序程序输入数组规模为2 000(将从大到小排列的数组通过冒泡排序排列成从小到大排列的数组),汉诺塔程序输入规模为24,设置八皇后程序棋盘大小为12×12。混淆后程序经过反复测试执行,均能实现原始程序的正常功能,证明混淆方法具有可行性,能够保持程序语义功能等价。

表4 混淆前后程序的执行和开销情况

在时间开销方面,记录混淆前后的冒泡排序、汉诺塔和八皇后程序的精确执行时间以及另外三个程序的执行时间变化情况。实验结果显示冒泡排序、汉诺塔和八皇后程序的执行时间增长都非常小,另外三个程序正常执行过程均没有明显时间变化,说明混淆方法没有带来巨大时间开销,具有较好的时间开销性能。在空间开销方面,混淆后程序体积适当增长,而且随着混淆的目标代码数量越多,程序体积增长越大。然而程序载体通常具有足够的存储和处理资源,适当的体积增长基本不会影响程序的存储、加载和运行等过程,因此混淆方法依然具有较好的空间开销性能。

由于没有统一的混淆强度度量标准,因此本文根据混淆方法的实际保护效果,提出适用本文方法的混淆强度指标并结合实验结果进行分析。首先混淆后程序的每个核心代码块的执行变成很多个gadget代码块的等价执行,与混淆前攻击者相比,需要分析数量更多的代码块以及更加复杂的控制转移关系,因此可以使用混淆代码块数量比例RB度量混淆方法在代码块层面的保护强度。设NKB表示混淆的原始程序核心代码块的数量,NGB表示混淆后程序gadget代码块的数量。RB的计算如下:

RB=NGB/NKB

混淆使得攻击者需要分析的指令数量大大增加,因此可以使用指令数量比例RI度量混淆方法在指令层面的保护强度。设NKI表示原始程序核心代码的指令数量,NGI表示混淆后程序gadget指令序列的指令数量。RI的计算如下:

RI=NGI/NKI

混淆所调用的gadget指令序列的二进制字节序列可能隐藏在正常指令代码的字节序列内部,使得静态反汇编获取不到实际执行的gadget指令序列,进而达到迷惑和干扰反汇编的效果。因此可以使用正确反汇编的gadget指令数量比例RD度量混淆方法抵抗静态反汇编的保护强度。设NDGI表示能够正确反汇编的gadget指令数量。RD的计算如下:

RD=NDGI/NGI

根据RB、RI、RD的定义可知,RB和RI越大,攻击者需要分析的代码块和指令数量相对越多,需要分析gadget之间的控制转移关系越复杂;RD越小,攻击者静态反汇编能够正确获取的指令数量相对越少。实验分别统计6款测试程序混淆前后的NKB、NGB、NKI、NGI、NDGI值,计算相应的RB、RI、RD值,结果如表5所示。图10更直观地对比展示了混淆前后的统计结果。

表5 混淆前后程序相关数据

(a)

(b)图10 混淆前后代码块数量和指令数量对比

由表5的实验结果可以看到,RB的取值范围为14.4~18.4,平均值为16.9,意味着攻击者需要分析的混淆后代码块数量是混淆前的16.9倍。RI的取值范围为5.36~9.45,平均值为7.09,意味着攻击者需要分析的混淆后指令数量是混淆前的7.09倍。RD的取值范围为0.42~0.63,平均值为0.54,意味着通过静态反汇编只能正确得到54%的混淆后gadget指令。实验结果证明,混淆方法能够有效增加静动态获取和分析核心代码的难度,在一定程度实现代码隐藏的保护效果。

6 结 语

本文针对程序核心代码容易暴露给逆向攻击的问题,提出一种基于ROP技术的代码混淆方法。方法借鉴ROP攻击技术的代码组织和调用方式,通过在栈空间预设指令地址和数据,利用程序内存空间随机分布的gadget指令序列动态组合执行,实现目标代码的等价功能。同时方法采取搜索和构造两种方法获取混淆所需要的gadget指令序列,进一步实现执行代码和路径的随机多样化。实验和分析证明,方法能够有效增加攻击者获取和分析核心代码的难度,在静态和动态两方面实现较好的保护效果,具有较好的时间和空间开销性能。

猜你喜欢
等价攻击者代码
基于贝叶斯博弈的防御资源调配模型研究
等价转化
正面迎接批判
正面迎接批判
n次自然数幂和的一个等价无穷大
神秘的代码
一周机构净增(减)仓股前20名
一行代码玩完19亿元卫星
将问题等价转化一下再解答
近期连续上涨7天以上的股