写在前面 这篇文章的前半部分包括写的环境搭建都是基于 LLVM 源码的构建方式,重点在于帮助了解 llvm 的结构,如果目的只是编写 Pass 的话,直接跳到 自己编写 LLVM Pass 部分即可
LLVM 环境搭建 由于我的 Ubuntu 版本不够,无法直接 apt install llvm-20,所以就去 https://github.com/llvm/llvm-project.git 里面下载了源码,解压到 wsl 下
更新包列表sudo apt-get update
安装基础编译工具(包含g++, make等)sudo apt-get install build-essential
安装 CMakesudo apt-get install cmake
安装 Ninjasudo apt-get install ninja-build
安装 Clangsudo apt-get install clang
安装其他依赖sudo apt-get install python3 zlib1g-dev libtinfo-dev
进入 llvm 源码目录
1 cd llvm-project-llvmorg-20 .1 .8
配置并生成构建文件
1 cmake -S llvm -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang"
进入 build 目录,并行使用 8 个线程进行构建
把 llvm 源码导入 CLion 中,找到 llvm-project-llvmorg-20.1.8\llvm 目录下的 CMakeLists.txt,打开 在设置里的 构造、执行、部署 中点开 CMake,点 + 添加,在 CMake 选项中输入之前的命令cmake -S llvm -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang"
, 确认后发现多了两个文件夹 cmake-build-debug 和 cmake-build-release,然后 ninja -j8 编译,但是我这里好像是因为当前执行命令的目录路径 (/home/eleven/…) 和它在 CMakeCache.txt 文件中记录的路径 (//wsl.localhost/Ubuntu/home/eleven/…) 不一致,编译失败了,然后就删了重新构建一次
1 2 3 4 cd ~/llvm-project-llvmorg-20.1.8/llvm/cmake-build-releaserm -rf CMakeCache.txt CMakeFiles/ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release ninja -j8
https://llvm.org/docs/GettingStarted.html#llvm-tools 先编写一个简单的 c 语言脚本
1 2 3 4 5 6 7 #include <stdio.h> int main () { printf ("Hello, LLVM!\n" ); return 0 ; }
用 clang 编译clang ez.c -o ez
clang 默认方式与 GCC 相同,标准 -S 和 -c 参数分别生成 .s 和 .o 文件
把 c 源代码编译成 LLVM IRclang -emit-llvm -S ez.c -o ez.ll
-emit-llvm 告诉编译器不要生成机器码(如 .o 或可执行文件),而是生成 LLVM IR -S 告诉编译器,生成的 LLVM IR 应该以文本格式输出,而不是默认的二进制字节码格式 .ll 文件是人类可读的文本文件
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 source_filename = "ez.c" target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" @.str = private unnamed_addr constant [14 x i8 ] c "Hello, LLVM!\0A\00 " , align 1 define dso_local i32 @main () #0 { %1 = alloca i32 , align 4 store i32 0 , ptr %1 , align 4 %2 = call i32 (ptr , ...) @printf (ptr noundef @.str ) ret i32 0 }declare i32 @printf (ptr noundef, ...) #1 attributes #0 = { noinline nounwind optnone uwtable "frame-pointer" = "all" "min-legal-vector-width" = "0" "no-trapping-math" = "true" "stack-protector-buffer-size" = "8" "target-cpu" = "x86-64" "target-features" = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu" = "generic" }attributes #1 = { "frame-pointer" = "all" "no-trapping-math" = "true" "stack-protector-buffer-size" = "8" "target-cpu" = "x86-64" "target-features" = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu" = "generic" }!llvm.module.flags = !{!0 , !1 , !2 , !3 , !4 }!llvm.ident = !{!5 }!0 = !{i32 1 , !"wchar_size" , i32 4 }!1 = !{i32 8 , !"PIC Level" , i32 2 }!2 = !{i32 7 , !"PIE Level" , i32 2 }!3 = !{i32 7 , !"uwtable" , i32 2 }!4 = !{i32 7 , !"frame-pointer" , i32 2 }!5 = !{!"Ubuntu clang version 18.1.3 (1ubuntu1)" }
执行 .ll 文件 : lli ez.ll
lli 是 LLVM Interpreter(LLVM 解释器) 的缩写,是一个命令行工具,能够直接执行 LLVM IR代码
把 c 源码编译成 Bitcodeclang -O3 -emit-llvm ez.c -c -o ez.bc
-O 表示优化,3表示这是最高级别的优化 -emit-llvm 作用同上,告诉编译器生成 LLVM IR -c 告诉编译器只进行编译,不进行链接,即生成一个目标文件而不是可执行文件
.bc 文件是 LLVM 字节码(Bitcode)的缩写,它与之前的 .ll 文件都属于 LLVM IR,但是
.ll
文件是 LLVM IR 的文本格式 ,人类可读,并且可以使用文本编译器查看内容.bc
文件是 LLVM IR 的二进制格式 ,人类不可读,但文件体积更小,加载和处理速度更快,更适合作为编译器后端的输入
llvm 中提供了 llvm-as (汇编器) 和 llvm -dis (反汇编器),让两种形式的文件能相互转化 把 .ll 文件转化为 .bc 文件 llvm-as ez.ll -o ez.bc
把 .bc 文件转化为 .ll 文件llvm-dis ez.bc -o ez.ll
llvm-dis < ez.bc | less
llvm-dis 是 LLVM 的反汇编工具,用于将 .bc 文件转换为 .ll 文件 < 是一个重定向操作符,告诉 llvm-dis 命令,从标准输入(stdin)而不是从命令行参数中读取数据 | 是 管道 符号,它将左边命令的标准输出(stdout)作为右边命令的标准输入 less 是一个分页查看器,能够让用户逐页查看文本内容
把 llvm 字节码文件转化为汇编llc ez.bc -o ez.s
llc 是 LLVM Static Compiler,即LLVM 编译流程中的后端代码生成器
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 .text .file "ez.c" .globl main .p2align 4, 0x90 .type main,@function main: .cfi_startproc pushq %rax .cfi_def_cfa_offset 16 movl $.Lstr , %edi callq puts@PLT xorl %eax, %eax popq %rcx .cfi_def_cfa_offset 8 retq.Lfunc_end0 : .size main, .Lfunc_end0-main .cfi_endproc .type .Lstr ,@object .section .rodata.str1.1 ,"aMS" ,@progbits,1.Lstr : .asciz "Hello, LLVM!" .size .Lstr , 13 .ident "Ubuntu clang version 18.1.3 (1ubuntu1)" .section ".note.GNU-stack" ,"" ,@progbits
把汇编转化成可执行文件clang ez.s -o ez_asm
我直接用这个失败了,因为汇编代码使用了绝对地址引用(movl $.Lstr, %edi),与 PIE 不兼容 禁用pie:clang -no-pie ez.s -o ez_asm
使用 opt 来执行 pass
在典型的编译流程中,源代码首先被编译成 LLVM IR,然后 opt 会介入,对这个 IR 进行一系列的分析和转换,优化代码opt --help
获取 LLVM 中可用程序转换列表
LLVM Pass LLVM Pass 框架是 LLVM 系统的一个重要组成部分,Pass 执行构成编译器的各种转换和优化
Pass 的核心特征:
模块化:每个 Pass 都被设计成一个独立的、自包含的单元,它只专注于完成一个特定的任务
可组合:LLVM 允许通过 Pass 管理器将多个 Pass 串联起来,形成一个完整的优化管道,开发者可以根据需求自由组合不同的 Pass
统一接口:与旧版 Pass 管理器中通过继承定义 Pass 接口不同,新版 Pass 管理器中的 Pass 都遵循一个基于概念的多态接口
Pass 的分类
FunctionPass:在函数上运行,这是最常见的 Pass 类型,通常用于函数内部的优化或分析。
ModulePass:在整个模块(通常是一个源文件)上运行,它适用于需要在函数间进行分析或优化的场景
LoopPass:在每个循环上运行
根据官方文档具体了解 passhttps://llvm.org/docs/WritingAnLLVMNewPMPass.html
基于源码的 LLVM Pass 上文已经为新 pass 构建好了环境 以 llvm-project 目录下创建好了的这个为例 llvm/lib/Transforms/Utils/HelloWorld.cpp
首先需要在一个头文件中定义这个 pass,创建文件 llvm/include/llvm/Transforms/Utils/HelloWorld.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H #define LLVM_TRANSFORMS_HELLONEW_HELLOWORLD_H #include "llvm/IR/PassManager.h" namespace llvm {class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {public : PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) ; }; } #endif
1 class HelloWorldPass : public PassInfoMixin<HelloWorldPass> {
定义了一个 HelloWorldPass 类,它继承自 PassInfoMixin,这是 LLVM 新版 Pass 管理器的一个基类PassInfoMixin
是一个 CRTP(奇异递归模板模式)模板,提供了 Pass 的基本信息和接口
1 2 public : PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) ;
run
方法是 Pass 的核心执行函数Function &F
:当前正在处理的函数对象的引用FunctionAnalysisManager &AM
:函数分析管理器,用于获取和管理分析结果PreservedAnalyses
:返回值,告诉 Pass 管理器哪些分析结果在此 Pass 执行后仍然有效
然后看llvm/lib/Transforms/Utils/HelloWorld.cpp
1 2 3 4 5 6 7 8 9 10 #include "llvm/Transforms/Utils/HelloWorld.h" #include "llvm/IR/Function.h" using namespace llvm;PreservedAnalyses HelloWorldPass::run (Function &F, FunctionAnalysisManager &AM) { errs () << F.getName () << "\n" ; return PreservedAnalyses::all (); }
头文件中包含了刚刚定义的 HelloWorld.h 以及 LLVM 的 Function 类定义,提供函数相关的 API
pass 的核心逻辑
1 errs () << F.getName () << "\n" ;
errs()
:LLVM 的错误输出流F.getName()
:获取当前函数的名称 整体作用:将函数名打印到标准错误输出
1 return PreservedAnalyses::all ();
告诉 Pass 管理器所有的分析结果都被保留
以上就是 Pass 本身的代码,接下来需要在几个地方添加一些内容来注册这个 pass
在 llvm/lib/Passes/PassRegistry.def
文件的 FUNCTION_PASS 部分添加
1 FUNCTION_PASS ("helloworld" , HelloWorldPass ())
让这个 pass 名为 “helloworld”
在 llvm/lib/Passes/PassBuilder.cpp
中添加正确的 #include:
1 #include "llvm/Transforms/Utils/HelloWorld.h"
然后就可以编译运行了,但是我按照这样操作之后再 ninja 编译时出错了,大概意思是刚刚的 HelloWorld.cpp 中缺少头文件 添加了下面这一行之后就能顺利编译了
1 #include "llvm/Passes/PassBuilder.h"
在build
目录下执行 ninja
回显
1 [59 /59 ] Creating library symlink lib /libclang-cpp.so
使用 opt 来运行一个 pass
1 2 3 opt -passes=helloworld ez.ll -o /dev/null
-passes=helloworld: 告诉 opt 运行一个名为 helloworld 的 Pass -o /dev/null: 将 opt 的输出结果重定向到空设备,不保存修改后的 IR 文件
以上是旧的注册方法,LLVM 还提供了一种新的注册方式 –以 插件 的形式注册 Pass 首先,移除之前按照旧方法而做的修改 然后在 llvm/lib/Transforms/Utils/HelloWorld.cpp
中修改如下:
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 #include "llvm/Transforms/Utils/HelloWorld.h" #include "llvm/IR/Function.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/PassBuilder.h" using namespace llvm;PreservedAnalyses HelloWorldPass::run (Function &F, FunctionAnalysisManager &AM) { errs () << "Hello from HelloWorldPass: " << F.getName () << "\n" ; return PreservedAnalyses::all (); }extern "C" LLVM_ATTRIBUTE_WEAK PassPluginLibraryInfo llvmGetPassPluginInfo () { return {LLVM_PLUGIN_API_VERSION, "HelloWorldPass" , "v1.0" , [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "hello-world" ) { FPM.addPass (HelloWorldPass ()); return true ; } return false ; }); }}; }
工作流程:
用户运行:opt -passes=hello-world input.ll LLVM 加载插件并调用 llvmGetPassPluginInfo() 插件注册解析回调到 PassBuilder PassBuilder 解析 “hello-world” 名称 回调函数将 HelloWorldPass 添加到管道 Pass 管理器执行 HelloWorldPass::run()
方法 输出函数名并完成处理
接下来需要修改 CMakeLists.txt 文件,添加插件的构建配置 在 llvm/lib/Transforms/Utils/CMakeLists.txt
中添加 Passes 和 PARTIAL_SOURCES_INTENDED
1 2 3 4 5 6 7 8 9 LINK_COMPONENTS Analysis Core Support TargetParser Passes PARTIAL_SOURCES_INTENDED )
然后进入 build 目录,重新运行 cmake,让 Cmake 读取修改后的 CMakeLists.txt;再执行 ninja
1 2 3 cd /home/eleven/llvm-project-llvmorg-20.1.8/build cmake -G Ninja ../llvm ninja
自己编写 LLVM Pass 先安装 LLVM 的软件包,我这里安装的是 LLVM 19
1 sudo apt install llvm-19
创建一个新的目录 myllvm
Function Pass
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 35 #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/raw_ostream.h" using namespace llvm;struct HelloPass : public PassInfoMixin<HelloPass> { PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) { errs () << "Hello from LLVM 19: " << F.getName () << "\n" ; return PreservedAnalyses::all (); } static bool isRequired () { return true ; } };extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo () { return { .APIVersion = LLVM_PLUGIN_API_VERSION, .PluginName = "HelloPass" , .PluginVersion = "v0.1" , .RegisterPassBuilderCallbacks = [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "hello" ) { FPM.addPass (HelloPass{}); return true ; } return false ; }); } }; }
一些注释
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 35 36 37 38 39 40 41 42 43 44 #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/row_ostream.h" using namespace llvm;struct HelloPass : public PassInfoMixin<HelloPass> { PreservedAnalyses run (Function &F, FunctionAnalysisManager &AM) { errs () << "Hello from LLVM 19:" << F.getName () << "\n" ; return PreservedAnalyses::all (); } static bool isRequired () { return true ; } };extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfollvmGetPassPluginInfo () { return { .APIVersion = LLVM_PLUGIN_API_VERSION, .PluginName = "HelloPass" , .PluginVersion = "v0.1" , .RegisterPassBuilderCallbacks = [](PassBuilder &PB) { PB.registerPipelineParstringCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "hello" ) { FPM.addPass (HelloPass{}); return true ; } return false ; }); }}; }
编译 LLVM Pass
1 clang++-18 -shared -fPIC `llvm-config-19 --cxxflags` -o HelloPass.so Hello.cpp
clang++ 是 C++ 编译器,18是 clang 版本 -shared 告诉编译器和连接器创建一个共享库(动态链接库) -fPIC Position-Independent Code,位置无关代码,创建共享库是个标志这是必需的 `llvm-config-19 –cxxflags` 确保编译器可以找到LLVM pass所依赖的所有头文件
前半部分查询 LLVM 配置信息,19 是LLVM 版本 后半部分的–cxxflags 参数让 llvm-config-19 输出编译C++代码时需要的标志 -o HelloPass.so 指定生成共享库的名字
准备一个测试文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> int main () { printf ("Hello, World!\n" ); return 0 ; }int add (int a, int b) { return a + b; }int subtract (int a, int b) { return a - b; }
编译成 .ll 文件
1 clang -emit-llvm -S test1.c -o test1.ll
用 opt 执行 Pass
1 opt -load-pass-plugin=./HelloPass.so -passes="hello" ./test1.ll -disable-output
HelloPass.so 是共享库文件 hello 是 pass 名称 在默认情况下,opt 会将经过pass处理后的LLVM IR输出到标准输出,-disable-output 能禁止该行为 输出
1 2 3 Hello from LLVM 19 : main Hello from LLVM 19 : add Hello from LLVM 19 : subtract
混淆–自实现 OLLVM 符号混淆 写了一个实现混淆函数名的 pass,把自定义的函数名都变成随机生成的名字
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/Module.h" #include "llvm/IR/Function.h" #include "llvm/Support/raw_ostream.h" #include <random> #include <string> #include <set> using namespace llvm;struct SymbolReplacePass : public PassInfoMixin<SymbolReplacePass> {private : std::string generateRandomName () { static std::random_device rd; static std::mt19937 gen (rd()) ; static std::uniform_int_distribution<> dis (0 , 25 ); std::string name; for (int i = 0 ; i < 8 ; ++i) { name += ('a' + dis (gen)); } return name; } bool shouldObf (const Function &F) { if (F.isDeclaration ()) return false ; StringRef name = F.getName (); std::set<std::string> preservedNames = { "main" , "_start" , "__libc_start_main" , "printf" , "scanf" , "malloc" , "free" , "exit" }; if (name.starts_with ("_" ) && name != "main" ) return false ; for (const auto &preserved : preservedNames) { if (name == preserved) return false ; } return true ; }public : PreservedAnalyses run (Module &M, ModuleAnalysisManager &) { bool modified = false ; std::vector<Function *> functionsToObf; for (Function &F : M) { if (shouldObf (F)) { functionsToObf.push_back (&F); } } for (Function *F : functionsToObf) { std::string origName = F->getName ().str (); std::string newName = generateRandomName (); F->setName (newName); errs () << "[SymbolReplace] obfuscated : " << origName << "->" << newName << "\n" ; modified = true ; } if (modified) { errs () << "Total functions obfuscated:" << functionsToObf.size () << "\n" ; } return modified ? PreservedAnalyses::none () : PreservedAnalyses::all (); } };extern "C" LLVM_ATTRIBUTE_WEAK::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo () { return { .APIVersion = LLVM_PLUGIN_API_VERSION, .PluginName = "SymbolReplacePass" , .PluginVersion = "v0.1" , .RegisterPassBuilderCallbacks = [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "symbol_replace" ) { MPM.addPass (SymbolReplacePass ()); return true ; } return false ; }); } }; }
准备的测试文件
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 # include <stdio.h> int func1 (int a, int b) { return a + b; }int func2 (int a, int b) { return a - b; }int func3 (int a, int b) { return a * b; }int main () { func1(7 ,11 ); func2(7 ,11 ); func3(7 ,11 ); printf ("func1: %d\n" , func1(7 , 11 )); printf ("func2: %d\n" , func2(7 , 11 )); printf ("func3: %d\n" , func3(7 , 11 )); return 0 ; }
编译插件clang++-18 -shared -fPIC
llvm-config-19 –cxxflags -o SymbolReplacePass.so obf.cpp
生成 .ll 文件clang -emit-llvm -S test2.c -o test2.ll
进行全函数混淆opt -load-pass-plugin=./SymbolReplacePass.so -passes=symbol_replace test2.ll -S -o afterObf_test2.ll
可以看到输出
1 2 3 4 [SymbolReplace ] obfuscated : func1-> sgwvbkay [SymbolReplace ] obfuscated : func2-> lwhtbjkx [SymbolReplace ] obfuscated : func3-> cfjkyqtx Total functions obfuscated:3
编译最终程序clang afterObf_test2.ll -o afterObf_test2
用 ida 查看混淆前后的程序,对比
混淆前:
混淆后:
指令替换 自实现指令混淆的 pass
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 #include "llvm/IR/PassManager.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/IR/Module.h" #include "llvm/IR/Function.h" #include "llvm/IR/BasicBlock.h" #include "llvm/IR/Instructions.h" #include "llvm/Support/raw_ostream.h" #include <random> #include <vector> using namespace llvm;struct InstructionSubstitutionPass : public PassInfoMixin<InstructionSubstitutionPass> {private : std::mt19937 rng; int getRandomInt (int max) { std::uniform_int_distribution<int > dist (0 , max - 1 ) ; return dist (rng); } void substituteAdd (BinaryOperator *addInst) { IRBuilder<> builder (addInst); Value *op1 = addInst->getOperand (0 ); Value *op2 = addInst->getOperand (1 ); Value *dummy = builder.CreateMul (op1, ConstantInt::get (op1->getType (), 1 ), "dummy" ); Value *xorResult = builder.CreateXor (op1, op2, "xor_tmp" ); Value *andResult = builder.CreateAnd (op1, op2, "and_tmp" ); Value *shiftResult = builder.CreateShl (andResult, 1 , "shift_tmp" ); Value *addResult = builder.CreateAdd (xorResult, shiftResult, "add_subst" ); Value *finalResult = builder.CreateXor (addResult, ConstantInt::get (op1->getType (), 0 ), "add_subst" ); addInst->replaceAllUsesWith (finalResult); errs () << "[InstSubst] Replaced ADD instruction\n" ; } void substituteSub (BinaryOperator *subInst) { IRBuilder<> builder (subInst); Value *op1 = subInst->getOperand (0 ); Value *op2 = subInst->getOperand (1 ); Value *notOp2 = builder.CreateNot (op2, "not_tmp" ); Value *negOp2 = builder.CreateAdd (notOp2, ConstantInt::get (op2->getType (), 1 ), "neg_tmp" ); Value *finalResult = builder.CreateAdd (op1, negOp2, "final_tmp" ); subInst->replaceAllUsesWith (finalResult); errs () << "[InstSubst] Replaced SUB instruction\n" ; } void substituteXor (BinaryOperator *xorInst) { IRBuilder<> builder (xorInst); Value *op1 = xorInst->getOperand (0 ); Value *op2 = xorInst->getOperand (1 ); Value *orResult = builder.CreateOr (op1, op2, "or_tmp" ); Value *andResult = builder.CreateAnd (op1, op2, "and_tmp" ); Value *finalResult = builder.CreateSub (orResult, andResult, "final_tmp" ); xorInst->replaceAllUsesWith (finalResult); errs () << "[InstSubst] Replaced XOR instruction\n" ; } void substituteMul (BinaryOperator *mulInst) { if (!isa <ConstantInt>(mulInst->getOperand (1 ))) { return ; } ConstantInt *constOp = cast <ConstantInt>(mulInst->getOperand (1 )); uint64_t multiplier = constOp->getZExtValue (); if (multiplier > 0 && (multiplier & (multiplier - 1 )) == 0 ) { IRBuilder<> builder (mulInst); Value *op1 = mulInst->getOperand (0 ); unsigned shiftAmount = 0 ; uint64_t temp = multiplier; while (temp > 1 ) { temp >>= 1 ; shiftAmount++; } Value *shiftResult = builder.CreateShl (op1, shiftAmount, "mul_subst" ); mulInst->replaceAllUsesWith (shiftResult); errs () << "[InstSubst] Replaced MUL instruction\n" ; } } void substituteICmp (ICmpInst *cmpInst) { IRBuilder<> builder (cmpInst); Value *op1 = cmpInst->getOperand (0 ); Value *op2 = cmpInst->getOperand (1 ); ICmpInst::Predicate pred = cmpInst->getPredicate (); if (pred == ICmpInst::ICMP_EQ) { Value *xorResult = builder.CreateXor (op1, op2, "xor_tmp" ); Value *zero = ConstantInt::get (op1->getType (), 0 ); Value *newCmp = builder.CreateICmpEQ (xorResult, zero, "eq_subst" ); cmpInst->replaceAllUsesWith (newCmp); errs () << "[InstSubst] Replace ICMP_EQ instruction\n" ; } }public : InstructionSubstitutionPass () : rng (std::random_device{}()) {} PreservedAnalyses run (Function &F, FunctionAnalysisManager &) { bool modified = false ; std::vector<Instruction *> toReplace; for (BasicBlock &BB : F) { for (Instruction &I : BB) { if (auto *binOp = dyn_cast <BinaryOperator>(&I)) { switch (binOp->getOpcode ()) { case Instruction::Add: case Instruction::Sub: case Instruction::Xor: case Instruction::Mul: toReplace.push_back (&I); break ; default : break ; } } else if (auto *cmpInst = dyn_cast <ICmpInst>(&I)) { toReplace.push_back (&I); } } } for (Instruction *inst : toReplace) { if (auto *binOp = dyn_cast <BinaryOperator>(inst)) { switch (binOp->getOpcode ()) { case Instruction::Add: substituteAdd (binOp); modified = true ; break ; case Instruction::Sub: substituteSub (binOp); modified = true ; break ; case Instruction::Xor: substituteXor (binOp); modified = true ; break ; case Instruction::Mul: substituteMul (binOp); modified = true ; break ; default : break ; } } else if (auto *cmpInst = dyn_cast <ICmpInst>(inst)) { substituteICmp (cmpInst); modified = true ; } } for (Instruction *inst : toReplace) { if (inst->use_empty ()) { inst->eraseFromParent (); } } if (modified) { errs () << "[InstSubst] Modified function: " << F.getName () << "\n" ; } return modified ? PreservedAnalyses::none () : PreservedAnalyses::all (); } };extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo () { return { .APIVersion = LLVM_PLUGIN_API_VERSION, .PluginName = "InstructionSubstitutionPass" , .PluginVersion = "v0.1" , .RegisterPassBuilderCallbacks = [](PassBuilder &PB) { PB.registerPipelineParsingCallback ( [](StringRef Name, FunctionPassManager &FPM, ArrayRef<PassBuilder::PipelineElement>) { if (Name == "instruction_sub" ) { FPM.addPass (InstructionSubstitutionPass ()); return true ; } return false ; }); }}; }
测试文件
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> int calc (int a, int b) { int c = 18 ; int res = 0 ; int sum = a + b; int diff = a - b; int xor_res = diff ^ b; int mul_res = xor_res * 4 ; if (mul_res == c) { return 1 ; } else { return 0 ; } }void instsub () { int x = 10 ; int y = 20 ; int res = x + y; res = res - 5 ; res = res ^ 15 ; res = res * 2 ; printf ("Result: %d\n" , res); if (res == 50 ) { printf ("right\n" ); } else { printf ("wrong\n" ); } }int main () { printf ("Hello instruction substitute!\n" ); printf ("calc result: %d\n" , calc(5 , 3 )); instsub(); }
编译 LLVM Pass 插件clang++-18 -shared -fPIC
llvm-config-19 –cxxflags -o InstructionSubstitutionPass.so sub.cpp
将 C 源码编译为 LLVM IRclang -O0 -emit-llvm -c test3.c -o test3.ll
运行自定义 Pass 对 IR 进行转换opt -load-pass-plugin=./InstructionSubstitutionPass.so -passes=instruction_sub test3.ll -S -o afterSub_test3.ll
将混淆后的 IR 编译为可执行文件clang afterSub_test3.ll -o afterSub_test3