10.4.1 静态混淆
1.PC端
(1)外形混淆
外形混淆主要包括删除和改名。删除是指将程序中与执行无关的调试信息、注释、不会用到的方法和类等结构删除。删除后使攻击者难以阅读和理解,并减小程序的体积。改名包括对程序中的变量名、常量名、类名、方法名称等标识符作词法上的变换以阻止攻击者对程序的理解。
改名的方法有Hashing改名、名字交换、重载归纳等。Hashing改名就是将原来的名字替换为一个不相关的名字。名字交换是将原程序中所有的名字集中,再随机地分发给变量、常量、类、方法等。这种方法比较隐蔽,攻击者往往不易察觉。例如,以下三个不同的方法名都被替换成“a”而不会引发冲突,如图10.55所示。
图10.55 替换名字示意
上面的三个函数名均替换为fat后,三个函数进行了重载,函数调用根据参数类型进行区分调用。就算法的性能而言,外形混淆算法为程序增加的混淆度有限,算法的强度性能比较低,但是这类算法具有高度单向性,其弹性性能可达到单向混淆,同时算法没有给程序带来额外的开销,算法实现容易,因此得到了广泛的应用,大多数混淆器都支持外形混淆。
(2)结构混淆
结构混淆的目的是使得攻击者对程序的控制流难以理解,用伪装的条件判断语句来隐藏真实的执行路径。模糊谓词就是具有对加混淆者易于判断而对攻击者来说难于推导的特性的谓词。如果一个谓词P在p点它的输出在加混淆时是已知的,对一直是输出FALSE和TURE的谓词分别记作PT和PF,对可能是TURE也可能是FALSE的记为P?。在图10.56中,对于按次序执行的两条语句A、B,增加一个控制条件,以决定B的执行。通过这种方式加大逆向工程的难度,但是所有的干扰控制都不会影响B的执行。
图10.56 结构混淆示意
平展控制流技术是控制结构混淆的一种,它的目标是通过对控制流图进行平展,使得所有基本块看起来具有相同的前驱和后继集合,从而达到对程序的控制流逻辑关系进行混淆的目的。下面以一个小程序演示平展控制流技术,其中图10.57是源代码,而图10.58是控制流平展示意。
图10.57 用于平展的例子代码
图10.58的源程序通过应用平展控制流后得到如图10.59控制流图,S是switch块,x是调度变量。当控制进入函数中,基本块init将控制转移到A。在此之后,控制流通过不同基本块中对x的赋值来指导。
图10.58 平展前控制流图
图10.59 平展后控制流图
在上述平展控制流中,程序执行时,每个基本块对调度变量进行赋值,指示接下来要执行的基本块。通过使用调度变量作为索引变量的switch块,达到间接跳转到控制流后继的目的。对调度变量的赋值是在函数自身的内部完成的。因此,尽管混淆后代码的控制流行为不是很明显,但是可以通过检测赋给调度变量的常数来重构控制流图。控制结构混淆算法增加了程序的u1,u2,u3,u5复杂度,抵抗攻击能力强,但是开销很大。
(3)布局混淆
布局混淆是指删除或者混淆软件源代码或者中间代码中与执行无关的辅助文本信息,增加攻击者阅读和理解代码的难度。软件源代码中的注释文本、调试信息可以直接删除,用不到的方法和类等代码或数据结构也可以删除,这样既可以使攻击者难以理解代码的语义,也可以减小软件体积,提高软件装载和执行的效率。软件代码中的常量名、变量名、类名和方法名等标识符的命名规则和字面意义有利于攻击者对代码的理解,布局混淆通过混淆这些标识符增加攻击者对软件代码理解的难度。标识符混淆的方法有多种,如哈希函数命名、标识符交换和重载归纳等。哈希函数命名是简单地将原来标识符的字符串替换成该字符串的哈希值,这样标识符的字符串就与软件代码不相关了;标识符交换是指先收集软件代码中所有的标识符字符串,然后再随机地分配给不同的标识符,该方法不易被攻击者察觉;重载归纳是指利用高级编程语言命名规则中的一些特点,如在不同的命名空间中变量名可以相同,使软件中不同的标识符尽量使用相同的字符串,增加攻击者对软件源代码的理解难度。布局混淆是最简单的混淆方法,它不改变软件的代码和执行过程。
(4)数据混淆
数据混淆算法对程序中的数据结构进行转换,以非常规的方式组织数据,增加攻击者获取有效信息的难度。数据混淆方法有静态数据动态生成,数组结构转换,类继承转换,数据存储空间转换等。
1)静态数据动态生成。静态数据,尤其是字符串数据,包含大量攻击者需要的信息,利用函数或子程序对静态数据进行动态生成的方式混淆,能增加程序的u1,u2复杂度。这样虽然算法的强度与弹性提升,但是程序开销会大大增加。可以在应用中适当地选择混淆数据来增强程序性能。
2)数组结构转换包括将数组拆分或者合并,增加或减少数组的维度等。合并增加了程序的u1,u2复杂度,拆分数组增加了程序的u1,u2,u6复杂度,改变数组维度增加了程序的u1,u2,u6,u3复杂度。只用一种数组转换方式抵抗攻击的性能较弱,可将几种方式组合能大大加强抵抗攻击的强度。
3)类继承转换。类是面向对象语言中重要的模块化与抽象化概念。类的设计结构与继承关系反映了程序的设计思路。类继承转换就是对设计结构和继承关系进行混淆。它包括合并类、分割类和类型隐藏等。类继承转换提高了程序的u1,u7复杂度,额外开销也很小。数据混淆算法实现简单,开销也较小。但是其强度和弹性性能较控制结构算法弱,可与控制结构混淆算法组合使用来加强程序性能。
2.移动端
典型的静态混淆方法有控制流平坦化和花指令等。
控制流平坦化,就是在不改变源代码的功能前提下,将C或C++代码中的if、while、for、do等控制语句转换成switch分支语句。这样做的好处是可以模糊switch中case代码块之间的关系,从而增加分析难度。这种技术的思想是,首先将要实现平坦化的方法分成多个基本块和一个入口块,为每个基本块编号,并让这些基本块都有共同的前驱模块和后继模块。前驱模块主要是进行基本块的分发,分发通过改变switch变量来实现。后继模块也可用于更新switch变量的值,并跳转到switch开始处。控制流平坦化后的代码会产生大量分支和无意义的冗余代码,会极大地加大安全分析人员的逆向分析难度。控制流平坦化目前用得最多的是ollvm的开源混淆方案,很多国内加固厂商都可以看到使用它的身影。对于ollvm的反混淆思路,多采用基于符号执行的方法来消除控制流平坦化。
花指令也叫垃圾指令,是指在原始程序中插入一组无用的字节,但又不会改变程序的原始逻辑,程序仍然可以正常运行,然而反汇编工具在反汇编这些字节时会出错,由此造成反汇编工具失效,提高破解难度。花指令的主要思想是,当花指令与正常指令的开始几个字节被反汇编工具识别成一条指令的时候,才可以使得反汇编工具报错。因此插入的花指令都是一些随机的但不完整的指令。这些花指令必须要满足两个条件:在程序运行时,花指令是位于一个永远也不会被执行的路径中。这些花指令也是合法指令的一部分,只不过它们是不完整指令而已。也就是说,只需要在每个要保护的代码块之前插入无条件分支语句和花指令。无条件分支是保证程序在运行的时候不会运行到花指令的位置。而反汇编工具在反汇编时由于会执行到花指令,所以就会报错。在Dalvik Bytecode Obfuscation on Android文章中,就是利用线性扫描的特点,插入fill-array-data-payload花指令,导致反编译工具失效。花指令也存在很多优秀的开源项目,比如APKFuscator,dalvik-obfuscator等。在花指令的对抗上,主要的方法就是利用代码扫描技术检测出花指令的位置和长度,然后利用NOP指令进行替换和取代即可。
同时比较常用的工具有ProGuard,该工具的原理是使用简短无意义的英文字母对代码中的包含的变量名、函数名以及类名进行替代,该过程是不可逆的。ProGuard在混淆的过程中会将一些不影响应用正常运行的信息删除,这些信息的缺失使得对程序的逆向分析困难性进一步增加。但是ProGuard也是有局限性的,并不是所有的类和变量都能进行混淆处理,只能进行简单的类名、函数名混淆。
在Android代码的保护过程中,Java函数反射法也是比较常用的方法。函数反射调用是指利用Java的反射机制对函数或者方法进行调用,通过大量运用反射会使得代码的冗余度大大增加,反汇编后的代码会变得难以阅读和理解,增大反汇编难度。但是过多的反射调用也会影响程序的运行速度。在函数反射调用的动态对抗上,主要是利用函数反射调用的特殊规律,对加密的代码进行相对应的还原处理。