基于雪崩猜想的混淆构造
符号执行
定义:一种程序分析技术,采用抽象的符号代替精确值作为程序输入变量,通过约束求解器得出每个路径抽象的输出结果。(解方程)
- 静态符号执行:不执行程序,将所有输入符号化。
- 动态(混合)符号执行:尽可能地执行程序,尽可能少地引入符号变量。
分析流程大致如下:
极简示例
将下列C++源代码编译,并用符号执行框架angr进行分析。
#include<iostream>
using namespace std;
int main()
{
int choice;
cin >> choice;
if(choice >= 0) {
cout << "right path\n";
} else {
cout << "wrong path\n";
}
return 0;
}
找到right path的约束条件为:
In [2]: simgr.explore(find=0x401208)
Out[2]: <SimulationManager with 1 active, 1 found>
In [3]: simgr.found[0].solver.constraints
Out[3]: [<Bool (LShR((condition_56_32), 0x1f)[0:0] & 1) == 0>]
LShR操作表示逻辑右移,化简该条件可知约束为choice >= 0
符号执行框架组成
符号执行框架有很多,常用的有angr、klee、triton这三个。它们大都由以下几部分组成:
- 文件加载器
- 中间语言转换接口
- 约束求解器(比如z3)
- 模拟执行引擎(动态分析需要)
angr框架(v9.2.38)
angr是一个支持多架构的二进制文件动态符号执行分析工具
simgr.use_technique | simgr.explore | simgr.step | simgr.run |
在分析过程中使用指定的分析方法(默认为广度优先搜索)。 | 接收一个地址,输出到达该地址时程序的状态(默认找到一条路径就返回)。 | 以块或函数为最小单位模拟执行一步。 | 按照设定的分析方法执行直到分析完毕。 |
angr中常用的约束收集策略如下:
- BFS:默认的搜索方式,一层一层进行搜索。
- DFS:往深处走,其他的分支路径都推到后面再说。
- LengthLimiter:限制最深路径的深度。
- LoopSeer:将循环次数过多的情况延后处理。
- MemoryWatcher:监视每一步的内存分配释放情况。
- Veritesting:CMU论文提到的一种缓解路径爆炸方法的实现。
符号执行的缺陷
- 路径爆炸:可能出现的程序执行路径过多,造成框架内存溢出,分析被迫停止。
- 复杂约束求解:SMT求解器对非线性约束的求解速度较慢。
- 符号内存建模:对类似array[symbolic variable]的寻址访问,会出现大量的可能执行路径,严重减缓框架的分析速度。
for-loop混淆构造路径爆炸
// 混淆前
if(sec == 0x123456)
go()
// 混淆后
int ch1 = sec & 0xFF;
int ch2 = (sec & 0xFF00) >> 8;
int ch3 = (sec & 0xFF0000) >> 16;
char c1, c2, c3;
for (int i = 0; i < ch1; i++) c1++;
for (int i = 0; i < ch2; i++) c2++;
for (int i = 0; i < ch3; i++) c3++;
if (c1 == 0x12 && c2 == 0x34 && c3 == 0x56) go();
// 总计有256^3条路径,构造路径爆炸问题
基于雪崩猜想构造路径爆炸
雪崩猜想
雪崩猜想又叫3n+1猜想、角谷静夫猜想等,猜想具体内容如下: $$ f(x)= \begin{cases} \frac{x}{2}&& \text{x为偶数} \newline 3x+1&& \text{x为奇数} \end{cases} $$
在x为正整数的情况下,经过不断地自迭代后,最终一定会进入4->2->1序列。
举例: 37->112->56->28->14->7->22->11->34->17->52->26->13->40->20->10->5->16->8->4->2->1
基于雪崩猜想构造混淆的代码示例:
int a;
uint64_t b = (uint64_t)&a + rand();
// 使得符号执行引擎符号化b
for (int i = 0; i < 1000; i++)
{
if (b & 1)
b = 3 * b + 1;
else
b /= 2;
}
// 类似的还有3x+3猜想、3x+371猜想等
if (b <= 4)
cout << "secret\n";
else
cout << "fake path\n";
不动点问题构造路径爆炸
雪崩猜想构造路径爆炸的核心:多次迭代、分支判断和固定解。
数值分析中的不动点拥有类似的迭代结构,举例: $$f(x) = 1 + \frac{1}{x}$$
$$ \forall{x} \in R-{-0.618}, f(…f(f(x))) \approx 1.618 $$
不动点迭代的条件: $$|f’(x_0)|<1,x_0为不动点$$
举例:
$$f(x)= \begin{cases} \frac{3}{x-2}&& \text{某些条件} \newline \frac{4}{x-3}&& \text{某些条件取反} \end{cases}$$
最终可能会迭代到不动点-1
代码实验
# 3和4分别是第一个函数和第二个函数的另一个不动点
x = int(input("输入除3,4外的任意整数:"))
for i in range(100):
if(x > 0):
x = 3/(x-2)
else:
x = 4/(x-3)
print(x,end=' ')
if(i % 4 == 0):
# 换行
print()
代码结果如下:
存在的问题
- 分支条件的选择:需要选择满足性质的函数使得在特定分支条件下仍能收敛到不定点。
- 运算效率:与雪崩猜想猜想相比,不动点定理在实数域上进行运算,运算效率较低。
- 浮点数精度判断问题:计算机计算浮点数存在误差。
参考文献
- https://api.angr.io/
- kcon2022 击败SOTA反混淆方法
- Linear obfuscation to combat symbolic execution
- Code obfuscation against symbolic execution attacks
- 数值分析原理 吴勃英