一、LLVM
1. 什么是 LLVM
- 官网:https://llvm.org/
- The LLVM Project is a collection of modular and reusable
compiler
andtoolchain
technologies. - LLVM 项目是模块化、可重用的
编译器
以及工具链
技术集合
2.创始人
-
Chris Lattner
,亦是 Swift 之父
3. 有些文章把 LLVM 当做 Low Level Virtual Machine (低级虚拟机)的缩写简称,官方开头描述如下
- The name "LLVM" itself is no an acronym; it is the full name of the project.
- “LLVM” 这个名称不是首字母缩写词;它是项目的全名
二、编译器架构
1. 传统编译器架构
Frontend:前端;词法分析、语法分析、语义分析、生成中间代码
Optimizer:优化器;中间代码优化
Backend:后端;生成机器码
2. LLVM 架构
- 不同的前端后端使用统一的中间代码 LLVM Intermediate Representation (LLIR)
- 如果需要支持一种新的编程语言,那么只需要实现一个新的前端
- 如果需要支持一种新的硬甲设备,那么只需要实现一个新的后端
- 优化阶段是一个通用的阶段,它针对的统一的 LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改
- 相比之下,GCC 的前端和后端没分得太开,前后端耦合在了一起。所以 GCC 为了支持一门新的语言,或者为了支持一个新的目标平台,就变得特别困难
- LLVM 现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC 家族、Java、.NET、Python、Ruby、Scheme、Haskell 等)
三、Clang
1. 什么是 Clang?
- LLVM 项目的一个子项目
- 基于 LLVM 架构的 C/C++/Objective-C 编译器
前端
- 官网:http://clang.llvm.org/
2. 相比于 GCC,Clang 有如下优点
- 编译速度快
- 占用内存小
- 模块化设计
- 诊断信息可读性强
- 设计清晰简单,容易理解,易于扩展增强
四、OC 源文件编译的过程
编写如下 test.m
文件
int test(int a, int b) {
int c = a + b + 3;
return c;
}
1. 命令行查看编译过程:clang -ccc-print-phases test.m
carrot__lsp$ clang -ccc-print-phases test.m
0: input, "test.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
2. 词法分析,生成 Token:clang -fmodules -E -Xclang -dump-tokens test.m
carrot__lsp$ clang -fmodules -E -Xclang -dump-tokens test.m
int 'int' [StartOfLine] Loc=<test.m:1:1>
identifier 'test' [LeadingSpace] Loc=<test.m:1:5>
l_paren '(' Loc=<test.m:1:9>
int 'int' Loc=<test.m:1:10>
identifier 'a' [LeadingSpace] Loc=<test.m:1:14>
comma ',' Loc=<test.m:1:15>
int 'int' [LeadingSpace] Loc=<test.m:1:17>
identifier 'b' [LeadingSpace] Loc=<test.m:1:21>
r_paren ')' Loc=<test.m:1:22>
l_brace '{' [LeadingSpace] Loc=<test.m:1:24>
int 'int' [StartOfLine] [LeadingSpace] Loc=<test.m:2:5>
identifier 'c' [LeadingSpace] Loc=<test.m:2:9>
equal '=' [LeadingSpace] Loc=<test.m:2:11>
identifier 'a' [LeadingSpace] Loc=<test.m:2:13>
plus '+' [LeadingSpace] Loc=<test.m:2:15>
identifier 'b' [LeadingSpace] Loc=<test.m:2:17>
plus '+' [LeadingSpace] Loc=<test.m:2:19>
numeric_constant '3' [LeadingSpace] Loc=<test.m:2:21>
semi ';' Loc=<test.m:2:22>
return 'return' [StartOfLine] [LeadingSpace] Loc=<test.m:3:5>
identifier 'c' [LeadingSpace] Loc=<test.m:3:12>
semi ';' Loc=<test.m:3:13>
r_brace '}' [StartOfLine] Loc=<test.m:4:1>
eof '' Loc=<test.m:4:2>
3. 语法分析,生成语法树(AST,Abstract syntax Tree):carrot__lsp$ clang -fmodules -fsyntax-only -Xclang -ast-dump test.m
carrot__lsp$ clang -fmodules -fsyntax-only -Xclang -ast-dump test.m
`-FunctionDecl 0x7fc0a8832ff8 <test.m:1:1, line:4:1> line:1:5 test 'int (int, int)'
|-ParmVarDecl 0x7fc0a8832e70 <col:10, col:14> col:14 used a 'int'
|-ParmVarDecl 0x7fc0a8832ee8 <col:17, col:21> col:21 used b 'int'
`-CompoundStmt 0x7fc0a88332e8 <col:24, line:4:1>
|-DeclStmt 0x7fc0a8833260 <line:2:5, col:22>
| `-VarDecl 0x7fc0a8833110 <col:5, col:21> col:9 used c 'int' cinit
| `-BinaryOperator 0x7fc0a8833238 <col:13, col:21> 'int' '-'
| |-BinaryOperator 0x7fc0a88331f0 <col:13, col:17> 'int' '+'
| | |-ImplicitCastExpr 0x7fc0a88331c0 <col:13> 'int' <LValueToRValue>
| | | `-DeclRefExpr 0x7fc0a8833170 <col:13> 'int' lvalue ParmVar 0x7fc0a8832e70 'a' 'int'
| | `-ImplicitCastExpr 0x7fc0a88331d8 <col:17> 'int' <LValueToRValue>
| | `-DeclRefExpr 0x7fc0a8833198 <col:17> 'int' lvalue ParmVar 0x7fc0a8832ee8 'b' 'int'
| `-IntegerLiteral 0x7fc0a8833218 <col:21> 'int' 3
`-ReturnStmt 0x7fc0a88332d0 <line:3:5, col:12>
`-ImplicitCastExpr 0x7fc0a88332b8 <col:12> 'int' <LValueToRValue>
`-DeclRefExpr 0x7fc0a8833278 <col:12> 'int' lvalue Var 0x7fc0a8833110 'c' 'int'
五、代码混淆
iOS 程序可以通过 class-dump、Hopper、IDA 等获取类名、方法名、以及分析程序的执行逻辑,如果进行代码混淆,可以加大别人的分析难度。
1. 源码混淆,通过宏定义的方式。
- 类名
- 方法名
- 协议名
2. 字符串加密
很多时候,可执行文件中的字符串信息,对破解者来说,非常关键,是破解的捷径之一
为了加大破解、逆向难度,可以考虑对字符串进行加密
字符串的加密技术有很多种,可以根据自己的需要进行自行定制算法
-
这里举一个简单的例子:对每个字符串进行异或(^)处理
3. 混淆注意点
不能混淆系统方法
不能混淆 init 开头的等初始化方法
混淆属性时需要额外注意 set 方法
如果 xib、storyboard 中用到了混淆的内容,需要手动修正
可以考虑吧需要混淆的符号都加上前缀,跟系统自带的符号进行区分
混淆过多可能会被 App Store 拒绝上架,需要说明用途