OLLVM混淆

LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。

个人感觉OLLVM混淆大都是while true的嵌套,而OLLVM的破解一般都用angr

虚假控制流 (Bogus control flow)

在讲虚假控制流之前,我们看看别的,利用不透明谓词混淆代码的原理是什么?

不透明谓词是指一个表达式,他的值在执行到某处时,对程序员而言必然是已知的,但是由于某种原因,编译器或者说静态分析器无法推断出这个值,只能在运行时确定。

不透明谓词的设计必须满足俩条件

1,对于混淆器也就是开发者,它的值在执行到某阶段总是确定的。

2,对于分析器,也就是攻击者,只有执行到某阶段才能确切的知道它的值。

条件一和条件二完美符合,那么这个不透明谓词就算是一个高质量的设计。

原理其实挺简单,但是要设计出简单高效高质量的不透明谓词,是比较难的。

在将代码转换成汇编和机器码之前,编译器中的一种代码表示形式,可以简单的理解为具有跨平台性和其他特性的汇编,正确解释请看编译原理或了解一下LLVM和OLLVM。

虚假控制流的流程表示

用 OLLVM 项目中的注释部分就可以清晰表示出什么是虚假控制流。可以看到,相对于原程序中的单个代码块,混淆后的程序中加入了一个由不透明谓词组成的条件语句,由于混淆器知道他的值,所以可以保证被混淆程序的正确性,执行被混淆程序原本的代码,而编译器和反编译器都无法对这个表达式进行求值,只能保留此谓词,达到干扰静态分析的目的。

简单来说,通过大量的if和while语句来混淆反编译器,只有混淆器知道正确的路

Before :
entry
|
______v______
| Original |
|_____________|
|
v
return

After :
entry
|
____v_____
|condition*| (false)
|__________|----+
(true)| |
| |
______v______ |
+-->| Original* | |
| |_____________| (true)
| (false)| !-----------> return
| ______v______ |
| | Altered |<--!
| |_____________|
|__________|

怎么实现混淆

在其他项目中

在 OLLVM 中,使用了 y > 10 || x * (x + 1) % 2 == 0 这个不透明谓词,学过数学的都知道,x * (x + 1) % 2 == 0 是个永真式。也就是说,混淆器已经知道了 y > 10 || x * (x + 1) % 2 == 0 这个式子的值,所以可以将其放在条件语句中,控制代码的走向;而编译器和反编译器都认为这个表达式需要进行运行后才能求值,会保留这段代码,进而干扰到反编译器。

HikariObfuscator这个项目中使用 IRBuilder 生成一箩筐的整数运算,由于 LLVM 的 IRBuilder 会折叠常量,混淆器就能知道了之前生成的一箩筐的整数运算的最终结果,而编译器和反编译器都不知道生成的表达式的值,所以就获得了一个崭新的不透明谓词。

当然,上面的两个混淆都有较为简单的破解方法,就是将不透明谓词中的变量修改成常量,并且设置一个初始值,这时候 Hex-rays 等反编译器会对不透明谓词进行求值,如果不透明谓词的结果可知,那么这个表达式将会被优化。

具体实现混淆

有时间实操一下

去OLLVM

使用deflat.py反混淆

使用方法: python deflat.py -f path/to/binary –addr hexaddress

path/to/binary填文件,hexaddress填函数入口地址。

python deflat.py -f /home/phantomor/Desktop/EasyRe –addr 0x400A0D

[安洵杯 2019]game

打开一看是个Sudoku题,

点开函数一看

image1

使用上述工具对主要函数去混淆check1、check3

size_t __fastcall check1(char *a1)
{
size_t result; // rax
char v2; // [rsp+6Eh] [rbp-12h]
char v3; // [rsp+6Fh] [rbp-11h]
int i; // [rsp+70h] [rbp-10h]
int v5; // [rsp+74h] [rbp-Ch]
int j; // [rsp+74h] [rbp-Ch]
int k;

v5 = strlen(a1) >> 1;
for ( i = 0; i < strlen(a1) >> 1; ++i ) //移位 0-20 1-21
{
v3 = a1[v5];
a1[v5] = a1[i];
a1[i] = v3;
++v5;
}
for ( j = 0; j < strlen(a1); j += 2 ) //移位 0-1 2-3
{
v2 = a1[j];
a1[j] = a1[j + 1];
a1[j + 1] = v2;
}
for ( k = 0; ; ++k )
{
result = strlen(a1);
if ( k >= result )
break;
a1[k] = (a1[k] & 0xF3 | ~a1[k] & 0xC) - 20; //加密操作
}
return result;
}
int __fastcall check3(char *a1)
{
int result; // eax

if ( (unsigned int)check2(a1) )
result = printf("you get it!\n");
else
result = printf("error!\n");
return result;
}
最后把Sudoku做好
1, 0, 5, 3, 2, 7, 0, 0, 8
8, 0, 9, 0, 5, 0, 0, 2, 0
0, 7, 0, 0, 1, 0, 5, 0, 3
4, 9, 0, 1, 0, 0, 3, 0, 0
0, 1, 0, 0, 7, 0, 9, 0, 6
7, 0, 3, 2, 9, 0, 4, 8, 0
0, 6, 0, 5, 4, 0, 8, 0, 9
0, 0, 4, 0, 0, 1, 0, 3, 0
0, 2, 1, 0, 3, 0, 7, 0, 4

4693641762894685722843556137219876255986

脚本

int main()
{
char a[] = "4693641762894685722843556137219876255986";
int len = strlen(a);
int i;
char temp;
for (i = 0; i < len; i++)
{
temp = a[i] + 20;
temp = temp & 0xf3 | ~temp & 0xc;
a[i] = temp;
}
for (i = 0; i < len; i += 2)
{
temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
}
for (i = 0; i < len / 2; i++)
{
temp = a[i];
a[i] = a[i + len / 2];
a[i + len / 2] = temp;
}
printf("%s\n", a);
return 0;
}

参考链接

重写 OLLVM 之虚假控制流

Turning Regular Code Into Atrocities With LLVM

分析OLLVM