混淆应用代码
除了运行时加密或字节码转换以外,许多代码混淆技术聚焦于改变应用程序的控制流。这些技术的目的是获得超额收益,无论是没有达到抽象的状态还是达到太过抽象的状态。复杂性在结构谱系的两端始终是个问题。为此,可以使用以下策略:
- 内联和外联
- 重排序操作
- 使用异常处理转移控制
- 代码交错
- 集中式函数分发
内联是用函数体替换每个函数调用的做法。以这种方式,程序可以避免构建栈帧和在内存中四处跳转的开销。内联是相当标准的最优化技术,它最求以大小换速度。尽量最终的可执行文件执行速度提升了,但是它还是比较大。这是一种方便的技术,它不需要消耗多少时间(通常只需切换编译器配置选项),但同时却能产生收益,因为它破坏了高级程序语言极力加强的程序结构。
外联是另一种情况。为了以空间换时间,它试图将程序逻辑的重复片段整合为专用函数。程序需要更少的空间,但是由于新增函数调用的开销,程序会占用更多的时间来运行。因为嵌入式系统的存储器是稀缺产品,任何使用嵌入式系统的人都会立即赞成这个策略。如果过度使用,这种思路会将每条语句转变为它自己的函数调用 。如果内联未引入函数,而外联则是仅引入了函数,两种极端都能使分析人员感到困惑。
重排序操作依赖于这样的事实,即函数中不是所有语句都是顺序依赖关系。通过识别彼此相对独立并尽可能混合的这些语句,可以较好的利用这种技术。为了增强效果,可以将重排序与交错结合使用。然而,因为这种技术有可能在源代码级导致很多混淆,所以建议在机器代码级进行指令重排序。
大多数开发人员能够以某种方式使用异常处理。我们的指导者一般未能告诉我们,异常能够被用来在函数间进行突然的全局跳转。远跳转的这个特性能导致极度精妙的程序控制转移,当跳转看起来是一个意外情况而非对当前执行路径的正式重路由时,这一点尤其如此。这是浮点异常实际发挥作用的场景。
代码交错由以下几个步骤实现,他们包括提取两个或更多的程序,然后拆分程序的构成语句,最后把各种语句紧凑地穿梭以整合到单一例程中。理解该过程的最好方法就是看图(参见图1)。将单独的语句重新连接到一起并把他们重置于所属例程中的关键点在于用不透明谓词。
谓词就是一个条件语句,它对真或假做出评估。不透明谓词是一种预先知道结果的谓词,也就是说他总是返回相同的结果,即使它看起来不会。例如:
(index*NULL>0)
上述语句是一个总为假的不透明谓词。
(图1)
不透明谓词本质上是看似条件跳转的无条件跳转,这正是我们所希望的情况,因为我们想尽可能的使分析人员一直处于失衡状态和黑暗之中。
增强代码交错的一种方式是通过一个中心分发例程调用所有的例程。合并到一起的函数越多越好。在极端情况下,可以通过一个独立的分发例程合并可执行文件中的所有例程。这种中心化的调用策略是Armadillo的Nanomite技术背后的基本思路。
分发例程维护它自己的地址表,地址表返回地址映射到指定的函数中。以这种方式,调度程序知道将例程与调用者的映射关系。当调用一个例程序时,执行调用的代码在栈上传递它的返回地址。分发例程检查这个返回地址,并参考它的地址表来判定应该将程序控制重路由到哪个函数(参见图2)。从分析人员的角度来看,不管发生了什么事情,所有调用者看似都调用相同的例程序。
(图2)
转自 http://www.dcscms.com/article/content.php?seq=12