首页
社区
课程
招聘
[原创]OLLVM (二)条件跳转分支混淆
发表于: 2天前 81

[原创]OLLVM (二)条件跳转分支混淆

2天前
81

前言

最近学习了OLLVM,代码工程链接。我对原有代码添加了注释并做了代码分析。

目标

  1. 将函数内所有的条件跳转指令转换成间接跳转指令。
  2. 条件跳转指令和无条件跳转指令是相对的,条件跳转如:if.thenif.else

混淆前后对比

以ll文件中的语句举例。

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
double calculate(double a, double b, char op) {
    printf(calculate_print_format, a, b, op);
    switch (op) {
        case '+': return a + b;
        case '-': return a - b;
        case '*': return a * b;
        case '/':
            if (b != 0)
                return a / b;
            else
                // return 0.0 / 0.0; // NaN
                return -1; // NaN
        default:
            // return 0.0 / 0.0; // NaN
            return -1; // NaN
    }
}

对比

混淆前:

sw.bb4:                                           ; preds = %entry
  %10 = load double, ptr %b.addr, align 8
  %cmp = fcmp une double %10, 0.000000e+00
  br i1 %cmp, label %if.then, label %if.else

混淆后:

sw.bb4:                                           ; preds = %entry
  %10 = load double, ptr %b.addr, align 8
  %cmp = fcmp une double %10, 0.000000e+00
  %11 = select i1 %cmp, i64 1, i64 0
  %12 = getelementptr [2 x ptr], ptr @_Z9calculateddc_IndirectBrTargets, i64 0, i64 %11
  %EncDestAddr = load ptr, ptr %12, align 8
  %13 = getelementptr i8, ptr %EncDestAddr, i64 3224706807
  indirectbr ptr %13, [label %if.then, label %if.else]

sw.bb4模块代码对比图:

图片描述

@_Z9calculateddc_IndirectBrTargets是目标块地址加密数组:

@_Z9calculateddc_IndirectBrTargets = private global [2 x ptr] [ptr getelementptr (i8, ptr blockaddress(@_Z9calculateddc, %if.else), i64 -3224706807), ptr getelementptr (i8, ptr blockaddress(@_Z9calculateddc, %if.then), i64 -3224706807)]

解释混淆后的IR代码

  1. 首先定义了一个全局数组@_Z9calculateddc_IndirectBrTargets,它包含两个指针:

    • 第一个指针指向if.else块地址减去一个大数(3224706807)
    • 第二个指针指向if.then块地址减去同一个大数
  2. sw.bb4基本块中:

    • 加载一个double类型的值%10(它是源码中calculate函数的入参b的值)。
    • 比较%10是否不等于0.0(fcmp une)。
    • 根据比较结果选择索引:如果b≠0则返回1,否则返回0。
    • 使用索引从全局数组中获取对应下标的地址指针。
    • 加载这个指针值到%EncDestAddr,它是加密后的地址
    • 然后将这个地址加上之前减去的同一个大数3224706807结果是解密后的目标基本块的地址
    • 最后使用indirectbr指令跳转到计算出的地址,例如:indirectbr ptr %13, [label %if.then, label %if.else],语句解释:
      • ptr %13:这是间接跳转的目标地址,存储在寄存器 %13 中。这个寄存器包含了一个代码块的地址。
      • [label %if.then, label %if.else]:这是可能的目标块列表。这个列表告诉优化器哪些块可能是跳转目标(帮助优化),但运行时实际跳转目标由 %6 的值决定,不限于这两个。
  3. 这个代码是在实现某种控制流混淆(control flow obfuscation)技术:

    • 通过将真实的目标地址存储为"基地址+大偏移"的形式来隐藏。
    • 在运行时动态计算实际跳转目标。
    • 这是一种反逆向工程的保护手段。
  4. 本质上,这段代码等价于:

    if (b != 0.0) 
        goto if_then;
    else 
        goto if_else;
    

    但使用了复杂的间接跳转来实现相同功能。

这种技术使得静态分析难以确定控制流的目标,增加了逆向工程的难度。

IndirectBranch代码解析

1. 获取函数中所有以条件跳转结尾的基本块(if/else块)所跳转到的目标基本块

逻辑在NumberBasicBlock内。目标基本块下文简写为目标块

2. 目标基本块加密

L0级别目标基本块加密

逻辑在getIndirectTargets0函数内。根据目标块的地址加上一个随机的偏移,计算出一个新的地址,这个地址是目标块加密后的地址,多个目标块加密后的地址组成一个加密地址数组,例如:

@_Z9calculateddc_IndirectBrTargets = private global [2 x ptr] [ptr getelementptr (i8, ptr blockaddress(@_Z9calculateddc, %if.else), i64 -3224706807), ptr getelementptr (i8, ptr blockaddress(@_Z9calculateddc, %if.then), i64 -3224706807)]

这就是上文混淆后的加密地址数组。所谓的加密是指目标块地址加了-3224706807后,就是加密了。

L1级别目标基本块加密

TODO 待分析

L2级别目标基本块加密

TODO 待分析

3. 遍历函数中的所有基本块,替换其中的条件分支为间接跳转

概览

遍历函数中的所有基本块找到其中的条件分支,判断条件分支的所有后继模块是否被第1步收集到,被收集到的情况下将其替换为间接跳转,转换前后对比:

LLVM条件跳转分支混淆对比图

3.1 从地址加密数组内读取目标块加密后的地址

相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
 BBNumbering[Successor]返回的是后继基本块在 地址加密数组(DestBBs) 内的索引,
 下面会根据索引从数组内读取加密后的地址
 */
Value *const TIdx = ConstantInt::get(intType, BBNumbering[Successor0]);
Value *const FIdx = ConstantInt::get(intType, BBNumbering[Successor1]);
 
// 创建IR构建器
IRBuilder<> IRB(BI);
 
// 获取条件值和两个目标基本块的索引
Value *const Cond = BI->getCondition();
/*
 创建一个 select 指令(条件选择操作)。
 举例:%11 = select i1 %cmp, i64 0, i64 1
 */
Value *const Idx = IRB.CreateSelect(Cond, TIdx, FIdx);
 
/*
 计算 加密后的目标地址 的指针,即指针的指针(二级指针),
 相当于 C 语言中的:ptr* elem_addr = &global_array[index]
 */
Value *const GEP = IRB.CreateGEP(
    DestBBs->getValueType(), DestBBs,
    {Zero, Idx});
 
/*
 创建load语句,举例:%EncDestAddr = load ptr, ptr %12, align 8
 从上一步返回的二级指针,获取加密后的目标地址
 */
Value *const EncDestAddr = IRB.CreateLoad(
    GEP->getType(),
    GEP,
    "EncDestAddr");

3.2 解密地址

L1级别目标基本块解密

相关代码:

1
2
3
4
5
6
7
8
9
// 计算解密密钥
Value *DecKey = DecodeConstantKey;
 
......
 
// 加上某个值解密目标地址。举例:%13 = getelementptr i8, ptr %EncDestAddr, i64 1992839860
Value *DestAddr = IRB.CreateGEP(
    Type::getInt8Ty(Ctx),
    EncDestAddr, DecKey);

3.3 间接跳转指令替换原分支指令

相关代码:

1
2
3
4
5
6
7
8
// 创建间接跳转指令并替换原分支
IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
// 间接跳转指令添加第一个后继
IBI->addDestination(BI->getSuccessor(0));
// 间接跳转指令添加第二个后继
IBI->addDestination(BI->getSuccessor(1));
// 替换指令
ReplaceInstWithInst(BI, IBI);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2天前 被不歪编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回