控制流平坦化是OLLVM中使用到的一种代码保护方式,它还有2个兄弟-虚假控制流和指令替换,这3种保护方式可以累加,对于静态分析来说混淆后代码非常复杂。
控制流平坦化的主要思想就是以基本块为单位,通过一个主分发器来控制程序的执行流程。
例如一个常见的if-else分支结构的程序可以是这样:
经过控制流平坦化之后,得到了一个相当规整的流程图:
控制流平坦化在代码上体现出来可以简要地理解为是while+switch的结构,其中的switch可以理解为主分发器。这一点在IDA的反汇编里面可以很明显地体现出来。
混淆前:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
if ( argc == 2 )
{
if ( check_password(argv[1]) )
puts("Congratulation!");
else
puts("error");
result = 0;
}
else
{
puts("error");
result = 1;
}
return result;
}
混淆后:
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // eax
int v4; // eax
signed int v5; // ecx
signed int v7; // [rsp+44h] [rbp-1Ch]
int v8; // [rsp+58h] [rbp-8h]
v8 = 0;
v7 = 1633153878;
do
{
while ( v7 > -1424773165 )
{
if ( v7 > -608328430 )
{
if ( v7 > 1948349962 )
{
if ( v7 == 1948349963 )
{
v8 = 1;
v7 = -1883523171;
puts("error");
}
}
else if ( v7 > 1633153877 )
{
if ( v7 == 1633153878 )
{
v3 = 1228678806;
if ( argc != 2 )
v3 = 1948349963;
v7 = v3;
}
}
else
{
switch ( v7 )
{
case -608328429:
v8 = 0;
v7 = -1883523171;
break;
case -549365528:
v7 = -608328429;
puts("Congratulation!");
break;
case 1228678806:
v4 = check_password(argv[1]);
v5 = -1424773164;
if ( v4 )
v5 = -549365528;
v7 = v5;
break;
}
}
}
else if ( v7 == -1424773164 )
{
v7 = -608328429;
puts("error");
}
}
}
while ( v7 != -1883523171 );
return v8;
}
从IDA的流程图中也可以很容易地识别出经过控制流平坦化的程序。
可以看到,经过平坦化之后的代码块大部分整齐地堆积在程序流图的下方,而这部分代码就是真正有意义的代码块,称之为相关块。
而去平坦化要做的就是在茫茫人海中识别出相关块,然后理清楚这些相关块之间的关系。
目前去控制流平坦化最有效的办法是利用符号执行。具体的脚本和使用我参考了
反混淆:恢复被OLLVM保护的程序
而控制流平坦化的理论和去平坦化的算法参考了
反混淆:恢复被OLLVM保护的程序
然鹅在用腾讯安全应急响应中心提供的工具时因为angr和barf版本不同报了很多错,我在https://github.com/SnowGirls/deflat下载了这个工具的更新版,遇到部分报错如下:
File "default.py", line 32, in statement_inspect
if len(expressions) != 0 and isinstance(expressions[0], pyvex.expr.ITE):
TypeError: object of type 'generator' has no len()
谷歌了一下,发现了和我遇到同样问题的师傅
这位师傅罗列了很多的报错和修改方法,在参考并修改后执行成功。
可以发现经过去平坦化之后的代码已经恢复了大部分的可读性。
上面的反混淆脚本在处理控制流平坦化时只能解决简单的while+switch套娃。如果输入的数据能够影响循环的走向,我们就不得不处理已经经过的分支,否则因为分支过多而爆内存…个人猜想可以通过手动patch的方法对程序执行的路径树进行“剪枝”,或者把一个控制流平坦化流程图分成2个。目前还没有实际遇到过。以后遇到的时候再具体分析。
参考:
反混淆:恢复被OLLVM保护的程序
https://www.freebuf.com/articles/network/144896.html
利用符号执行去除控制流平坦化
https://security.tencent.com/index.php/blog/msg/112
修复去除控制流平坦化工具deflat.py
https://www.jianshu.com/p/cf72c6128b7a