OLLVM特性、使用原理
一、OLLVM特性
目前ollvm支持的特性有以下几种:
- 指令替换 -mllvm -sub
- 虚假控制流 -mllvm -bcf
- 打平控制流 -mllvm -fla
- 函数(Funtions)注解
二、指令替换
所谓指令替换仅仅是对标准二进制运算(比如加、减、位运算)使用更复杂的指令序列进行功能等价替换,当存在多种等价指令序列时,随机选择一种。
这种混淆并不直截了当而且并没有增加更多的安全性,因为通过重新优化可以很容易地把替换的等价指令序列变回去。然而,提供一个伪随机数,就可以使指令替换给二进制文件带来多样性。
目前,只有在整数上的操作可用,因为在浮点数上的运算替换会带来四舍五入的错误以及不必要的数值不准确。
可用选项有
- -mllvm -sub:激活指令替换趟(pass)
- -mllvm -sub_loop=3:如果激活了指令替换,使用这个选项在一个函数中应用3次指令替换。默认应用1次。
如何实现?
- 加法
(1)a = b - (-c)
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = sub i32 0, %1
%3 = sub nsw i32 %0, %2
(2)a= (-b + (-c))
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = sub i32 0, %0
%3 = sub i32 0, %1
%4 = add i32 %2, %3
%5 = sub nsw i32 0, %4
(3)r = rand(); a = b + r; a = a + c; a = a - r
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = add i32 %0, 1107414009
%3 = add i32 %2, %1
%4 = sub nsw i32 %3, 1107414009
(4)r = rand(); a = b - r; a = a + c; a = a + r
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = sub i32 %0, 1108523271
%3 = add i32 %2, %1
%4 = add nsw i32 %3, 1108523271
2. 减法
(1)a = b + (-c)
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = sub i32 0, %1
%3 = add nsw i32 %0, %2
(2)r = rand(); a = b + r; a = a - c; a = a - r
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = add i32 %0, 1571022666
%3 = sub i32 %2, %1
%4 = sub nsw i32 %3, 1571022666
(3)r = rand(); a = b - r; a = a - c; a = a + r
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = sub i32 %0, 1057193181
%3 = sub i32 %2, %1
%4 = add nsw i32 %3, 1057193181
3. 按位与
a = b & c => a = (b ^ ~c) & b
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = xor i32 %1, -1
%3 = xor i32 %0, %2
%4 = and i32 %3, %0
4. 按位或
a = b | c => a = (b & c) | (b ^c)
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = and i32 %0, %1
%3 = xor i32 %0, %1
%4 = or i32 %2, %3
5. 按位异或
a = b ^ c => a = (~b & c) | (b & ~c)
%0 = load i32* %a, align 4
%1 = load i32* %b, align 4
%2 = xor i32 %0, -1
%3 = and i32 %1, %2
%4 = xor i32 %1, -1
%5 = and i32 %0, %4
%6 = or i32 %3, %5
三、虚假控制流
这种方式通过在当前基本块之前添加一个基本块,来修改函数调用流程图。新添加的基本块包含一个不透明的谓语,然后再跳转到原来的基本块。
原始的基本块会被克隆,并充满了随机的垃圾指令。
可用选项有:
(1)-mllvm -bcf:激活虚假控制流趟(pass)
(2)-mllvm -bcf_loop=3:如果虚假控制流被激活,在一个函数中应用三次。默认应用一次。
(3)-mllvm -bcf_prob=40:如果虚假控制流趟被激活,一个基本块将会以40%的概率被混淆。默认30%。
一个例子:
下面的C语言代码,
#include <stdlib.h>
int main(int argc, char** argv) {
int a = atoi(argv[1]);
if(a == 0)
return 1;
else
return 10;
return 0;
}
会被翻译成这样的中间代码:
虚假控制流的趟结束之后,我们或许会得到下面的控制流图:
四、控制流打平
控制流打平的目的是将程序的控制流图完全地扁平化。
可用选项:
(1)-mllvm -fla:激活控制流打平
(2)-mllvm -split:激活基本块划分。一起使用时能提高打平能力。
(3)-mllvm -split_num=3:如果激活控制流打平,对每一个基本块应用三次控制流打平。默认使用1次。
考虑下面的C代码片段
#include <stdlib.h>
int main(int argc, char** argv) {
int a = atoi(argv[1]);
if(a == 0)
return 1;
else
return 10;
return 0;
}
经过控制流打平后,这段代码代码变成了这样:
#include <stdlib.h>
int main(int argc, char** argv) {
int a = atoi(argv[1]);
int b = 0;
while(1) {
switch(b) {
case 0:
if(a == 0)
b = 1;
else
b = 2;
break;
case 1:
return 1;
case 2:
return 10;
default:
break;
}
}
return 0;
}
所有的基本块都被分开且放到了无限循环中,程序流变成了由switch和变量b控制。在打平控制流之前的控制流是这样的:
打平之后的控制流是这样的:
五、函数注解
使用注解可以定制哪个函数参与混淆。