(1997-), 男, 博士生, 主要研究领域为网络安全
(1974-), 男, 博士, 教授, 博士生导师,主要研究领域为网络安全
(1969-), 男, 博士, 正高级工程师, 博士生导师, CCF高级会员, 主要研究领域为计算机系统安全
(1985-), 男, 博士, 副教授, 主要研究领域为网络信息安全
(1972-), 女, 教授, 主要研究领域为网络安全机制分析
控制流是程序过程的抽象表现, 对控制流进行混淆, 可有效提高代码抗逆向能力. 提出了控制流深度模糊思想:针对循环结构, 利用回调函数构造等价循环模型, 将过程内基本块跳转变更为过程间函数调用, 对抗逆向技术. 综合应用控制流分析和数据流依赖性分析, 建立了基于回调函数的控制流深度模糊模型, 并给出功能一致性证明. 为进一步增大混淆强度, 设计并实现了函数调用融合算法, 构造更为复杂的函数调用过程. 最后, 使用OpenSSL和SpecInt-2000标准测试套件作为测试集, 验证了模型的可行性和有效性.
Control flow is an abstract expression of the program process, and it is of critical significance to obfuscate the control flow to effectively reinforce the code’s ability to resist reverse manners. This study proposes the idea of deep control flow: as for the loop structure, the callback function is utilized to construct an equivalent loop model, and the basic block in the program process is converted into inter-process function calling to counter reverse technology. This study comprehensively applies control flow analysis and data flow dependency analysis to establish a deep control flow obfuscation model based on callback function and gives proof of functional consistency. To further enhance obfuscation, the function calling fusion algorithm is designed and implemented pertinently to construct a more sophisticated function calling process. Finally, OpenSSL and SPECint-2000 benchmark suite is used as the test set to verify the feasibility and effectiveness of the proposed model.
软件保护是网络安全的重要组成部分, 在软件保护领域, 一个关键性的研究方向在于构造有效的混淆算法. Collberg等人[
循环结构包含大量算法逻辑相关的有效信息, 对循环结构进行混淆, 可有效提高代码保护能力. 本文在相关研究的基础上提出了控制流深度模糊思想, 即: 利用回调函数, 将显式的循环执行转为隐式的函数调用, 隐藏循环相关的路径分支信息. 传统循环通过过程内基本块跳转实现循环功能;而对于回调型循环, 循环相关代码处于不同函数内, 通过过程间函数调用关系实现循环功能. 本文将这种控制转移方式称为控制流的深度. 深度模糊就在于利用过程间函数调用, 灵活替换传统循环, 隐藏原始控制流, 对抗逆向分析. 围绕这一概念, 本文进一步提出了函数嵌套融合算法, 使循环调用由相邻过程间转移至随机过程间, 强化模型的深度特征.
本文的主要贡献在于:
(1) 从控制流和数据流的角度出发, 论证回调型循环与传统循环逻辑等价, 并给出了等价变换方式;
(2) 设计函数调用融合算法, 通过随机添加冗余函数调用, 构造复杂的控制流过程, 提高生成程序复杂度;
(3) 混淆系统实现: 针对两类循环结构进行局部优化, 确保模型应用前后功能一致, 并通过实验验证了系统的可行性与抗逆向分析的效果.
控制流混淆的思路主要有两种: (1) 破坏原始程序控制流结构; (2) 采用加密等手段保护跳转信息. 前者以控制流扁平化算法为代表, 对分支结构进行等价变换. 在一定程度上隐藏路径信息, 但不能做到完全消除, 因此具有可逆性. 后者以条件混淆算法、分类器混淆算法等[
总体而言, 当前代码混淆主要围绕过程内控制流展开, 代码等价执行过程由程序本身实现. 因此, 本文考虑在传统混淆算法的基础上实施改变, 使混淆过程由过程内扩展至过程间, 控制流转移变为程序与系统协同进行. 通过异常处理函数实施控制流混淆是这一思想的一种实现方式, Popov等人[
然而, 异常处理机制的复杂性导致控制流转移过程不完全可控, 且容易引起程序安全问题. 而函数回调过程相对简单, 执行流可预测. 因此, 本文提出了控制流深度模糊思想, 通过系统回调函数, 将循环结构分离至不同控制流执行过程, 同时将控制流转移过程交付系统执行, 实现对程序路径信息的隐藏, 对抗逆向分析.
回调型循环通过回调函数“响应-执行”机制, 将基本块循环跳转变换为函数间的反复调用. 以
如
显然, 传统循环转变为回调循环, 代码被剥离至不同函数体内, 将面临以下问题.
(1) 控制转移过程是否等价, 对复杂循环结构是否具有适用性;
(2) 如何维持变量作用域和数据依赖关系, 保证功能一致性.
要解决上述问题, 首先应选取适当的回调函数, 本文选取的回调函数均具有以下特点.
(1) 即时响应性: 执行调用者函数后, 立即触发响应事件, 调用回调函数;
(2) 反复回调性: 程序运行时, 循环次数不可控. 故在不触发退出条件的情况下, 调用者反复调用回调函数;
(3) 特定退出条件: 对应循环边界条件, 提供相应的退出机制, 以结束回调过程, 继续执行程序的其他功能.
Windows操作系统中存在大量回调函数, 包括消息传递、字符处理等. 按照上述规则, 本文初步选取100个符合条件的回调函数作为模型的基础. 下文将围绕控制转移过程的等价性和数据依赖关系的一致性展开论述, 证明模型的可行性.
Code example and execution process of callback loop
回调型循环代码示例与执行过程
控制流和数据流是进行程序分析的主要依据, 本文首先通过支配关系分析准确定义循环结构, 说明两类循环控制流等价; 其次, 利用跨过程别名分析, 证明代码变换前后数据流一致. 并给出相应的代码变换策略, 证明两类循环具有等价性.
对循环结构的分析一般以基本块为单位, 构造控制流程图
定义
定义
定义
为进一步分析循环结构, 分别建立支配关系矩阵
定义
结合边关系矩阵, 有关基本块的入度与出度有如下计算公式:
基本块的出度可以作为标识循环类型的重要依据, 参照C语言代码风格, 循环分为执行优先循环(do-while), 和判断优先循环(while-do, for). 根据出度的不同, 判定方式如下.
推论
针对不同的循环类型, 代码变换策略有所区别, 具体处理方式将在第3节中介绍. 下文将以执行优先循环为对象, 证明两类循环间存在控制流等价关系, 并对特殊循环结构和复杂控制流进行讨论.
回调型循环以函数调用的方式实现, 传统循环以基本块间跳转的方式实现, 两者不完全相同, 但在可见行为上一致, 符合代码混淆的要求. 因此, 对传统循环结构的分析方法同样适用回调型循环.
回向边是标识循环结构的主要依据, 参照
Equivalent execution process of two types of loops
两类循环等价执行流程
在此基础上, 针对循环结构中标识控制流转移关系的控制流关键字continue和break进行分析: continue使控制流由循环体内跳转至循环尾, 属于循环内跳转, 不需额外处理; break使控制流由循环体跳转至循环出口点, 当回调函数返回值为0时, 回调过程结束, 程序继续运行. 两类行为逻辑相同, 可以进行等价替换.
最后, 对实际编程中存在的特殊循环结构进行分类讨论, 判定代码变换的可行性及应用的变换方式, 如
Special loop structure
特殊循环结构
本文共总结3类特殊循环结构.
(1) 嵌套循环, 是指循环头属于其他循环循环体的控制结构. 对此类循环进行代码变换, 应按照支配关系依次进行. 若存在回向边
(2) goto循环, 指通过跳转关键字goto等价形成的循环功能, 该类循环具有回向边, 满足循环定义, 在控制流图层面与传统循环结构完全相同, 因此可等价处理.
(3) 交叉跳转, 指通过跳转关键字goto在循环内外进行任意跳转, 这种跳转行为只存在于函数内部, 不适用于回调型循环跨函数间跳转过程, 且违反程序可规约性, 因此对该种结构不进行处理, 维持原程序.
综上所述, 利用回调相关机制形成的循环结构与传统循环在控制流层面等价, 且对特殊循环结构具有处理能力. 下文将对数据流进行分析, 证明两类循环具有数据流等价性.
回调型循环将循环代码块由原函数转移至回调函数, 代码的分割将会破坏数据依赖关系. 因此需对程序中数据流进行分析, 发现有关依赖关系并完成重建.
定义
需要说明的是: 本文实施静态程序分析的对象是LLVM中间过程, 由于LLVM中间过程采用静态单赋值形式, 因此对任意变量
定义
推论
推论
为实现数据依赖关系的发现, 综合上述定义与推论, 本文实现了基于深度优先搜索的数据依赖发现算法. 首先对循环内指令的依赖数据进行迭代扫描, 如果发现数据流起始顶点, 则返回上层继续扫描; 如果发现存在对循环外部指令的依赖, 则记录该依赖数据并返回. 算法描述如算法1.
算法
输入: 指令
输出: 指令循环外依赖数据集合
1
2 procedure
3
4
5
6
7
8
9
10
11
12
13
应用该算法, 可有效挖掘原程序中存在的数据依赖关系, 并为依赖关系重建提供基础. 本文采用的重建方法是通过构建依赖数据的别名指针, 实现对原始变量地址的读取和写入, 构建过程如下.
首先, 将依赖数据地址依次入栈, 使栈中数据与依赖数据构成别名; 再按照对应关系, 完成对循环内引用变量的替换, 实现数据传递. 下文将进一步对该方法进行分析, 证明应用前后数据流等价.
通过指针传参, 程序可以在回调函数中访问原始变量, 此时, 对同一存储地址产生多条访问路径. 按照程序分析相关理论, 如果两个访问路径在指令
在本文模型中, 如果栈指针与原始变量在执行过程中构成必然别名, 则证明程序变换前后数据流等价, 具有数据一致性. 为完成这一证明, 首先给出别名分析的相关形式化定义.
定义
定义
推论
回调函数在执行时, 由调用者函数接受参数指针, 此时, 栈指针
应用推论3.4对回调函数中所有指令执行时别名状态集进行分析, 根据不动点理论, 对任意指令可解出确定的状态集合, 且集合中始终包含{<*
综上所述, 基于回调函数的循环模型在控制流和数据流上均可与传统循环形成等价, 因此, 两类循环间存在可替代关系.
LLVM框架是可扩展的程序优化平台, 提供大量API用于分析和修改中间语言代码[
混淆系统执行过程共分4个阶段: 第1阶段, 以C/C++编写的源程序作为输入, Clang将源程序转换为LLVM的中间表示; 第2阶段, 对中间语言进行分析, 挖掘循环代码块与数据依赖关系; 第3阶段, 引入回调函数并进行代码重组, 完成功能一致性处理与混淆强度提升; 第4阶段, 生成目标平台可执行文件.
本节将对第3阶段功能一致性处理与混淆强度提升进行具体讨论, 并对最终建立的系统进行科学评估.
循环入口点的出度是标识循环类型的主要依据, 对于不同类型的循环, 由于判断和执行的优先级不同, 标识跳转条件的比较指令位置也有所差异: 判断优先型循环中, 跳转条件位于循环头; 执行优先型循环中, 跳转条件位于循环尾. LLVM通过比较指令
然而, 在具体应用中, 两类循环的等价转换方式有所区别: 执行优先型在程序运行后立即进入循环结构, 与函数调用逻辑相符; 判断优先型先进行循环边界检测再确定是否进入循环结构. 因此, 针对后者额外添加跳转条件代码块, 在回调函数首次触发时, 执行该基本块进行入口判断, 通过代码冗余保持原程序执行逻辑不变. 具体形式如
Callback boundary setting for two types of loops
两类循环回调边界设定
控制流深度模糊模型利用回调函数, 将循环逻辑隐藏于不同函数的调用过程中, 从而对抗逆向分析. 为了进一步强调深度化的模型特征, 本文在代码变换的基础上构造了一种具有深度可伸缩的回调模型, 即按照用户需求, 随机融入相应量级的冗余回调函数, 形成更为复杂的调用关系, 提高混淆强度.
函数调用过程在形式上类似于基于深度优先的多叉树节点遍历, 因此, 实施多叉树融合将是函数调用融合的抽象解决办法: 假设对于任意函数
Coding tree
编码树
记该树中所有节点的集合为
Coding tree
编码树
显然, 将树
Random fusion of coding trees
编码树随机融合
值得注意的是:
函数融合算法的主要思想在于利用函数深度优先的执行方式构造符合函数执行序列的树结构, 并对树结构进行编码解析, 将函数融合问题抽象为树融合问题, 基于一定的随机选择, 最终给出融合策略. 算法设计如算法2.
算法
输入: 树
输出: 融合节点对应关系.
1
2
3
4 procedure
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
函数融合完成后, 回调循环过程将不再局限于相邻过程间调用, 而转为跨随机过程调用. 相比融合前, 函数调用过程更为复杂, 关键代码处于更深层的调用函数中, 有效提高抗逆向能力.
模型开销是评估抗逆向方法好坏的重要组成部分, 该指标主要包括程序变换后的代码膨胀率和执行效率. 本文在系统建立的前提下, 针对系统开销进行了理论分析, 进一步说明实际可用性.
定义
由于代码变换不涉及数据段, 因此可用指令条数近似标识程序大小. 本文记程序中循环数为
其中,
设
显然,
由公式(6)可知: 判断优先型循环的冗余代码是导致开销增加的主要原因, 执行优先型循环的代码替换基本不引发代码膨胀. 除此之外, 与传统抗逆向方法对复杂程序敏感不同, 本模型的代码膨胀率随程序的增大逐步趋于稳定, 可以有效适用大规模开发场景.
定义
此处, 本文用程序运行时执行指令数近似表示运行耗时. 显然, 除数据依赖和边界设定等冗余指令外, 程序需额外执行回调响应事件
其中,
显然, 程序执行效率与回调响应事件的执行效率紧密相关. 因此, 回调函数的选取是影响模型开销的主要因素. 一般来说, 本文选取的回调函数满足
回调函数是本文模型的基础,
Information of callback function
回调函数信息
函数名 | 主要功能 | 函数名 | 主要功能 |
对排序后的数组进行搜索 | 遍历Windows字体模块 | ||
_ |
对指定对象进行线性搜索 | 枚举窗体属性列表 | |
_ |
对指定对象进行线性搜索 | 枚举顶级窗体 | |
执行快速排序 | 枚举指定父窗体的子窗体 |
可以看出: 我们选取的回调函数以数组和模块的遍历为主, 主要进行读操作, 保证混淆算法不影响程序原始功能. 另外, 为使回调函数满足反复回调性, 我们在逆向分析的基础上对遍历指针进行动态复位, 如_
代码
1 //Compile:vs2015-Debug
2
3 _
4 {
5 mov eax, esi
6 sub eax,0x24
7 mov
8 }
9 *
分析表明: 当程序复杂度增大时, 混淆后的程序开销与原程序开销的比值趋于固定. 为展现模型特征, 本文选取OpenSSL中的3种常用算法和基准测试套件SpecInt-2000中的3类复杂程序作为测试代码, 实施模型评估. Parser和Twoif的循环数量相对较多, 我们认为: 为避免代码混淆特征过于明显, 同一回调函数不应被多次使用. 一般情况下, 我们以5为阈值, 首先按照循环相关基本块的数量对循环进行排序, 优先处理基本块数量较多的循环, 当存在同数量级循环无法完全处理时, 优先选择字符处理回调函数进行处理.
在上述测试样例之外, 本文选用简单排序算法Bubble Sort作为部分实验环节的演示示例. 测试集基本信息见
Basic information about test cases
测试用例基本信息
测试源码程序 | 代码行数 | 循环数量 | 处理循环数量 |
Bubble Sort | 28 | 2 | 2 |
SHA256 | 183 | 7 | 7 |
AES256 | 634 | 25 | 25 |
RSA2048 | 1 375 | 38 | 38 |
Bzip | 4 625 | 171 | 171 |
Parser | 11 421 | 562 | 421 |
Twoif | 20 500 | 906 | 526 |
首先对模型进行功能一致性检测: 对于加解密算法和压缩算法, 利用脚本随机填充数据, 目标类型包括字符串和二进制文件, 生成大小不同(1 B–1 000 MB)的测试用例; 对于Parser和Twoif算法, 直接应用SpecInt-2000标准测试样例, 比较模型应用前后输出结果. 实验表明: 回调型循环与传统循环算法逻辑一致、功能一致, 可以进行进一步测试.
本文设计的控制流深度模糊模型旨在增强代码的保护能力, 目前学术界主要采用Collberg提出的多维定性描述法来分析保护技术是否有效[
模型开销主要针对程序变化后的代码膨胀率和执行效率, 前文已经从理论上证明: 本模型对于复杂程序敏感度低, 开销随着程序增大趋向一个稳定值. 实验部分主要确定该值的具体大小并与传统逆向方法进行比较, 证明本模型具有开销低和适用大型程序的优势.
对测试集应用回调函数模型,
Code expansion rate after model application
模型应用后代码膨胀率
在公式(4.6)中, 本文用执行指令数近似分析了模型应用后执行效率的变化, 但在实际测试中, 需要用更准确的耗时指标测定程序执行效率. 此外, 为了检验回调函数选取对于模型开销的影响, 本文分别选取
如
因此, 实验检测结果与本文先期理论计算相符: 当程序复杂度增大时, 程序开销与程序规模仍呈线性关系. 学界认为: 线性相关的混淆算法可以支持大型程序的混淆, 因此本文模型具备良好的实用性.
Program execution efficiency after model application
模型应用后程序执行效率
OLLVM是著名的开源控制流混淆模型[
Comparison of obfuscation algorithm overhead
混淆算法开销对比
显然, 与OLLVM中的抗逆向算法相比, 本文模型在程序大小和运行效率上有显著区别, 稳定和低开销是本文模型的特点.
值得注意的是: 在实验中, 指令替换算法表现出了相似的开销特征, 然而指令替换算法保护效果相对较差. 因此, 在后续实验中, 本文将进一步说明本模型在抗逆向分析方面的优势.
动态行为调试和静态结构分析, 是逆向分析过程中的两个重要阶段. 本模型将同一函数内的循环跳转切分为多个函数的循环调用, 程序运行时, 循环跳转行为被隐藏, 调试分析的难度增大. 同时, 由于循环结构被重组, 循环跳转由系统自动化回调来实现, 程序控制流在静态层面将表现为顺序结构, 隐藏原本的控制流逻辑, 对抗静态分析.
获取程序的跳转信息有助于分析人员理解程序执行过程, 同时也是进行逆向分析的基础. 因此, 混淆技术的目的之一就在于隐藏跳转信息, 添加冗余信息, 增大程序的复杂性. 为描述混淆模型的抗逆向能力, 本文提出了程序执行熵的概念, 定量描述混淆前后程序的信息量. 模型针对循环结构进行处理, 有效跳转信息的多少, 成为衡量模型抗逆性的重要指标.
本文给出程序执行熵的具体计算方法如下.
首先记录程序运行过程中出现的条件跳转指令集合:
记程序中共有
记程序中共有非跳转指令
显然, 熵值越大, 程序中包含的跳转信息越少, 算法逆向难度越大. 而为了进一步增大模型应用后的程序执行熵, 本文建立了函数嵌套融合算法, 通过更深层次的函数调用减少有效信息含量. 根据前文所述, 设程序变换前函数调用树的叶子节点数为
贾春福等人[
需要说明的是: 该项目并未开放源码, 本文参照其技术原理进行了算法重构, 一定程度上反映了该工作所起到的混淆功效.
首先对所有算法应用两类模型, 然后通过动态二进制插桩平台Dynamorio, 在基本块的末尾指令(通常为分支跳转指令)处, 记录当前指令地址和跳转的目标地址, 最后生成程序执行熵, 结果见
执行熵是程序有效跳转信息的评价指标, 本文模型与对照模型都通过改变程序跳转方式隐藏控制流信息, 增大程序熵值. 对照模型针对条件跳转指令进行处理, 既适用于分支结构, 也适用于循环结构. 因此, 相比本文模型覆盖面更广, 取得了更好的跳转信息隐藏效果. 然而, 本文模型的优势在于良好的伸缩性, 通过随机嵌入冗余函数, 构造复杂的控制流过程, 可以进一步压缩有效跳转信息. 如
Program execution entropy before and after model application
模型应用前后程序执行熵
测试程序 | 变化前熵值 | 对照模型 | |||
SHA256 | 13.43 | 28.76 | 19.84 | 40.52 | 116.50 |
AES256-CBC | 25.31 | 42.84 | 36.13 | 44.15 | 136.79 |
RSA2048 | 26.24 | 58.64 | 40.41 | 76.53 | 208.91 |
Bzip | 40.46 | 80.43 | 74.15 | 151.94 | 342.84 |
Parser | 38.18 | 83.71 | 71.24 | 139.64 | 318.42 |
Twoif | 40.94 | 78.57 | 72.88 | 145.87 | 329.11 |
伸缩性由融合树的深度决定, 深度越深, 抗调试效果越好, 相应的模型开销也越大. 然而函数嵌套仅在程序中加入函数调用, 且该调用过程在循环体外, 不对程序本身执行效率构成较大影响. 且程序执行熵的增长与目标融合树深度的增加近似于指数关系, 因此, 该算法将以较小的效率损失快速提高程序执行熵.
但是, 深度的增加与冗余代码的生成也成指数关系, 容易造成代码快速膨胀. 经过实验测算, 假设
抗静态分析是应用本模型的另一个优势, 此处以冒泡排序为测试样例, 同时选取控制流扁平化混淆算法作为对照模型, 展示本模型在抗静态分析方面的能力.
IDA是实行静态分析的主要软件, 应用模型前的冒泡排序算法在IDA中完全暴露了自身的控制流逻辑, 分析者可以根据程序控制流图轻易掌握程序的跳转逻辑, 并进一步解析出算法思路. 因此, 隐藏原程序的跳转关系、破坏控制流图, 对于逆向分析具有重要意义. IDA中以汇编语言表述程序功能, 为便于理解, 本文
Control flow flattening obfuscation algorithm
控制流扁平化混淆算法
Control flow graph of callback loop structure
回调型循环控制流图
综上所述, 本文模型既增强了程序执行过程的复杂性, 又显著改变了程序的静态组成结构, 起到了良好的代码保护效果.
抗同源性强调程序变换前后的差异性, 增强程序的抗同源性可有效应对代码复用等攻击手段. 结构同源性与指令序列同源性是进行同源性分析的两个重要标准. 抗静态分析一节中, 本文已表明:应用回调模型混淆后的程序, 在不产生明显静态混淆特征的前提下, 显著改变了程序的控制流结构, 因此可有效对抗基于结构相似的同源性分析手段. 本节将主要针对指令序列相似性, 对模型进行分析.
取原程序中某函数起始地址为零地址, 设
Instruction offset before and after model application
模型应用前后指令偏移
本文提出了控制流深度模糊的概念, 利用回调函数的执行特征, 将循环逻辑隐藏于函数调用中. 同时, 以代码变换为基础, 抽象出函数调用过程, 实施冗余函数的随机融合, 进一步增强抗逆向能力. 在实验部分, 本文选取了3类混淆算法作为对照模型, 分别从开销、抗逆性、抗同源性角度出发进行了深入分析. 结果表明: 本文模型在开销和抗逆性方面都取得了较好的混淆效果, 同时具备一定抗同源性分析能力, 具有良好的适用性.
本文目前对变量作用域的处理主要采用数据流依赖分析, 事实上, 准确抽取代码数据是一大难点, 特别是编程中动态特性的使用, 使得准确抽取这些数据难度更为加大[
陈喆, 贾春福, 宗楠, 郑万通. 随机森林在程序分支混淆中的应用. 电子学报, 2018, 46(10): 2458−2466. [doi: 10.3969/j.issn.0372-2112.2018.10.020]
Chen Z, Jia CF, Zong N, Zhen WT. Branch obfuscation using random forest. Acta Electronica Sinica, 2018, 46(10): 2458−2466 (in Chinese with English abstract). [doi: 10.3969/j.issn.0372-2112.2018.10.020]
贾春福, 王志, 刘昕, 刘昕海. 路径模糊: 一种有效抵抗符号执行的二进制混淆技术. 计算机研究与发展, 2011, 48(11): 2111−2119.
Jia CF, Wang Z, Liu X, Liu XH. Branch obfuscation: An efficient binary code obfuscation to impede symbolic execution. Journal of Computer Research and Development, 2011, 48(11): 2111−2119 (in Chinese with English abstract). [doi: 10.1007/s00466-010-0527-8]
http://www.jos.org.cn/1000-9825/5429.htm]]>
http://www.jos.org.cn/1000-9825/5429.htm]]>