首页
社区
课程
招聘
14
[原创] OLLVM 攻略笔记
发表于: 2025-3-30 22:19 4887

[原创] OLLVM 攻略笔记

2025-3-30 22:19
4887

在现代软件保护技术中,控制流混淆(Control Flow Obfuscation)是一种常见且有效的手段,用于增加逆向工程的难度。OLLVM 是基于 LLVM 编译器框架的一个扩展,它通过插入复杂的控制流混淆逻辑,使得生成的二进制代码难以被分析和理解。间接调用和控制流平坦化是 OLLVM 中最具代表性的混淆技术之一,它通过将程序的控制流打散并引入调度器逻辑,极大地增加了逆向工程的复杂性。

本篇笔记将从以下几个方面展开:

通过这些内容,希望能够帮助读者更好地理解 OLLVM 的工作原理,并掌握应对复杂混淆技术的分析方法,毕竟有谁能拒绝参加一场顶级玩家之间的攻防游戏呢?

下载网络上大佬开源的 ollvm 项目,goron 是我实际测试比较好用的版本,提供了额外的几种强度不错的混淆 Pass,选择 9.0 版本则是考虑到这个版本网络上可供学习的资料比较多,非常适合学习使用。

没有 clion的可以用命令行编译 clang,生成 release 编译配置文件

编译 release

将 ndk 中的 clang 加入环境变量

使用编译器打开 ollvm/llvm-project-9.0.1/llvm/CMakeLists.txt

编译器 file — settings — Build,Execution,Deployment — Cmake — Cmake options,配置 Cmake, 添加 Release 选项并设置 CMake options 如下

编译器主界面将工程切换为 clang 并将模式切换为 Release 模式,最后点击运行开始编译

使用 VSCode 打开 ollvm/llvm-project-9.0.1

新建文件夹 .vscode

创建文件 .vscode/launch.json

创建文件 .vscode/tasks.json

运行和调试 --> 启动

将 clang 添加到临时环境变量中

测试编译

交叉编译 arm64 需要先安装 gcc-aarch64 和 g++-aarch64

交叉编译 arm64

Ollvm 编译 X86 版本的可执行文件

Ollvm 编Arm64 版本的可执行文件

下载 android-ndk-r21e

将之前编译的 ollvm 中的 lib 和 bin 文件夹拷贝到 ndk 文件夹中

在 android studio 工程中 local.properties 中自定义 ndk 路径

在 CMakeLists.txt 中添加编译时使用的 ollvm 命令

c

在 LLVM 编译过程中,首先需要将 .c 文件通过前端编译器(如 Clang)转换为 LLVM IR(中间表示)。

ll

LLVM Assembly 文件是一种类似于汇编语言的中间表示。它是一种人类可读的文本格式,用于表示 LLVM IR。这种格式在跨平台上具有一定的可移植性。

bc

LLVM Bitcode 文件是一种中间表示的二进制格式,具有跨平台和跨编译器的特性。.bc 文件包含了经过前端编译器生成的 LLVM IR,但已经被编译成了一种更加紧凑的二进制形式。

s

汇编语言文件 .s 文件是汇编语言程序的源代码文件,包含了目标机器的汇编指令。在 LLVM 编译过程的最后阶段,LLVM IR 会被转换为特定目标机器的汇编代码,存储在 .s 文件中。这种格式通常不跨平台,因为汇编指令是特定于目标体系结构的。

生成 ll 文件

ll 文件也是可以执行的(需要在clion中先将 lli 编译出来)

生成 bc 文件(需要在clion中先将 llvm-as 编译出来)

生成 s 文件 (需要在clion中先将 llc 编译出来)

s 文件生成可执行文件

bc 文件生成可执行文件

ll 文件生成可执行文件

这部分除了理解 IR 汇编的基本语法,还必须理解 IR 语言中代码块的概念,这样我们才能理解 OLLVM 中最难的控制流平坦化的技术原理

C 代码

IR 汇编

C 代码

IR 汇编

C 代码

IR 汇编

C 代码

IR 汇编

这里只给出笔者认为比较重要的代码片段,其中注释多为 AI 分析(让 AI 辅助我们理解复杂的工程是个很棒的主意),也包含少量的笔者在分析时插入的日志 LOG,笔者总结的经验就是在分析的时候多插入LOG,在关键代码前后多打印对应的 IR 汇编,对比前后的区别,将下面的任意一个 Pass 分析透彻后,熟悉了 LLVM 操作 IR 汇编的函数,接下来的源码分析会越来越得心应手。

核心功能:加密函数调用地址,将 bl lable 指令转变为 blr reg ,抵抗反编译器的静态分析

核心功能:加密块间的调用地址,将 b lable 指令转变为 br reg,抵抗反编译器的静态分析

核心功能:控制流平坦化,OLLVM 中最核心的保护手段,将函数中的所有代码块编号放到一个巨大的 while switch case 中进行分发执行,抵抗反编译器的静态分析

Unicorn 是一个模拟 CPU 执行的框架,Unicorn 在 OLLVM 反混淆中的作用为计算 BR 寄存器的值以及控制流平坦化中作为 SWITCH ID 的寄存器的值,这里附上一段笔者学习 Unicorn 的代码,希望可以帮助大家理解 Unicorn 的用法。

flare-emu 的核心功能是通过 Unicorn 的仿真能力,为 IDA 二进制分析提供强大的动态仿真支持。它支持多种架构(包括 x86x86_64ARMARM64),并提供了五种主要的仿真接口,以及一系列相关的辅助和实用函数。

项目地址:https://github.com/mandiant/flare-emu.git

C 代码

编译三种混淆的二进制文件

IDA 打开 main_icall 中的 main 函数可以发现函数 add5 的跳转已经被加密

image-20250330174115411

这里我们可以发现原本的 BL 指令被替换成了 BLR 指令,这里我们只需要利用 flare_emu模拟执行一下函数,利用 iterate 方法强行执行到目标地址就可以获取目标寄存器的值了,在最后将 BLR 指令改回 BL 指令就可以了

image-20250330174235860

根据观察,函数跳转的地址通过模拟执行还是比较好计算出来的,这里我的思路如下:

使用 flare_emu 的 iterate 方法,从指定的地址范围开始模拟执行

targetCallBack 回调中记录每个间接调用指令的目标地址

修复间接调用

IDA 已经可以解析出函数 add 的地址

image-20250330185323389

IDA 打开 main_indbr 中的 add5 函数可以发现函数主体无法被解析

image-20250329102546345

这里我们可以发现原本的 BL 指令被替换成了 BLR 指令,不过这里就不能像 icall 那样粗暴的直接计算地址了,因为这里可能会涉及到多个分支的问题。

image-20250330194337897

通过观察我发现一般只有两个分支,所以我这里的还原思路如下:

使用 flare_emu 的 emulateFrom 方法,从函数起始地址模拟执行到 BR 指令:

在模拟过程中,通过 my_instruction_hook 拦截指令,记录 CSEL 或 CSET 指令的地址,需要注意有两个分支。

根据 CSELCSET 指令的类型,修复为条件跳转指令

IDA 已经可以解析出函数 add5 的函数主体

image-20250330200033584

IDA 打开 main_indbr 中的 add5 函数可以发现函数逻辑难以理解

image-20250330133618450

控制流平坦化作为 ollvm 中最重要的 Pass,也是最难以还原的,我这里的还原思路如下:

image-20250330200817934

IDA 已经可以解析出函数 add5 的函数逻辑

image-20250330133542910

通过本次学习和实践,笔者对 OLLVM 的编译、使用以及其混淆技术有了更深入的理解。具体来说:

这次学习让笔者认识到,面对复杂的混淆技术,工具的选择和分析思路的清晰性至关重要。同时也证明了混淆技术虽然强大,但并非不可破解。希望本篇笔记能够为其他研究 OLLVM 的同学提供一些参考和帮助。

细品sec2023安卓赛题

llvm学习笔记——llvm基础

初窺ARM平坦化還原

自实现一个LLVM Pass以及OLLVM简单的魔改

从源码视角分析Arkari间接跳转混淆

OLLVM控制流平坦化混淆还原

ARM64 目前主流的反混淆技术的初窥

ollvm_assets.zip

环境 配置
VMware 16.1.1 build-17801498
Ubuntu Ubuntu 20.04.2 LTS
物理机内存 32G
虚拟机内存 16G
物理机储存 2T
虚拟机内存 1T
目标版本 llvm-project-9.0
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig libncurses5 cmake ninja-build
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 libncurses5 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig libncurses5 cmake ninja-build
git clone https://github.com/amimo/goron.git
git checkout e943a8a78325632df64988e05d66ad5fa0e0c6f6
git clone https://github.com/amimo/goron.git
git checkout e943a8a78325632df64988e05d66ad5fa0e0c6f6
cd llvm-project-9.0.1
mkdir build_release
cd build_release
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release  -DLLVM_ENABLE_PROJECTS="clang" ../llvm
cd llvm-project-9.0.1
mkdir build_release
cd build_release
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release  -DLLVM_ENABLE_PROJECTS="clang" ../llvm
ninja -j16
ninja -j16
export PATH=/home/lxz/ollvm/llvm-project/build_release/bin:$PATH
export PATH=/home/lxz/ollvm/llvm-project/build_release/bin:$PATH
-G Ninja -DLLVM_ENABLE_PROJECTS="clang"
-G Ninja -DLLVM_ENABLE_PROJECTS="clang"
export PATH=/home/lxz/ollvm/llvm-project/cmake-build-release/bin::$PATH
export PATH=/home/lxz/ollvm/llvm-project/cmake-build-release/bin::$PATH
clang hello.c -o hello
clang hello.c -o hello
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
clang hello.c -o hello -target aarch64-linux-gnu
clang hello.c -o hello -target aarch64-linux-gnu
# 开启指令替换、控制流平坦化、虚假控制流
clang hello.c -o hello -mllvm -sub -mllvm -bcf -mllvm -fla
# 开启指令替换、控制流平坦化、虚假控制流
clang hello.c -o hello -mllvm -sub -mllvm -bcf -mllvm -fla
# 开启指令替换、控制流平坦化、虚假控制流
clang hello.c -o hello -mllvm -sub -mllvm -bcf -mllvm -fla -target aarch64-linux-gnu
# 开启指令替换、控制流平坦化、虚假控制流
clang hello.c -o hello -mllvm -sub -mllvm -bcf -mllvm -fla -target aarch64-linux-gnu
wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip
unzip android-ndk-r21e-linux-x86_64.zip
wget https://dl.google.com/android/repository/android-ndk-r21e-linux-x86_64.zip
unzip android-ndk-r21e-linux-x86_64.zip
cp /home/lxz/ollvm/llvm-project/build_debug/lib /home/lxz/ollvm/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/lib
cp /home/lxz/ollvm/llvm-project/build_debug/bin /home/lxz/ollvm/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin
cp /home/lxz/ollvm/llvm-project/build_debug/lib /home/lxz/ollvm/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/lib
cp /home/lxz/ollvm/llvm-project/build_debug/bin /home/lxz/ollvm/android-ndk-r21e/toolchains/llvm/prebuilt/linux-x86_64/bin
ndk.dir =/home/lxz/ollvm/android-ndk-r21e
ndk.dir =/home/lxz/ollvm/android-ndk-r21e
# 只开启控制流平坦化
add_definitions(-mllvm -fla)
 
# 开启指令替换、控制流平坦化、虚假控制流
add_definitions(-mllvm -sub -mllvm -bcf -mllvm -fla)
 
# 指定控制流平坦化和虚假控制流的次数
add_definitions(-mllvm -sub -mllvm -sub_loop=3 -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -fla -mllvm -split_num=3)
# 只开启控制流平坦化
add_definitions(-mllvm -fla)
 
# 开启指令替换、控制流平坦化、虚假控制流
add_definitions(-mllvm -sub -mllvm -bcf -mllvm -fla)
 
# 指定控制流平坦化和虚假控制流的次数
add_definitions(-mllvm -sub -mllvm -sub_loop=3 -mllvm -bcf -mllvm -bcf_loop=3 -mllvm -fla -mllvm -split_num=3)
export PATH=~/ollvm/llvm-project-9.0.1/llvm/cmake-build-release/bin::$PATH
clang -emit-llvm -S hello.c -o hello.ll
export PATH=~/ollvm/llvm-project-9.0.1/llvm/cmake-build-release/bin::$PATH
clang -emit-llvm -S hello.c -o hello.ll
lli hello.ll
lli hello.ll
llvm-as hello.ll -o hello.bc
llvm-as hello.ll -o hello.bc
llc hello.bc -o hello.s
llc hello.bc -o hello.s
clang hello.s -o hello_s
clang hello.s -o hello_s
clang hello.bc -o hello_bc
clang hello.bc -o hello_bc
clang hello.ll -o hello_ll
clang hello.ll -o hello_ll
int fun1(int a, int b){
    return a+b;
}
int fun1(int a, int b){
    return a+b;
}
; dso_local:这是一个链接属性,表示该函数是局部的,仅在当前模块中可见。
; i32:表示函数的返回类型是 32 位整数。
;@_Z4fun1ii(i32, i32):这是函数的名称和参数列表,@ 是函数名的标识符。
#0:这是一个属性组的引用,表示该函数具有一些特定的属性(如调用约定等),但具体属性需要查看属性组的定义。
; 参数优先分配 %0%1%2%3 ...
define dso_local i32 @_Z4fun1ii(i32, i32) #0 {
   
  ; 在栈上分配一个 i32 类型的内存空间,对齐到 4 字节边界,标记为 %3
  %3 = alloca i32, align 4
   
  ; 在栈上分配一个 i32 类型的内存空间,对齐到 4 字节边界,标记为 %4
  %4 = alloca i32, align 4
   
  ; 将 %0 的值存储到 %3 指向的内存中,4 字节对齐
  store i32 %0, i32* %3, align 4
   
  ; 将 %1 的值存储到 %4 指向的内存中, 4 字节对齐
  store i32 %1, i32* %4, align 4
   
  ; 从 %3 指向的内存中加载一个 i32 类型的值,存储到临时变量 %5 中,4 字节对齐
  %5 = load i32, i32* %3, align 4
   
  ; 从 %4 指向的内存中加载一个 i32 类型的值,存储到临时变量 %6 中,4 字节对齐
  %6 = load i32, i32* %4, align 4
   
  ; 将 %5 %6 中的值相加,结果存储到临时变量 %7 中,加法操作不会检查有符号溢出
  %7 = add nsw i32 %5, %6
   
  ; 返回函数的结果,即 %7 中存储的加法结果
  ret i32 %7
}
; dso_local:这是一个链接属性,表示该函数是局部的,仅在当前模块中可见。
; i32:表示函数的返回类型是 32 位整数。
;@_Z4fun1ii(i32, i32):这是函数的名称和参数列表,@ 是函数名的标识符。
#0:这是一个属性组的引用,表示该函数具有一些特定的属性(如调用约定等),但具体属性需要查看属性组的定义。
; 参数优先分配 %0%1%2%3 ...
define dso_local i32 @_Z4fun1ii(i32, i32) #0 {
   
  ; 在栈上分配一个 i32 类型的内存空间,对齐到 4 字节边界,标记为 %3
  %3 = alloca i32, align 4
   
  ; 在栈上分配一个 i32 类型的内存空间,对齐到 4 字节边界,标记为 %4
  %4 = alloca i32, align 4
   
  ; 将 %0 的值存储到 %3 指向的内存中,4 字节对齐
  store i32 %0, i32* %3, align 4
   
  ; 将 %1 的值存储到 %4 指向的内存中, 4 字节对齐
  store i32 %1, i32* %4, align 4
   
  ; 从 %3 指向的内存中加载一个 i32 类型的值,存储到临时变量 %5 中,4 字节对齐
  %5 = load i32, i32* %3, align 4
   
  ; 从 %4 指向的内存中加载一个 i32 类型的值,存储到临时变量 %6 中,4 字节对齐
  %6 = load i32, i32* %4, align 4
   
  ; 将 %5 %6 中的值相加,结果存储到临时变量 %7 中,加法操作不会检查有符号溢出
  %7 = add nsw i32 %5, %6
   
  ; 返回函数的结果,即 %7 中存储的加法结果
  ret i32 %7
}
int fun2(int a, int b){
    int ret = 0;
    for(int i = 0; i < 5; i++){
        ret += a;
    }
    return ret+b;
}
int fun2(int a, int b){
    int ret = 0;
    for(int i = 0; i < 5; i++){
        ret += a;
    }
    return ret+b;
}
define dso_local i32 @_Z4fun2ii(i32, i32) #0 {
  %3 = alloca i32, align 4          (参数1
  %4 = alloca i32, align 4          (参数2
  %5 = alloca i32, align 4          (中间计算结果)
  %6 = alloca i32, align 4          (循环计数器)
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  store i32 0, i32* %5, align 4
  store i32 0, i32* %6, align 4
   
  ; 无条件跳转到标签 %7,开始循环
  br label %7
 
;这部分对应 i < 5
7:                                                ; preds = %14, %2
  %8 = load i32, i32* %6, align 4
   
  ; 比较 %8 是否小于 5,结果存放到 %9%9 为布尔值
  ; %9 = (bool)(%8 < 5)
  %9 = icmp slt i32 %8, 5
   
  ; 根据比较结果布尔类型的 %9 跳转到标签 %10 %17
  ; if(%9){
  ;     br label %10
  ; }else{
  ;     br label %17
  ; }
  br i1 %9, label %10, label %17
 
; 这部分对应 ret += a
10:                                               ; preds = %7
  ; 从 %3 指向的内存中加载第一个参数的值
  %11 = load i32, i32* %3, align 4
   
  ; 从 %5 指向的内存中加载中间计算结果的值
  %12 = load i32, i32* %5, align 4
   
  ; 将中间计算结果 %12 和第一个参数 %11 相加,结果存储到 %13
  %13 = add nsw i32 %12, %11
   
  ; 将新的中间计算结果 %13 存储到 %5 指向的内存中
  store i32 %13, i32* %5, align 4
   
  ; 跳转到标签 %14,继续循环
  br label %14
 
; 这部分对应 i++
14:                                               ; preds = %10
  ; 从 %6 指向的内存中加载循环计数器的值
  %15 = load i32, i32* %6, align 4
   
  ; 将循环计数器 %15 1,结果存储到 %16
  %16 = add nsw i32 %15, 1
   
  ; 将新的循环计数器值 %16 存储到 %6 指向的内存中
  store i32 %16, i32* %6, align 4
   
  ; 跳转回标签 %7,继续循环
  br label %7
 
;这部分对应 return ret+b;
17:                                               ; preds = %7
 
  ; 从 %5 指向的内存中加载最终的中间计算结果
  %18 = load i32, i32* %5, align 4
   
  ; 从 %4 指向的内存中加载第二个参数的值
  %19 = load i32, i32* %4, align 4
   
  ; 将中间计算结果 %18 和第二个参数 %19 相加,结果存储到 %20
  %20 = add nsw i32 %18, %19
   
  ; 返回函数的结果,即 %20 中存储的值
  ret i32 %20
}
define dso_local i32 @_Z4fun2ii(i32, i32) #0 {
  %3 = alloca i32, align 4          (参数1
  %4 = alloca i32, align 4          (参数2
  %5 = alloca i32, align 4          (中间计算结果)
  %6 = alloca i32, align 4          (循环计数器)
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  store i32 0, i32* %5, align 4
  store i32 0, i32* %6, align 4
   
  ; 无条件跳转到标签 %7,开始循环
  br label %7
 
;这部分对应 i < 5
7:                                                ; preds = %14, %2
  %8 = load i32, i32* %6, align 4
   
  ; 比较 %8 是否小于 5,结果存放到 %9%9 为布尔值
  ; %9 = (bool)(%8 < 5)
  %9 = icmp slt i32 %8, 5
   
  ; 根据比较结果布尔类型的 %9 跳转到标签 %10 %17
  ; if(%9){
  ;     br label %10
  ; }else{
  ;     br label %17
  ; }
  br i1 %9, label %10, label %17
 
; 这部分对应 ret += a
10:                                               ; preds = %7
  ; 从 %3 指向的内存中加载第一个参数的值
  %11 = load i32, i32* %3, align 4
   
  ; 从 %5 指向的内存中加载中间计算结果的值
  %12 = load i32, i32* %5, align 4
   
  ; 将中间计算结果 %12 和第一个参数 %11 相加,结果存储到 %13
  %13 = add nsw i32 %12, %11
   
  ; 将新的中间计算结果 %13 存储到 %5 指向的内存中
  store i32 %13, i32* %5, align 4
   
  ; 跳转到标签 %14,继续循环
  br label %14
 
; 这部分对应 i++
14:                                               ; preds = %10
  ; 从 %6 指向的内存中加载循环计数器的值
  %15 = load i32, i32* %6, align 4
   
  ; 将循环计数器 %15 1,结果存储到 %16
  %16 = add nsw i32 %15, 1
   
  ; 将新的循环计数器值 %16 存储到 %6 指向的内存中
  store i32 %16, i32* %6, align 4
   
  ; 跳转回标签 %7,继续循环
  br label %7
 
;这部分对应 return ret+b;
17:                                               ; preds = %7
 
  ; 从 %5 指向的内存中加载最终的中间计算结果
  %18 = load i32, i32* %5, align 4
   
  ; 从 %4 指向的内存中加载第二个参数的值
  %19 = load i32, i32* %4, align 4
   
  ; 将中间计算结果 %18 和第二个参数 %19 相加,结果存储到 %20
  %20 = add nsw i32 %18, %19
   
  ; 返回函数的结果,即 %20 中存储的值
  ret i32 %20
}
int fun3(int a, int b){
    return fun2(a, b);
}
int fun3(int a, int b){
    return fun2(a, b);
}
define dso_local i32 @_Z4fun3ii(i32, i32) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4
  %6 = load i32, i32* %4, align 4
  ; 调用函数 _Z4fun2ii,将 %5 %6 作为参数传递,返回值存储到临时变量 %7
  %7 = call i32 @_Z4fun2ii(i32 %5, i32 %6)
  ret i32 %7
}
define dso_local i32 @_Z4fun3ii(i32, i32) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  store i32 %0, i32* %3, align 4
  store i32 %1, i32* %4, align 4
  %5 = load i32, i32* %3, align 4
  %6 = load i32, i32* %4, align 4
  ; 调用函数 _Z4fun2ii,将 %5 %6 作为参数传递,返回值存储到临时变量 %7
  %7 = call i32 @_Z4fun2ii(i32 %5, i32 %6)
  ret i32 %7
}
int fun4(int a, int b){
    if(a > 5){
        return fun2(a, b);
    }else{
        return fun3(a, b);
    }
}
int fun4(int a, int b){
    if(a > 5){
        return fun2(a, b);
    }else{
        return fun3(a, b);
    }
}
define dso_local i32 @_Z4fun4ii(i32, i32) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i32, align 4
  store i32 %0, i32* %4, align 4
  store i32 %1, i32* %5, align 4
  %6 = load i32, i32* %4, align 4
  %7 = icmp sgt i32 %6, 5
  br i1 %7, label %8, label %12
 
8:                                                ; preds = %2
  %9 = load i32, i32* %4, align 4
  %10 = load i32, i32* %5, align 4
  %11 = call i32 @_Z4fun2ii(i32 %9, i32 %10)
  store i32 %11, i32* %3, align 4
  br label %16
 
12:                                               ; preds = %2
  %13 = load i32, i32* %4, align 4
  %14 = load i32, i32* %5, align 4
  %15 = call i32 @_Z4fun3ii(i32 %13, i32 %14)
  store i32 %15, i32* %3, align 4
  br label %16
 
16:                                               ; preds = %12, %8
  %17 = load i32, i32* %3, align 4
  ret i32 %17
}
define dso_local i32 @_Z4fun4ii(i32, i32) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i32, align 4
  store i32 %0, i32* %4, align 4
  store i32 %1, i32* %5, align 4
  %6 = load i32, i32* %4, align 4
  %7 = icmp sgt i32 %6, 5
  br i1 %7, label %8, label %12
 
8:                                                ; preds = %2
  %9 = load i32, i32* %4, align 4
  %10 = load i32, i32* %5, align 4
  %11 = call i32 @_Z4fun2ii(i32 %9, i32 %10)
  store i32 %11, i32* %3, align 4
  br label %16
 
12:                                               ; preds = %2
  %13 = load i32, i32* %4, align 4
  %14 = load i32, i32* %5, align 4
  %15 = call i32 @_Z4fun3ii(i32 %13, i32 %14)
  store i32 %15, i32* %3, align 4
  br label %16
 
16:                                               ; preds = %12, %8
  %17 = load i32, i32* %3, align 4
  ret i32 %17
}
bool runOnFunction(Function &Fn) override {
 
  if (!toObfuscate(flag, &Fn, "icall")) { // 如果不需要混淆,返回 false
    return false;
  }
 
  if (Options && Options->skipFunction(Fn.getName())) { // 如果选项中跳过该函数,返回 false
    return false;
  }
 
  LLVMContext &Ctx = Fn.getContext(); // 获取上下文
 
  CalleeNumbering.clear(); // 清空被调用函数编号映射
  Callees.clear(); // 清空被调用函数集合
  CallSites.clear(); // 清空调用指令集合
 
  NumberCallees(Fn); // 为函数的被调用者编号
 
  if (Callees.empty()) { // 如果没有被调用函数,返回 false
    return false;
  }
 
  uint32_t V = RandomEngine.get_uint32_t() & ~3; // 生成随机数
  ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false); // 创建加密密钥常量
 
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr; // 混淆信息指针
  if (IPO) { // 如果有 IP 混淆上下文
    SecretInfo = IPO->getIPOInfo(&Fn); // 获取 IP 信息
  }
 
  Value *MySecret;
  if (SecretInfo) { // 如果有混淆信息
    MySecret = SecretInfo->SecretLI; // 使用混淆信息中的值
  } else {
    MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true); // 否则使用 0
  }
 
  ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0); // 创建零常量
  GlobalVariable *Targets = getIndirectCallees(Fn, EncKey); // 获取间接被调用者全局变量
 
  for (auto CI : CallSites) { // 遍历调用指令集合
    SmallVector<Value *, 8> Args; // 参数集合
    SmallVector<AttributeSet, 8> ArgAttrVec; // 参数属性集合
 
    CallSite CS(CI); // 创建调用站点
 
    Instruction *Call = CS.getInstruction(); // 获取调用指令
    Function *Callee = CS.getCalledFunction(); // 获取被调用函数
    FunctionType *FTy = CS.getFunctionType(); // 获取函数类型
    IRBuilder<> IRB(Call); // 创建 IR 构建器
 
    Args.clear(); // 清空参数集合
    ArgAttrVec.clear(); // 清空参数属性集合
 
    Value *Idx = ConstantInt::get(Type::getInt32Ty(Ctx), CalleeNumbering[CS.getCalledFunction()]); // 获取被调用函数编号
    Value *GEP = IRB.CreateGEP(Targets, {Zero, Idx}); // 创建元素指针获取指令
    LoadInst *EncDestAddr = IRB.CreateLoad(GEP, CI->getName()); // 创建加载指令
    Constant *X;
    if (SecretInfo) { // 如果有混淆信息
      X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey); // 计算表达式
    } else {
      X = ConstantExpr::getSub(Zero, EncKey); // 计算表达式
    }
 
    const AttributeList &CallPAL = CS.getAttributes(); // 获取调用属性列表
    CallSite::arg_iterator I = CS.arg_begin(); // 参数迭代器
    unsigned i = 0;
 
    for (unsigned e = FTy->getNumParams(); i != e; ++I, ++i) { // 遍历参数
      Args.push_back(*I); // 添加参数到集合
      AttributeSet Attrs = CallPAL.getParamAttributes(i); // 获取参数属性
      ArgAttrVec.push_back(Attrs); // 添加参数属性到集合
    }
 
    for (CallSite::arg_iterator E = CS.arg_end(); I != E; ++I, ++i) { // 遍历剩余参数
      Args.push_back(*I); // 添加参数到集合
      ArgAttrVec.push_back(CallPAL.getParamAttributes(i)); // 添加参数属性到集合
    }
 
    AttributeList NewCallPAL = AttributeList::get( // 创建新的调用属性列表
        IRB.getContext(), CallPAL.getFnAttributes(), CallPAL.getRetAttributes(), ArgAttrVec);
 
    Value *Secret = IRB.CreateSub(X, MySecret); // 创建减法指令
    Value *DestAddr = IRB.CreateGEP(EncDestAddr, Secret); // 创建元素指针获取指令
 
 
    // add 进一步增加 icall 的运算复杂度
 
    // 对 MySecret 进行一系列复杂的运算
    Value *Value1 = IRB.CreateAdd(MySecret, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *Value2 = IRB.CreateSub(MySecret, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 进一步增加复杂性
    Value *Value3 = IRB.CreateMul(Value1, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *Value4 = IRB.CreateMul(Value2, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 使用位运算
    Value *Value5 = IRB.CreateShl(Value3, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), 2)); // 左移 2 位
    Value *Value6 = IRB.CreateLShr(Value4, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), 3)); // 逻辑右移 3 位
 
    // 最终结果
    Value *FinalValue1 = IRB.CreateAdd(Value5, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *FinalValue2 = IRB.CreateSub(Value6, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 创建比较指令,比较 FinalValue1 和 FinalValue2 是否相等
    Value *CmpResult = IRB.CreateICmpEQ(FinalValue1, FinalValue2);
 
    // 将 FinalValue1 转换为 i8* 类型的指针
    Value *FinalValue1AsPtr = IRB.CreateIntToPtr(FinalValue1, Type::getInt8PtrTy(Fn.getContext()));
 
    // 使用 CreateSelect 生成 Result,确保 TrueValue 和 FalseValue 的类型一致
    Value *Result = IRB.CreateSelect(CmpResult, FinalValue1AsPtr, DestAddr);
    DestAddr = Result;
    // add
 
    Value *FnPtr = IRB.CreateBitCast(DestAddr, FTy->getPointerTo()); // 创建位转换指令
    FnPtr->setName("Call_" + Callee->getName()); // 设置名称
    CallInst *NewCall = IRB.CreateCall(FTy, FnPtr, Args, Call->getName()); // 创建新的调用指令
    NewCall->setAttributes(NewCallPAL); // 设置属性
    Call->replaceAllUsesWith(NewCall); // 替换所有使用
    Call->eraseFromParent(); // 从父基本块中移除
  }
 
  return true; // 返回 true
}
bool runOnFunction(Function &Fn) override {
 
  if (!toObfuscate(flag, &Fn, "icall")) { // 如果不需要混淆,返回 false
    return false;
  }
 
  if (Options && Options->skipFunction(Fn.getName())) { // 如果选项中跳过该函数,返回 false
    return false;
  }
 
  LLVMContext &Ctx = Fn.getContext(); // 获取上下文
 
  CalleeNumbering.clear(); // 清空被调用函数编号映射
  Callees.clear(); // 清空被调用函数集合
  CallSites.clear(); // 清空调用指令集合
 
  NumberCallees(Fn); // 为函数的被调用者编号
 
  if (Callees.empty()) { // 如果没有被调用函数,返回 false
    return false;
  }
 
  uint32_t V = RandomEngine.get_uint32_t() & ~3; // 生成随机数
  ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false); // 创建加密密钥常量
 
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr; // 混淆信息指针
  if (IPO) { // 如果有 IP 混淆上下文
    SecretInfo = IPO->getIPOInfo(&Fn); // 获取 IP 信息
  }
 
  Value *MySecret;
  if (SecretInfo) { // 如果有混淆信息
    MySecret = SecretInfo->SecretLI; // 使用混淆信息中的值
  } else {
    MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true); // 否则使用 0
  }
 
  ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0); // 创建零常量
  GlobalVariable *Targets = getIndirectCallees(Fn, EncKey); // 获取间接被调用者全局变量
 
  for (auto CI : CallSites) { // 遍历调用指令集合
    SmallVector<Value *, 8> Args; // 参数集合
    SmallVector<AttributeSet, 8> ArgAttrVec; // 参数属性集合
 
    CallSite CS(CI); // 创建调用站点
 
    Instruction *Call = CS.getInstruction(); // 获取调用指令
    Function *Callee = CS.getCalledFunction(); // 获取被调用函数
    FunctionType *FTy = CS.getFunctionType(); // 获取函数类型
    IRBuilder<> IRB(Call); // 创建 IR 构建器
 
    Args.clear(); // 清空参数集合
    ArgAttrVec.clear(); // 清空参数属性集合
 
    Value *Idx = ConstantInt::get(Type::getInt32Ty(Ctx), CalleeNumbering[CS.getCalledFunction()]); // 获取被调用函数编号
    Value *GEP = IRB.CreateGEP(Targets, {Zero, Idx}); // 创建元素指针获取指令
    LoadInst *EncDestAddr = IRB.CreateLoad(GEP, CI->getName()); // 创建加载指令
    Constant *X;
    if (SecretInfo) { // 如果有混淆信息
      X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey); // 计算表达式
    } else {
      X = ConstantExpr::getSub(Zero, EncKey); // 计算表达式
    }
 
    const AttributeList &CallPAL = CS.getAttributes(); // 获取调用属性列表
    CallSite::arg_iterator I = CS.arg_begin(); // 参数迭代器
    unsigned i = 0;
 
    for (unsigned e = FTy->getNumParams(); i != e; ++I, ++i) { // 遍历参数
      Args.push_back(*I); // 添加参数到集合
      AttributeSet Attrs = CallPAL.getParamAttributes(i); // 获取参数属性
      ArgAttrVec.push_back(Attrs); // 添加参数属性到集合
    }
 
    for (CallSite::arg_iterator E = CS.arg_end(); I != E; ++I, ++i) { // 遍历剩余参数
      Args.push_back(*I); // 添加参数到集合
      ArgAttrVec.push_back(CallPAL.getParamAttributes(i)); // 添加参数属性到集合
    }
 
    AttributeList NewCallPAL = AttributeList::get( // 创建新的调用属性列表
        IRB.getContext(), CallPAL.getFnAttributes(), CallPAL.getRetAttributes(), ArgAttrVec);
 
    Value *Secret = IRB.CreateSub(X, MySecret); // 创建减法指令
    Value *DestAddr = IRB.CreateGEP(EncDestAddr, Secret); // 创建元素指针获取指令
 
 
    // add 进一步增加 icall 的运算复杂度
 
    // 对 MySecret 进行一系列复杂的运算
    Value *Value1 = IRB.CreateAdd(MySecret, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *Value2 = IRB.CreateSub(MySecret, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 进一步增加复杂性
    Value *Value3 = IRB.CreateMul(Value1, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *Value4 = IRB.CreateMul(Value2, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 使用位运算
    Value *Value5 = IRB.CreateShl(Value3, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), 2)); // 左移 2 位
    Value *Value6 = IRB.CreateLShr(Value4, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), 3)); // 逻辑右移 3 位
 
    // 最终结果
    Value *FinalValue1 = IRB.CreateAdd(Value5, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
    Value *FinalValue2 = IRB.CreateSub(Value6, ConstantInt::get(Type::getInt32Ty(Fn.getContext()), V));
 
    // 创建比较指令,比较 FinalValue1 和 FinalValue2 是否相等
    Value *CmpResult = IRB.CreateICmpEQ(FinalValue1, FinalValue2);
 
    // 将 FinalValue1 转换为 i8* 类型的指针
    Value *FinalValue1AsPtr = IRB.CreateIntToPtr(FinalValue1, Type::getInt8PtrTy(Fn.getContext()));
 
    // 使用 CreateSelect 生成 Result,确保 TrueValue 和 FalseValue 的类型一致
    Value *Result = IRB.CreateSelect(CmpResult, FinalValue1AsPtr, DestAddr);
    DestAddr = Result;
    // add
 
    Value *FnPtr = IRB.CreateBitCast(DestAddr, FTy->getPointerTo()); // 创建位转换指令
    FnPtr->setName("Call_" + Callee->getName()); // 设置名称
    CallInst *NewCall = IRB.CreateCall(FTy, FnPtr, Args, Call->getName()); // 创建新的调用指令
    NewCall->setAttributes(NewCallPAL); // 设置属性
    Call->replaceAllUsesWith(NewCall); // 替换所有使用
    Call->eraseFromParent(); // 从父基本块中移除
  }
 
  return true; // 返回 true
}
// 重写 runOnFunction 方法,用于对每个函数进行处理
bool runOnFunction(Function &Fn) override {
  // 判断是否需要进行混淆
  if (!toObfuscate(flag, &Fn, "indbr")) {
    return false;
  }
 
  // 如果混淆选项中指定了跳过该函数,则返回
  if (Options && Options->skipFunction(Fn.getName())) {
    return false;
  }
 
  // 如果函数为空、具有链接一次的链接类型或属于启动代码段,则跳过
  if (Fn.getBasicBlockList().empty() || Fn.hasLinkOnceLinkage() || Fn.getSection() == ".text.startup") {
    return false;
  }
 
  LLVMContext &Ctx = Fn.getContext(); // 获取 LLVM 上下文
 
  // 初始化成员变量
  BBNumbering.clear();
  BBTargets.clear();
 
  // 分裂所有临界边,避免间接分支指令无法处理的情况
  SplitAllCriticalEdges(Fn, CriticalEdgeSplittingOptions(nullptr, nullptr));
  NumberBasicBlock(Fn); // 为基本块编号
 
  // 如果没有可处理的基本块,则返回
  if (BBNumbering.empty()) {
    return false;
  }
 
  // 生成随机加密密钥
  uint32_t V = RandomEngine.get_uint32_t() & ~3;
  ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false);
 
  // 获取 IPO 混淆信息
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
  if (IPO) {
    SecretInfo = IPO->getIPOInfo(&Fn);
  }
 
  // 获取混淆值
  Value *MySecret;
  if (SecretInfo) {
    MySecret = SecretInfo->SecretLI;
  } else {
    MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true);
  }
 
  // 创建间接分支目标的全局变量
  ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);
  GlobalVariable *DestBBs = getIndirectTargets(Fn, EncKey);
 
  // 遍历函数中的每个基本块
  for (auto &BB : Fn) {
    auto *BI = dyn_cast<BranchInst>(BB.getTerminator()); // 获取分支指令
    if (BI && BI->isConditional()) { // 如果是条件分支
      IRBuilder<> IRB(BI); // 创建 IR 构建器
 
      Value *Cond = BI->getCondition(); // 获取分支条件
      Value *Idx; // 用于间接分支的索引
      Value *TIdx, *FIdx; // 真分支和假分支的索引
 
      // 获取真分支和假分支的编号
      TIdx = ConstantInt::get(Type::getInt32Ty(Ctx), BBNumbering[BI->getSuccessor(0)]);
      FIdx = ConstantInt::get(Type::getInt32Ty(Ctx), BBNumbering[BI->getSuccessor(1)]);
 
      // 根据条件选择索引
      Idx = IRB.CreateSelect(Cond, TIdx, FIdx);
 
      // 计算目标地址
      Value *GEP = IRB.CreateGEP(DestBBs, {Zero, Idx});
      LoadInst *EncDestAddr = IRB.CreateLoad(GEP, "EncDestAddr");
 
      // 计算解密密钥
      Constant *X;
      if (SecretInfo) {
        X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey);
      } else {
        X = ConstantExpr::getSub(Zero, EncKey);
      }
      Value *DecKey = IRB.CreateSub(X, MySecret);
 
      // 解密目标地址
      Value *DestAddr = IRB.CreateGEP(EncDestAddr, DecKey);
 
      // 创建间接分支指令并替换原来的分支指令
      IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
      IBI->addDestination(BI->getSuccessor(0));
      IBI->addDestination(BI->getSuccessor(1));
      ReplaceInstWithInst(BI, IBI);
    }
  }
 
  return true; // 表示对函数进行了修改
}
// 重写 runOnFunction 方法,用于对每个函数进行处理
bool runOnFunction(Function &Fn) override {
  // 判断是否需要进行混淆
  if (!toObfuscate(flag, &Fn, "indbr")) {
    return false;
  }
 
  // 如果混淆选项中指定了跳过该函数,则返回
  if (Options && Options->skipFunction(Fn.getName())) {
    return false;
  }
 
  // 如果函数为空、具有链接一次的链接类型或属于启动代码段,则跳过
  if (Fn.getBasicBlockList().empty() || Fn.hasLinkOnceLinkage() || Fn.getSection() == ".text.startup") {
    return false;
  }
 
  LLVMContext &Ctx = Fn.getContext(); // 获取 LLVM 上下文
 
  // 初始化成员变量
  BBNumbering.clear();
  BBTargets.clear();
 
  // 分裂所有临界边,避免间接分支指令无法处理的情况
  SplitAllCriticalEdges(Fn, CriticalEdgeSplittingOptions(nullptr, nullptr));
  NumberBasicBlock(Fn); // 为基本块编号
 
  // 如果没有可处理的基本块,则返回
  if (BBNumbering.empty()) {
    return false;
  }
 
  // 生成随机加密密钥
  uint32_t V = RandomEngine.get_uint32_t() & ~3;
  ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false);
 
  // 获取 IPO 混淆信息
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
  if (IPO) {
    SecretInfo = IPO->getIPOInfo(&Fn);
  }
 
  // 获取混淆值
  Value *MySecret;
  if (SecretInfo) {
    MySecret = SecretInfo->SecretLI;
  } else {
    MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true);
  }
 
  // 创建间接分支目标的全局变量
  ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);
  GlobalVariable *DestBBs = getIndirectTargets(Fn, EncKey);
 
  // 遍历函数中的每个基本块
  for (auto &BB : Fn) {
    auto *BI = dyn_cast<BranchInst>(BB.getTerminator()); // 获取分支指令
    if (BI && BI->isConditional()) { // 如果是条件分支
      IRBuilder<> IRB(BI); // 创建 IR 构建器
 
      Value *Cond = BI->getCondition(); // 获取分支条件
      Value *Idx; // 用于间接分支的索引
      Value *TIdx, *FIdx; // 真分支和假分支的索引
 
      // 获取真分支和假分支的编号
      TIdx = ConstantInt::get(Type::getInt32Ty(Ctx), BBNumbering[BI->getSuccessor(0)]);
      FIdx = ConstantInt::get(Type::getInt32Ty(Ctx), BBNumbering[BI->getSuccessor(1)]);
 
      // 根据条件选择索引
      Idx = IRB.CreateSelect(Cond, TIdx, FIdx);
 
      // 计算目标地址
      Value *GEP = IRB.CreateGEP(DestBBs, {Zero, Idx});
      LoadInst *EncDestAddr = IRB.CreateLoad(GEP, "EncDestAddr");
 
      // 计算解密密钥
      Constant *X;
      if (SecretInfo) {
        X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey);
      } else {
        X = ConstantExpr::getSub(Zero, EncKey);
      }
      Value *DecKey = IRB.CreateSub(X, MySecret);
 
      // 解密目标地址
      Value *DestAddr = IRB.CreateGEP(EncDestAddr, DecKey);
 
      // 创建间接分支指令并替换原来的分支指令
      IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
      IBI->addDestination(BI->getSuccessor(0));
      IBI->addDestination(BI->getSuccessor(1));
      ReplaceInstWithInst(BI, IBI);
    }
  }
 
  return true; // 表示对函数进行了修改
}
bool Flattening::flatten(Function *f) {
  vector<BasicBlock *> origBB;  // 存储原始基本块
  BasicBlock *loopEntry;        // 进入循环的基本块
  BasicBlock *loopEnd;          // 退出循环的基本块
  LoadInst *load;               // switch 变量的加载指令
  SwitchInst *switchI;          // switch 指令
  AllocaInst *switchVar;        // switch 变量的存储位置
 
  // 生成一个随机的加扰密钥
  char scrambling_key[16];
  llvm::cryptoutils->get_bytes(scrambling_key, 16);
 
  // 降低 switch 复杂度,使用不同 LLVM 版本的适配
#if LLVM_VERSION_MAJOR * 10 + LLVM_VERSION_MINOR >= 90
  FunctionPass *lower = createLegacyLowerSwitchPass();
#else
  FunctionPass *lower = createLowerSwitchPass();
#endif
  lower->runOnFunction(*f);
 
  // 遍历所有基本块,并存储到 origBB
  for (Function::iterator i = f->begin(); i != f->end(); ++i) {
    BasicBlock *tmp = &*i;
    origBB.push_back(tmp);
    if (isa<InvokeInst>(tmp->getTerminator())) {
      return false// 遇到 invoke 指令,终止混淆
    }
  }
 
  // 如果基本块数量小于等于1,无需平坦化
  if (origBB.size() <= 1) {
    return false;
  }
 
  LLVMContext &Ctx = f->getContext();  // 获取 LLVM 上下文
 
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
  if (IPO) {
    SecretInfo = IPO->getIPOInfo(f);  // 获取 IPO 相关信息
  }
 
  Value *MySecret = SecretInfo ? SecretInfo->SecretLI : ConstantInt::get(Type::getInt32Ty(Ctx), 0);
 
  // 移除第一个基本块
  origBB.erase(origBB.begin());
 
  // 处理第一个基本块,确保其可以被 switch 控制
  Function::iterator tmp = f->begin();
  BasicBlock *insert = &*tmp;
  if (isa<BranchInst>(insert->getTerminator()) && insert->getTerminator()->getNumSuccessors() > 1) {
    BasicBlock::iterator i = insert->end();
    --i;
    if (insert->size() > 1) {
      --i;
    }
    BasicBlock *tmpBB = insert->splitBasicBlock(i, "first");
    origBB.insert(origBB.begin(), tmpBB);
  }
  insert->getTerminator()->eraseFromParent();
 
  // 创建 switch 变量并初始化
  switchVar = new AllocaInst(Type::getInt32Ty(f->getContext()), 0, "switchVar", insert);
  new StoreInst(ConstantInt::get(Type::getInt32Ty(f->getContext()), llvm::cryptoutils->scramble32(0, scrambling_key)), switchVar, insert);
 
  // 创建控制流平坦化的循环结构
  loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);
  loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);
  load = new LoadInst(switchVar, "switchVar", loopEntry);
  insert->moveBefore(loopEntry);
  BranchInst::Create(loopEntry, insert);
  BranchInst::Create(loopEntry, loopEnd);
 
  // 创建 switch 语句
  BasicBlock *swDefault = BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
  BranchInst::Create(loopEnd, swDefault);
  switchI = SwitchInst::Create(load, swDefault, 0, loopEntry);
 
  // 处理基本块跳转
  for (BasicBlock *bb : origBB) {
    bb->moveBefore(loopEnd);
    ConstantInt *numCase = ConstantInt::get(switchI->getCondition()->getType(), llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key));
    switchI->addCase(numCase, bb);
  }
 
  fixStack(f); // 修正栈结构
  lower->runOnFunction(*f);
  delete lower;
 
  return true;
}
bool Flattening::flatten(Function *f) {
  vector<BasicBlock *> origBB;  // 存储原始基本块
  BasicBlock *loopEntry;        // 进入循环的基本块
  BasicBlock *loopEnd;          // 退出循环的基本块
  LoadInst *load;               // switch 变量的加载指令
  SwitchInst *switchI;          // switch 指令
  AllocaInst *switchVar;        // switch 变量的存储位置
 
  // 生成一个随机的加扰密钥
  char scrambling_key[16];
  llvm::cryptoutils->get_bytes(scrambling_key, 16);
 
  // 降低 switch 复杂度,使用不同 LLVM 版本的适配
#if LLVM_VERSION_MAJOR * 10 + LLVM_VERSION_MINOR >= 90
  FunctionPass *lower = createLegacyLowerSwitchPass();
#else
  FunctionPass *lower = createLowerSwitchPass();
#endif
  lower->runOnFunction(*f);
 
  // 遍历所有基本块,并存储到 origBB
  for (Function::iterator i = f->begin(); i != f->end(); ++i) {
    BasicBlock *tmp = &*i;
    origBB.push_back(tmp);
    if (isa<InvokeInst>(tmp->getTerminator())) {
      return false// 遇到 invoke 指令,终止混淆
    }
  }
 
  // 如果基本块数量小于等于1,无需平坦化
  if (origBB.size() <= 1) {
    return false;
  }
 
  LLVMContext &Ctx = f->getContext();  // 获取 LLVM 上下文
 
  const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
  if (IPO) {
    SecretInfo = IPO->getIPOInfo(f);  // 获取 IPO 相关信息
  }
 
  Value *MySecret = SecretInfo ? SecretInfo->SecretLI : ConstantInt::get(Type::getInt32Ty(Ctx), 0);
 
  // 移除第一个基本块
  origBB.erase(origBB.begin());
 
  // 处理第一个基本块,确保其可以被 switch 控制
  Function::iterator tmp = f->begin();
  BasicBlock *insert = &*tmp;
  if (isa<BranchInst>(insert->getTerminator()) && insert->getTerminator()->getNumSuccessors() > 1) {
    BasicBlock::iterator i = insert->end();
    --i;
    if (insert->size() > 1) {
      --i;
    }
    BasicBlock *tmpBB = insert->splitBasicBlock(i, "first");
    origBB.insert(origBB.begin(), tmpBB);
  }
  insert->getTerminator()->eraseFromParent();
 
  // 创建 switch 变量并初始化
  switchVar = new AllocaInst(Type::getInt32Ty(f->getContext()), 0, "switchVar", insert);
  new StoreInst(ConstantInt::get(Type::getInt32Ty(f->getContext()), llvm::cryptoutils->scramble32(0, scrambling_key)), switchVar, insert);
 
  // 创建控制流平坦化的循环结构
  loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);
  loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);
  load = new LoadInst(switchVar, "switchVar", loopEntry);
  insert->moveBefore(loopEntry);
  BranchInst::Create(loopEntry, insert);
  BranchInst::Create(loopEntry, loopEnd);
 
  // 创建 switch 语句
  BasicBlock *swDefault = BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
  BranchInst::Create(loopEnd, swDefault);
  switchI = SwitchInst::Create(load, swDefault, 0, loopEntry);
 
  // 处理基本块跳转
  for (BasicBlock *bb : origBB) {
    bb->moveBefore(loopEnd);
    ConstantInt *numCase = ConstantInt::get(switchI->getCondition()->getType(), llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key));
    switchI->addCase(numCase, bb);
  }
 
  fixStack(f); // 修正栈结构
  lower->runOnFunction(*f);
  delete lower;
 
  return true;
}
环境 配置
操作系统 Windows 10
IDA 版本 7.7
Python 3.10.7
unicorn 2.1.1
keystone-engine 0.9.2
import unicorn  # 导入 Unicorn 库
import capstone  # 导入 Capstone 库
import binascii  # 导入 binascii 库
 
# 定义一个函数 print_regs,用于打印寄存器的值
def print_regs(mu):
    for i in range(unicorn.arm64_const.UC_ARM64_REG_X0, unicorn.arm64_const.UC_ARM64_REG_X30 + 1):  # 遍历 X0-X30
        print('X%d: 0x%x' % (i - unicorn.arm64_const.UC_ARM64_REG_X0, mu.reg_read(i)))
 
# 定义一个回调函数 hook_code,用于在代码执行时触发
def hook_code(mu, addr, size, user_data):
    print('-------hook code start-------')
    code = mu.mem_read(addr, size)  # 读取内存中的代码
    cp = capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM)  # 初始化 Capstone 模块
    for i in cp.disasm(code, addr):  # 反汇编代码
        print('[addr:0x%x]: %s %s' % (i.address, i.mnemonic, i.op_str))
    print_regs(mu)  # 打印寄存器的值
 
# 定义一个回调函数 hook_mem_write,用于在内存写入时触发
def hook_mem_write(mu, type, addr, size, value, user_data):
    print('-------hook mem write-------')
    if type == unicorn.UC_MEM_WRITE:  # 如果是内存写入事件
        print('memory write addr:0x%x size:%x value:0x%x' % (addr, size, value))
 
# 定义一个回调函数 hook_intr,用于在中断发生时触发
def hook_intr(mu, intno, user_data):
    print('-------hook intr start-------')
    print_regs(mu)  # 打印寄存器的值
 
# 定义一个测试函数 test_arm64
def test_arm64():
    # 定义一段 ARM64 指令集的代码
    code = b'\xe0\x03\x1f\xaa'  # mov x0, xzr (示例指令,可替换)
     
    # 初始化 Unicorn 模块
    mu = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM)
 
    # 定义内存地址、大小
    addr = 0x1000 
    size = 0x1000
     
    # 映射内存并将代码写入内存
    mu.mem_map(addr, size) 
    mu.mem_write(addr, code)
 
    # 读取内存中的代码并打印
    code_bytes = mu.mem_read(addr, len(code)) 
    print('addr:0x%x, content:%s' % (addr, binascii.b2a_hex(code_bytes)))
 
    # 设置寄存器的值
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X0, 0x100)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X1, 0x200)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X2, 0x300)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X3, 0x400)
 
    # 监听代码执行事件
    mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)
     
    # 监听内存写入事件
    mu.hook_add(unicorn.UC_HOOK_MEM_WRITE, hook_mem_write)
     
    # 监听中断事件
    mu.hook_add(unicorn.UC_HOOK_INTR, hook_intr)
 
    try:
        # 开始模拟执行代码
        mu.emu_start(addr, addr + len(code)) 
    except unicorn.UcError as e:
        print(e)
 
    # 读取内存中的数据并打印
    stack_bytes = mu.mem_read(addr, 4
    print("mem:0x%x, value:%s" % (addr, binascii.b2a_hex(stack_bytes)))
 
if __name__ == '__main__':
    test_arm64()
import unicorn  # 导入 Unicorn 库
import capstone  # 导入 Capstone 库
import binascii  # 导入 binascii 库
 
# 定义一个函数 print_regs,用于打印寄存器的值
def print_regs(mu):
    for i in range(unicorn.arm64_const.UC_ARM64_REG_X0, unicorn.arm64_const.UC_ARM64_REG_X30 + 1):  # 遍历 X0-X30
        print('X%d: 0x%x' % (i - unicorn.arm64_const.UC_ARM64_REG_X0, mu.reg_read(i)))
 
# 定义一个回调函数 hook_code,用于在代码执行时触发
def hook_code(mu, addr, size, user_data):
    print('-------hook code start-------')
    code = mu.mem_read(addr, size)  # 读取内存中的代码
    cp = capstone.Cs(capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM)  # 初始化 Capstone 模块
    for i in cp.disasm(code, addr):  # 反汇编代码
        print('[addr:0x%x]: %s %s' % (i.address, i.mnemonic, i.op_str))
    print_regs(mu)  # 打印寄存器的值
 
# 定义一个回调函数 hook_mem_write,用于在内存写入时触发
def hook_mem_write(mu, type, addr, size, value, user_data):
    print('-------hook mem write-------')
    if type == unicorn.UC_MEM_WRITE:  # 如果是内存写入事件
        print('memory write addr:0x%x size:%x value:0x%x' % (addr, size, value))
 
# 定义一个回调函数 hook_intr,用于在中断发生时触发
def hook_intr(mu, intno, user_data):
    print('-------hook intr start-------')
    print_regs(mu)  # 打印寄存器的值
 
# 定义一个测试函数 test_arm64
def test_arm64():
    # 定义一段 ARM64 指令集的代码
    code = b'\xe0\x03\x1f\xaa'  # mov x0, xzr (示例指令,可替换)
     
    # 初始化 Unicorn 模块
    mu = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM)
 
    # 定义内存地址、大小
    addr = 0x1000 
    size = 0x1000
     
    # 映射内存并将代码写入内存
    mu.mem_map(addr, size) 
    mu.mem_write(addr, code)
 
    # 读取内存中的代码并打印
    code_bytes = mu.mem_read(addr, len(code)) 
    print('addr:0x%x, content:%s' % (addr, binascii.b2a_hex(code_bytes)))
 
    # 设置寄存器的值
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X0, 0x100)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X1, 0x200)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X2, 0x300)
    mu.reg_write(unicorn.arm64_const.UC_ARM64_REG_X3, 0x400)
 
    # 监听代码执行事件
    mu.hook_add(unicorn.UC_HOOK_CODE, hook_code)
     
    # 监听内存写入事件
    mu.hook_add(unicorn.UC_HOOK_MEM_WRITE, hook_mem_write)
     
    # 监听中断事件
    mu.hook_add(unicorn.UC_HOOK_INTR, hook_intr)
 
    try:
        # 开始模拟执行代码
        mu.emu_start(addr, addr + len(code)) 
    except unicorn.UcError as e:
        print(e)
 
    # 读取内存中的数据并打印
    stack_bytes = mu.mem_read(addr, 4
    print("mem:0x%x, value:%s" % (addr, binascii.b2a_hex(stack_bytes)))
 
if __name__ == '__main__':
    test_arm64()
#include <stdio.h>
 
int add(int a, int b){
    return a + b;
}
 
int add5(int a, int b){
    int ret = 0;
    for (int i = 0; i < 5; i++){
        if(b > 10){
            ret -= add(a, b);
        }else{
            ret += add(a, b);
        }
    }
    return ret;
}
 
int main() {
    int ret = add5(1, 2);
    printf("add5 ret %d", ret);
    return 0;
}
#include <stdio.h>
 
int add(int a, int b){
    return a + b;
}
 
int add5(int a, int b){
    int ret = 0;
    for (int i = 0; i < 5; i++){
        if(b > 10){
            ret -= add(a, b);
        }else{
            ret += add(a, b);
        }
    }
    return ret;
}
 
int main() {
    int ret = add5(1, 2);
    printf("add5 ret %d", ret);
    return 0;
}
clang main.cpp -o main_icall -target aarch64-linux-gnu -mllvm -irobf-icall
 
clang main.cpp -o main_indbr -target aarch64-linux-gnu -mllvm -irobf-indbr
 
clang main.cpp -o main_cff -target aarch64-linux-gnu -mllvm -irobf-cff
clang main.cpp -o main_icall -target aarch64-linux-gnu -mllvm -irobf-icall
 
clang main.cpp -o main_indbr -target aarch64-linux-gnu -mllvm -irobf-indbr
 
clang main.cpp -o main_cff -target aarch64-linux-gnu -mllvm -irobf-cff
import flare_emu  # 导入flare_emu模块,用于模拟执行
import idc  # 导入idc模块,用于与IDA交互
import idaapi  # 导入idaapi模块,用于获取函数信息等
import keypatch  # 导入keypatch模块,用于汇编指令的修复
 
# 定义一个函数,用于修复指定地址的指令
def patch_one(address: int, new_instruction: str):
    """
    使用Keypatch修复指定地址的指令。
 
    参数:
        address (int): 要修复的指令地址。
        new_instruction (str): 新的汇编指令。
 
    返回:
        bool: 如果修复成功返回True,否则返回False。
    """
    kp_asm = keypatch.Keypatch_Asm()  # 初始化Keypatch汇编对象
    if kp_asm.arch is None# 检查Keypatch是否支持当前架构
        print("ERROR: Keypatch无法处理此架构")
        return False
 
    # 解析新的汇编指令
    assembly = kp_asm.ida_resolve(new_instruction, address)
    (encoding, count) = kp_asm.assemble(assembly, address)  # 将汇编指令转换为机器码
 
    if encoding is None# 如果没有生成机器码,说明无需修复
        print("Keypatch: 无需修复")
        return False
 
    # 将机器码转换为字节数据
    patch_data = ''.join(chr(c) for c in encoding)
    patch_len = len(patch_data)  # 获取机器码长度
    kp_asm.patch(address, patch_data, patch_len)  # 使用Keypatch进行修复
 
    print(f"修复完成: {assembly}"# 输出修复的指令
 
def myTargetCallBack(emu, address, argv, userData):
    """
    模拟执行时的回调函数,用于记录间接调用的目标地址。
 
    参数:
        emu (flare_emu.EmuHelper): 模拟器对象。
        address (int): 当前指令地址。
        argv (list): 当前指令的参数。
        userData (dict): 用户数据,用于存储间接调用的映射。
    """
    # 获取当前指令的反汇编字符串
    code_str = idc.GetDisasm(address)
 
    # 提取BR指令使用的寄存器名
    register_name = code_str.split(" ")[-1]
 
    # 输出当前地址和寄存器值
    print(f"address = {hex(address)}, X* = {hex(emu.getRegVal(register_name))}")
 
    # 将当前地址和寄存器值存储到用户数据中
    userData["br_map"][address] = emu.getRegVal(register_name)
 
def anti_icall():
    """
    主函数,用于处理间接调用(indirect call)的修复。
    """
 
    br_addr_list = [0x4006A0, 0x400724# 初始化间接调用地址列表
 
    eh = flare_emu.EmuHelper()  # 初始化flare_emu模拟器
 
    # 模拟执行指定范围内的代码,并记录间接调用的目标地址
    eh.iterate(br_addr_list, targetCallback=myTargetCallBack, hookData={"br_map": {}})
 
    # 遍历记录的间接调用地址,并修复为直接调用
    for br_addr in eh.hookData["br_map"]:
        print(f"br_addr = {hex(br_addr)} {hex(eh.hookData['br_map'][br_addr])}")
        patch_one(br_addr, f"bl {hex(eh.hookData['br_map'][br_addr])}")
import flare_emu  # 导入flare_emu模块,用于模拟执行
import idc  # 导入idc模块,用于与IDA交互
import idaapi  # 导入idaapi模块,用于获取函数信息等
import keypatch  # 导入keypatch模块,用于汇编指令的修复
 
# 定义一个函数,用于修复指定地址的指令
def patch_one(address: int, new_instruction: str):
    """
    使用Keypatch修复指定地址的指令。
 
    参数:
        address (int): 要修复的指令地址。
        new_instruction (str): 新的汇编指令。
 
    返回:
        bool: 如果修复成功返回True,否则返回False。
    """
    kp_asm = keypatch.Keypatch_Asm()  # 初始化Keypatch汇编对象
    if kp_asm.arch is None# 检查Keypatch是否支持当前架构

[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 4天前 被简单的简单编辑 ,原因: 一些排版调整
上传的附件:
收藏
免费 14
支持
分享
赞赏记录
参与人
雪币
留言
时间
mb_ripshzdx
为你点赞!
23小时前
@=llfly
+1
非常支持你的观点!
1天前
马先越
期待更多优质内容的分享,论坛有你更精彩!
1天前
Ram98
为你点赞!
2天前
szukodf
+1
为你点赞!
5天前
yiyeqiuyu
感谢你的贡献,论坛因你而更加精彩!
5天前
crackwiki
谢谢你的细致分析,受益匪浅!
2025-4-1 21:01
逆天而行
你的分享对大家帮助很大,非常感谢!
2025-4-1 15:55
东方玻璃
感谢你分享这么好的资源!
2025-4-1 13:04
Forgo7ten
+1
感谢你的贡献,论坛因你而更加精彩!
2025-3-31 16:42
令狐双
为你点赞!
2025-3-31 11:27
轻快笑着行
非常支持你的观点!
2025-3-31 09:38
Our_OT
为你点赞!
2025-3-31 00:03
逆向狗都不学
为你点赞!
2025-3-30 22:25
最新回复 (4)
雪    币: 2831
活跃值: (3380)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
好文 !
2025-4-1 15:55
0
雪    币: 573
活跃值: (1139)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
66666666
2025-4-2 11:06
0
雪    币: 130
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
学习了,膜拜一下
23小时前
0
雪    币: 4930
活跃值: (7304)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
无敌风火轮牛逼
23小时前
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册