首页
社区
课程
招聘
[原创]Angr符号执行练习--对付OLLVM Bogus Control Flow/虚假控制流
发表于: 2025-6-3 15:00 375

[原创]Angr符号执行练习--对付OLLVM Bogus Control Flow/虚假控制流

scz 活跃值
5
2025-6-3 15:00
375

☆ 背景介绍

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
#include <stdio.h>
#include <stdlib.h>
 
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;
}
#include <stdio.h>
#include <stdlib.h>
 
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;

[培训]科锐逆向工程师培训第53期2025年7月8日开班!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (2)
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2025-6-4 00:35
0
雪    币: 1505
活跃值: (2608)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
有意思的老铁
2天前
0
游客
登录 | 注册 方可回帖
返回