☆ 背景介绍
OLLVM支持"Bogus Control Flow/虚假控制流"。启用bcf编译源码,生成的二进制用IDA反汇编时,会看到许多实际执行时永不可达的基本块。冗余的不可达块对IDA F5带来干扰,肉眼看不出原始代码逻辑,同时不影响实际执行逻辑。bcf目的就是对抗静态分析。
OLLVM项目提供过一张示意图
上图那些条件判断,实际恒为true,执行时沿true前进,与原始逻辑无异。但静态分析时,受false干扰,并不知道false路径永不可达。
参看
作者用「angr符号执行」识别目标程序中不可达块,静态Patch目标程序,将不可达块NOP化。虽然IDA反汇编结构图仍然显乱,但F5比较智能,已能去干扰,显示出原始逻辑。
假设angr符号执行能精确识别干扰性质的条件跳转指令,我选择将所有干扰性质的条件跳转指令静态Patch成无条件跳转指令,直接跳向相应可达块;此方案本质上等价于NOP化不可达块。
本文以学习angr进阶用法为目的,借bcf反混淆为靶标,演示JMP化思路。
☆ hello.c
用某版OLLVM启用bcf编译,得到hello_bcf。
完整测试用例打包
☆ hello_bcf
IDA64反汇编hello_bcf
F5的伪代码没必要深究,看个大概即可。
☆ hello_bcf_patch.py
这是对付hello_bcf的完整代码,演示性质,非通用实现。
IDA64反汇编hello_bcf_new_*,F5查看main、sub_4014E0,已能看出hello.c所展示的代码逻辑。
☆ get_cond_jmp_list的技术原理
hello_bcf_patch.py的核心是获取那些永远只走同一条分支的条件跳转指令,与下列代码强相关
进一步说,核心代码是
是否hook子函数,要看子函数的返回值对父函数流程产生何种影响。单就hello_bcf而言,是否hook子函数,不影响最终结果,hello_bcf_new_*是一样的。
假设需要hook子函数,在successors()中完成,不必遍历"active stash",不必动用block.capstone.insns。
发现永远只走同一条分支的条件跳转指令后,记录(from,to);将来对from处的条件跳转指令(比如jnz)进行Patch,改成jmp,演示时假设均可用"eb xx"短跳转。
重载successors()的方案,带有巨大的Hacking性,只用hello_bcf测试过,仅为PoC,非健壮通用实现。不论目标样本如何变,本例展示的angr技术始终派得上用场,只是反混淆逻辑要case by case。
建议动态调试,加强理解angr流程。
创建: 2025-05-28 17:14
更新: 2025-06-03 14:31
目录:
☆ 背景介绍
☆ hello.c
☆ hello_bcf
☆ hello_bcf_patch.py
☆ get_cond_jmp_list的技术原理
创建: 2025-05-28 17:14
更新: 2025-06-03 14:31
目录:
☆ 背景介绍
☆ hello.c
☆ hello_bcf
☆ hello_bcf_patch.py
☆ get_cond_jmp_list的技术原理
原始流程
entry
|
______v______
| original |
|_____________|
|
v
return
原始流程
entry
|
______v______
| original |
|_____________|
|
v
return
启用bcf之后的流程
entry
|
____v_____
|condition*| (false)
|__________|----+
(true)| |
| |
______v______ |
+-->| original* | |
| |_____________| (true)
| (false)| !-----------> return
| ______v______ |
| | altered |<--!
| |_____________|
|__________|
启用bcf之后的流程
entry
|
____v_____
|condition*| (false)
|__________|----+
(true)| |
| |
______v______ |
+-->| original* | |
| |_____________| (true)
| (false)| !-----------> return
| ______v______ |
| | altered |<--!
| |_____________|
|__________|
利用angr符号执行去除虚假控制流 - 34r7hm4n [2021-02-10]
https://bbs.kanxue.com/thread-266005.htm
利用angr符号执行去除虚假控制流 - 34r7hm4n [2021-02-10]
https://bbs.kanxue.com/thread-266005.htm
NOP化之后的流程
entry
|
____v_____
|condition*| (false)
|__________|----+
(true)| |
| |
______v______ |
+-->| original* | |
| |_____________| (true)
| (false)| !-----------> return
| ______v______ |
| | nop |<--!
| |_____________|
|__________|
NOP化之后的流程
entry
|
____v_____
|condition*| (false)
|__________|----+
(true)| |
| |
______v______ |
+-->| original* | |
| |_____________| (true)
| (false)| !-----------> return
| ______v______ |
| | nop |<--!
| |_____________|
|__________|
JMP化之后的流程
entry
|
____v_____
|condition*|
|__________|
(jmp)|
|
______v______
+-->| original* |
| |_____________| (jmp)
| !-----------> return
| ______ ______
| | altered |
| |_____________|
|__________|
JMP化之后的流程
entry
|
____v_____
|condition*|
|__________|
(jmp)|
|
______v______
+-->| original* |
| |_____________| (jmp)
| !-----------> return
| ______ ______
| | altered |
| |_____________|
|__________|
$ pip3 show angr | grep Version
Version: 9.2.125.dev0
$ pip3 show angr | grep Version
Version: 9.2.125.dev0
static unsigned int foo ( unsigned int n )
{
unsigned int mod = n % 4;
unsigned int ret = 0;
if ( mod == 0 )
{
ret = ( n | 0xbaaad0bf ) * ( 2 ^ n );
}
else if ( mod == 1 )
{
ret = ( n & 0xbaaad0bf ) * ( 3 + n );
}
else if ( mod == 2 )
{
ret = ( n ^ 0xbaaad0bf ) * ( 4 | n );
}
else
{
ret = ( n + 0xbaaad0bf ) * ( 5 & n );
}
return ret;
}
int main ( int argc, char * argv[] )
{
unsigned int n,
ret;
if ( argc < 2 )
{
fprintf( stderr, "Usage: %s <num>\n", argv[0] );
return -1;
}
n = (unsigned int)strtoul( argv[1], NULL, 0 );
ret = foo( n );
fprintf( stdout, "n=%#x ret=%#x\n", n, ret );
return 0;
}
static unsigned int foo ( unsigned int n )
{
unsigned int mod = n % 4;
unsigned int ret = 0;
if ( mod == 0 )
{
ret = ( n | 0xbaaad0bf ) * ( 2 ^ n );
}
else if ( mod == 1 )
{
ret = ( n & 0xbaaad0bf ) * ( 3 + n );
}
else if ( mod == 2 )
{
ret = ( n ^ 0xbaaad0bf ) * ( 4 | n );
}
else
{
ret = ( n + 0xbaaad0bf ) * ( 5 & n );
}
return ret;
}
int main ( int argc, char * argv[] )
{
unsigned int n,
ret;
if ( argc < 2 )
{
fprintf( stderr, "Usage: %s <num>\n", argv[0] );
return -1;
}
n = (unsigned int)strtoul( argv[1], NULL, 0 );
ret = foo( n );
fprintf( stdout, "n=%#x ret=%#x\n", n, ret );
return 0;
}
clang -pipe -O0 -s -mllvm -passes=bcf -o hello_bcf hello.c
clang -pipe -O0 -s -mllvm -passes=bcf -o hello_bcf hello.c
https://scz.617.cn/unix/202505281714.txt
https://scz.617.cn/unix/202505281714.7z
https://scz.617.cn/unix/202505281714.txt
https://scz.617.cn/unix/202505281714.7z
$ file -b hello_bcf
ELF 64-bit LSB executable, x86-64, ..., stripped
$ file -b hello_bcf
ELF 64-bit LSB executable, x86-64, ..., stripped
__int64 __fastcall main(int a1, char **a2, char **a3)
{
...
v24 = a1;
v25 = a2;
//
// unk_4040??位于.bss,实际运行时初始化为0。下列布尔值恒false
//
if ( ((unk_404098 * (unk_404098 + 1)) & 1) != 0 && unk_4040F8 >= 10 )
//
// 此跳转永不发生
//
goto LABEL_11;
while ( 1 )
{
v3 = v25;
v19 = (unsigned int *)(&v17 - 2);
v20 = (const char ***)(&v17 - 2);
v21 = (unsigned int *)(&v17 - 2);
v22 = (unsigned int *)(&v17 - 2);
*((_DWORD *)&v17 - 4) = 0;
v17 = v3;
v23 = (int)v3 < 2;
//
// 下列布尔值恒true
//
if ( ((unk_404120 * (unk_404120 + 1)) & 1) == 0 || unk_4040C4 < 10 )
//
// 此break必发生
//
break;
LABEL_11:
//
// 永可不达的基本块
//
LODWORD(v17) = v24;
*(&v17 - 2) = v25;
}
if ( v23 )
{
//
// 下列布尔值恒false
//
if ( ((unk_40411C * (unk_40411C + 1)) & 1) != 0 && unk_4040C0 >= 10 )
//
// 此跳转永不发生
//
goto LABEL_12;
while ( 1 )
{
fprintf(stderr, "Usage: %s <num>\n", **v20);
*v19 = -1;
//
// 下列布尔值恒true
//
if ( ((unk_404110 * (unk_404110 + 1)) & 1) == 0 || unk_4040B8 < 10 )
//
// 此break必发生
//
break;
LABEL_12:
//
// 永可不达的基本块
//
fprintf(stderr, "Usage: %s <num>\n", **v20);
*v19 = -1;
}
}
else
{
...
}
do
v18 = *v19;
//
// 下列布尔值恒false
//
while ( ((unk_4040E8 * (unk_4040E8 + 1)) & 1) != 0 && unk_404138 >= 10 );
return v18;
}
__int64 __fastcall main(int a1, char **a2, char **a3)
{
...
v24 = a1;
v25 = a2;
//
// unk_4040??位于.bss,实际运行时初始化为0。下列布尔值恒false
//
if ( ((unk_404098 * (unk_404098 + 1)) & 1) != 0 && unk_4040F8 >= 10 )
//
// 此跳转永不发生
//
goto LABEL_11;
while ( 1 )
{
v3 = v25;
v19 = (unsigned int *)(&v17 - 2);
v20 = (const char ***)(&v17 - 2);
v21 = (unsigned int *)(&v17 - 2);
v22 = (unsigned int *)(&v17 - 2);
*((_DWORD *)&v17 - 4) = 0;
v17 = v3;
v23 = (int)v3 < 2;
//
// 下列布尔值恒true
//
if ( ((unk_404120 * (unk_404120 + 1)) & 1) == 0 || unk_4040C4 < 10 )
//
// 此break必发生
//
break;
LABEL_11:
//
// 永可不达的基本块
//
LODWORD(v17) = v24;
*(&v17 - 2) = v25;
}
if ( v23 )
{
//
// 下列布尔值恒false
//
if ( ((unk_40411C * (unk_40411C + 1)) & 1) != 0 && unk_4040C0 >= 10 )
//
// 此跳转永不发生
//
goto LABEL_12;
while ( 1 )
{
fprintf(stderr, "Usage: %s <num>\n", **v20);
*v19 = -1;
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!