
1. 引言提到 LLVM很多人的第一反应是「编译器基础设施」「Clang 的前端」「优化代码」——似乎 LLVM 只和编译有关。但实际上LLVM 的能力远不止于此它是一套模块化的编译框架允许你在编译的中间阶段插入自定义逻辑从而对程序做深度改造。本文将以一个实战场景为例通过编写自定义 LLVM Pass在编译期自动对函数名进行加密混淆并配合定制 clang 完成整个编译流程。读完本文你将掌握LLVM 的 IR中间表示与 Pass 机制如何编写一个简单的 FunctionPass 并注册到 LLVM如何通过 clang 插件或工具链集成让加密在编译时自动生效加密后程序的运行效果与反编译对抗思路。2. 前置知识LLVM 与 Pass 机制2.1 LLVM 是什么LLVMLow Level Virtual Machine是一套开源的编译器基础设施核心设计是「三段式」前端Clang → 中间表示IR → 后端CodeGen前端把高级语言C/C/Rust 等翻译成与机器无关的 IR后端再把 IR 翻译成目标机器码。中间的 IR 是 LLVM 的灵魂——它既保留了高级语言的语义信息又足够底层方便做各种分析和变换。2.2 Pass 是什么Pass 是 LLVM 中「对 IR 做一次遍历和变换」的单元。你可以把 Pass 理解为编译流水线上的一个「加工站」源代码Clang 前端LLVM IR自定义 Pass函数名加密优化 Pass后端 CodeGen目标机器码LLVM 官方自带大量 Pass如-mem2reg、-instcombine而我们今天要做的就是写一个自定义 Pass在 IR 阶段把函数名替换成随机字符串。3. 环境准备3.1 安装 LLVM 开发环境推荐使用 LLVM 15 或 16 版本较新且稳定。以 Ubuntu 为例# 安装 LLVM 开发库sudoapt-getinstallllvm-15-dev llvm-15-tools clang-15# 验证安装llvm-config-15--versionllvm-config-15--includedir如果你使用 macOS可以通过 Homebrewbrewinstallllvm153.2 项目结构我们创建一个独立的 Pass 工程llvm-func-encrypt/ ├── CMakeLists.txt ├── FuncEncryptPass.cpp └── README.md4. 编写自定义 Pass函数名加密4.1 Pass 的核心逻辑我们的 Pass 要做三件事遍历模块中的所有函数跳过main和 LLVM 内部函数避免破坏程序入口把函数名替换为随机生成的字符串。4.2 完整代码// FuncEncryptPass.cpp#includellvm/IR/Function.h#includellvm/IR/Module.h#includellvm/Pass.h#includellvm/Support/raw_ostream.h#includellvm/IR/LegacyPassManager.h#includellvm/Transforms/IPO/PassManagerBuilder.h#includerandom#includestringusingnamespacellvm;namespace{std::stringrandomName(size_t len){staticconstcharcharset[]abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;std::random_device rd;std::mt19937gen(rd());std::uniform_int_distributiondis(0,sizeof(charset)-2);std::string s;for(size_t i0;ilen;i){scharset[dis(gen)];}returns;}structFuncEncryptPass:publicFunctionPass{staticcharID;FuncEncryptPass():FunctionPass(ID){}boolrunOnFunction(FunctionF)override{// 跳过 main 函数避免破坏程序入口if(F.getName()main){returnfalse;}// 跳过声明没有函数体的外部函数if(F.isDeclaration()){returnfalse;}// 生成随机新名字std::string newNamerandomName(12);F.setName(newName);errs()[FuncEncrypt] F.getName()\n;returntrue;}};charFuncEncryptPass::ID0;}// namespace// 注册 PassstaticRegisterPassFuncEncryptPassX(func-encrypt,Encrypt function names,false/* Only looks at CFG */,false/* Analysis Pass */);// 注册到 opt 的 Pass 管道staticvoidregisterFuncEncryptPass(constPassManagerBuilder,legacy::PassManagerBasePM){PM.add(newFuncEncryptPass());}staticRegisterStandardPassesY(PassManagerBuilder::EP_EarlyAsPossible,registerFuncEncryptPass);4.3 CMake 配置cmake_minimum_required(VERSION 3.13) project(FuncEncryptPass) find_package(LLVM REQUIRED CONFIG) message(STATUS Found LLVM ${LLVM_PACKAGE_VERSION}) include_directories(${LLVM_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) add_library(FuncEncryptPass MODULE FuncEncryptPass.cpp) target_link_libraries(FuncEncryptPass PRIVATE LLVM)4.4 编译 Passmkdirbuildcdbuild cmake..make编译后会生成libFuncEncryptPass.so这就是我们要用的 Pass 插件。5. 定制 clang让加密自动生效5.1 两种集成方式要让加密在编译时自动生效有两种思路方式原理适用场景方式 Aopt手动加载先用 clang 生成 IR再用opt加载 Pass实验、调试方式 Bclang 插件通过-fpass-plugin让 clang 自动加载 Pass生产、一键编译5.2 方式 A手动两步编译# 第一步生成 IRclang-15-S-emit-llvm test.c-otest.ll# 第二步加载 Pass 加密函数名opt-15-load./build/libFuncEncryptPass.so -func-encrypt test.ll-S-otest_enc.ll# 第三步生成可执行文件llc-15 test_enc.ll-otest.s clang-15 test.s-otest5.3 方式 Bclang 插件推荐LLVM 15 之后支持-fpass-plugin可以直接让 clang 在编译时加载 Passclang-15 -fpass-plugin./build/libFuncEncryptPass.so test.c-otest这样一条命令就完成了「编译 加密」非常方便。5.4 验证效果写一个简单的测试程序// test.c#includestdio.hintadd(inta,intb){returnab;}intmain(){printf(Result: %d\n,add(3,4));return0;}编译并查看符号表clang-15 -fpass-plugin./build/libFuncEncryptPass.so test.c-otest# 查看符号表nmtest|grep-Eadd|main你会看到add已经被替换成类似kX9mQ2vLpZ7a的随机字符串而main保持不变。6. 进阶加密策略与对抗思路6.1 更复杂的加密策略上面的例子只是最简单的「随机改名」。实际对抗中可以叠加更多手段字符串加密把函数内的字符串常量也加密运行时解密控制流平坦化打乱 if/else 结构增加逆向难度指令替换把简单指令替换为等价但更复杂的指令序列花指令插入插入不影响语义的垃圾指令。6.2 注意事项调试信息加密后符号名丢失gdb调试会变得困难建议在 Debug 构建中关闭加密动态链接如果函数被其他模块引用如回调函数改名会导致链接失败需要跳过这些函数性能加密本身几乎无性能开销但复杂的混淆如控制流平坦化会显著增加代码体积和执行时间。7. 总结本文从一个实战角度展示了 LLVM 的「非编译」能力LLVM 的 IR 是高度可编程的中间表示Pass 机制允许我们在编译期深度改造程序通过自定义 FunctionPass我们实现了函数名的随机加密借助 clang 的-fpass-plugin加密可以无缝集成到日常编译流程中。LLVM 的价值远不止「编译」二字。无论是代码混淆、性能分析、静态检查还是自定义优化LLVM 都提供了强大的扩展点。希望本文能帮你打开 LLVM 进阶应用的一扇窗。