编译器

概念

将高级语言编写的程序转换位目标语言(通常是机器语言)的程序叫做编译器.这个转换的过程叫编译.不同于在执行解释性语言解释器总存在且逐行解释,编译器一次性把整个程序编译成二进制代码从而一般情况运行更快.但是由于解释器可以在不同平台上运作,兼容性,跨平台性更强.

编译器构成

传统的编译器构成常分为三个部分:前端,优化器,后端.编译过程中,前端负责语法分析,将源代码转为其高级形式:抽象语法树(即把源代码的字符串转为内存中有意义的数据);优化器将经过前端处理的中间代码进行优化;后端将优化后的中间代码转换为对应的汇编语言.

主要编译器

GCC

GCC(GNU Compiler Collection,GNU 编译器套装),是一套由 GNU 开发的编程语言编译器。GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C语言。GCC 快速演进,变得可处理 C++、Fortran、Pascal、Objective-C、Java 以及 Ada 等他语言。

LLVM

LLVM (Low Level Virtual Machine,底层虚拟机))提供了与编译器相关的支持,能够进行程序语言的编译期优化、链接优化、在线编译优化、代码生成。简而言之,可以作为多种编译器的后台来使用。

苹果公司一直使用 GCC 作为官方的编译器。GCC 作为一款开源的编译器,一直做得不错,但 Apple 对编译工具会提出更高的要求。原因主要有以下两点:

其一,是 Apple 对 Objective-C 语言(包括后来对 C 语言)新增很多特性,但 GCC 开发者并不买 Apple 的账——不给实现,因此索性后来两者分成两条分支分别开发,这也造成 Apple 的编译器版本远落后于 GCC 的官方版本。

其二,GCC 的代码耦合度太高,很难独立,而且越是后期的版本,代码质量越差,但 Apple 想做的很多功能(比如更好的 IDE 支持),需要模块化的方式来调用 GCC,但 GCC一直不给做。

该编译器的特殊点在于中间表示(IR).这是前端将源代码生成为一种类机器码(各平台不同),并可以由LLVM后端解析的代码.用以扩展解释器的跨平台性.此前,一般是把源代码编译成C代码然后交由对应平台的C编译器编译成二进制文件(因为一般平台都有C编译器).在LLVM中,只需要把源文件编译成IR然后交由LLVM后端直接编译到对应平台.

CLANG

Clang 是 LLVM 的前端,于LLVM的后端组合可以当作另一个编译器,Clang是Apple研发的前端旨在代替GCC的前端


OLLVM

在LLVM基础上,Obfuscator-LLVM项目被研发了出来,通过三种混淆Pass(模块)完善代码的安全性.

  • 控制流平坦化
  • 指令替换
  • 伪造控制流(又叫混淆控制流)

混淆Pass作用于IR,通过混淆IR使后端转换的文件被混淆,使逆向难度增大.

控制流平坦化

104308x4cppkp5zjsfl8zy

代码本身是真实块和retn块的组合,在进行控制流平坦化之后,程序的流程被分发器控制,所有真实块最后进行对标记的处理然后全部进入预处理器,根据标记的值不同由主分发器分发到副分发器再到真实块,从而混淆了整个执行流程,在反汇编视图中的特征是有很多while循环在后面有switch case语句.(以我的水平来看,控制流平坦化之后几乎不可读)

特征

原始的条件一般会转化为CMOV条件传送指令,根据比较结果设置标记的值

指令替换

用多个表达式替换一个计算表达式

特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
原式子:
a=b+c
混淆后:
a = b - (-c)
a = -(-b + (-c))
r = rand (); a = b + r; a = a + c; a = a - r
r = rand (); a = b - r; a = a + b; a = a + r

原式子:
a = b-c
混淆后:
a = b + (-c)
r = rand (); a = b + r; a = a - c; a = a - r
r = rand (); a = b - r; a = a - c; a = a + r

原指令:
a = b & c
混淆后:
a = (b ^ ~c) & b
a = ~(~a | ~b) & (r | ~r)

原指令:
a = b | c
混淆后:
a = (b & c) | (b ^ c)
a = (((~a & r) | (a & ~r)) ^ ((~b & r) | (b & ~r))) | (~(~a | ~b) & (r | ~r))

原指令:
a = a ^ b
混淆后:
a = (~a & b) | (a & ~b)
a = ((~a & r) | (a & ~r)) ^ ((~b & r) | (b & ~r))

或者

[IDA F5 增强插件,还我源代码(一)-腾讯云开发者社区-腾讯云 (tencent.com)](https://cloud.tencent.com/developer/article/1845335)

伪造控制流

伪造控制流是在一开始设置一个变量/表达式,这个变量/表达式被称为不透明谓词,并为每一个基本块生成一个垃圾指令块,通过不透明谓词进行跳转到不同位置,但是实际上不会到达垃圾指令块处.

特征图

6.png

对于初学者的逆向方法

伪造控制流:见Hex-Rays: 十步杀一人,两步秒OLLVM-BCF(zhihu.com)

控制流平坦化和伪造控制流:见pcy190/deflat: Use angr to deflat the flat control flow. (github.com)

指令替换:见joydo/d810 (github.com)