附录导读本附录手把手教你写一个自定义的 LLVM 混淆插件Pass。OLLVM 是最知名的开源混淆框架学会写 Pass你就能定制自己的混淆规则——比如决定哪些函数混淆、用什么方式混淆。什么是 PassPass 就是 LLVM 编译流水线上的一个加工工位它读入 IRIntermediate Representation中间表示编译器内部对程序的统一描述代码做某种变换例如把a b替换成等价但难懂的表达式再输出修改后的 IR。多个 Pass 串联起来就构成完整的编译/混淆流程。本附录的结构建议按顺序阅读 1. 第 1 节从源码编译 LLVM一次性环境准备需要较长时间 2. 第 2 节选择 LLVM 版本——不同版本 Pass 接口差异很大选错版本代码编不过 3. 第 3–4 节从一个最简 Pass 到完整的 MBA 替换 Pass含完整可编译源码 4. 第 5 节把 Pass 集成到 Android NDK 构建中 5. 第 6 节调试 Pass 的各种方法打印 IR 对比、FileCheck 回归测试、GDB 断点 6. 第 7 节新手最容易踩的坑PHI 节点、异常处理、metadata、迭代器失效。前置知识提醒你需要基本的 C 阅读能力并了解 LLVM IR 的基本样子可通过第 4.3 节的test.ll实例直观感受。不需要精通编译原理。附录知识定位本附录整体属于实践应用——从编译 LLVM、写 Pass 代码到调试每一步都配有可以直接执行的命令和完整源码是手把手教程。但它的受众是打算自己开发混淆 Pass 的人如果你是分析方、只想理解防御方的工作流程和混淆原理是怎么从代码变成二进制的可以把本附录当作背景参考来通读不必强制跟着每一步敲命令、搭编译环境。换句话说加固方开发者请逐节动手实操分析方/旁观学习者可以当理论阅读重点理解思路而非操作。1. LLVM 源码编译以 LLVM 18 为例知识定位实践应用面向打算自己开发 Pass 的读者。这是搭建开发环境的一次性操作照着命令跑即可如果只想理解混淆原理知道开发 Pass 前需要先编译一套 LLVM这个事实即可不必亲自跑一遍耗时的编译过程。# 获取源码 git clone --depth 1 --branch llvmorg-18.1.0 https://github.com/llvm/llvm-project.git # 编译Release 模式节省空间和时间 cd llvm-project cmake -S llvm -B build -G Ninja \ -DCMAKE_BUILD_TYPERelease \ -DLLVM_ENABLE_PROJECTSclang \ -DLLVM_TARGETS_TO_BUILDAArch64;ARM;X86 ninja -C build -j$(nproc)2. LLVM 版本选择建议知识定位实践应用中的决策参考。真正动手开发 Pass 时需要先按决策树选定版本号如果只是了解背景知道LLVM 不同版本的 Pass 接口不兼容选错版本会编译失败这个结论就够了不需要记住每个版本的具体 API 差异细节。不同 LLVM 版本的 Pass 接口存在显著差异选择合适的版本至关重要。LLVM 17 (LTS 级稳定)推荐场景需要最大兼容性和稳定性的生产项目。Pass 接口New Pass Manager 已完全取代 Legacy Pass ManagerLegacy PM 在 LLVM 17 中被移除。关键变化llvm::PassInfoMixinT作为标准 Pass 基类。registerPipelineParsingCallback用于注册 Pass 名称。Opaque Pointer不透明指针默认启用typed pointer 不再支持。注意事项所有使用getPointerElementType()的旧代码必须重写。LLVM 18 (推荐)推荐场景新项目开发的最佳平衡点兼顾现代特性与社区支持。Pass 接口变化PassBuilder::registerPipelineStartEPCallback等 EPExtension Point回调签名微调Lambda 参数增加OptimizationLevel。FunctionAnalysisManager的getResult语义不变但部分内置 Analysis 的返回类型有细微调整。新增CoroSplitPass等协程相关 Pass混淆 Pass 需注意与协程 IR 的交互。Android NDK 兼容NDK r27 使用 LLVM 18Pass 插件可直接兼容。LLVM 19 (前沿)推荐场景需要最新特性如改进的向量化、新后端支持的研究项目。Pass 接口变化PassBuilder的parsePassPipeline支持更细粒度的控制。部分 Analysis Pass 接口重构如LoopAnalysis相关。引入实验性的SandboxIR用于在受限环境中运行变换。TargetTransformInfo接口有 breaking change涉及成本模型查询。注意事项API 可能在后续 patch 中变化不建议用于需要长期维护的项目。版本选择决策树需要兼容 Android NDK r27? ──→ 选 LLVM 18 │ ↓ 否 需要最大稳定性? ──→ 选 LLVM 17 │ ↓ 否 需要前沿特性? ──→ 选 LLVM 19但要做好 API 适配工作3. 编写第一个 Function Pass知识定位实践应用。这是一个可以直接复制、编译、运行的最小 Pass 示例适合亲手敲一遍建立Pass 到底长什么样的直觉如果只是想理解概念读懂代码里的注释遍历指令、识别 ADD、替换即可不要求真的去编译它。// MyObfuscationPass.cpp #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h #include llvm/IR/Function.h using namespace llvm; struct MyObfuscationPass : public PassInfoMixinMyObfuscationPass { PreservedAnalyses run(Function F, FunctionAnalysisManager AM) { for (auto BB : F) { for (auto I : BB) { // 在这里实现混淆逻辑 // 例如识别 ADD 指令并替换为等价的 MBA 表达式 if (auto *BinOp dyn_castBinaryOperator(I)) { if (BinOp-getOpcode() Instruction::Add) { // TODO: 替换为 MBA } } } } return PreservedAnalyses::none(); } }; // 注册插件 extern C LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return {LLVM_PLUGIN_API_VERSION, MyObfuscation, 0.1, [](PassBuilder PB) { PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager FPM, ArrayRefPassBuilder::PipelineElement) { if (Name my-obf) { FPM.addPass(MyObfuscationPass()); return true; } return false; }); }}; }4. 完整的 MBAMixed Boolean-Arithmetic混合布尔-算术替换 Pass 示例知识定位实践应用本附录的核心实操内容。这是一个从源码、CMake 构建到运行验证全套可复现的完整案例建议真正打算开发混淆工具的读者按 4.1→4.4 顺序逐步敲一遍只是想理解 MBA 替换原理的读者重点看 4.1 中两条数学恒等式ab(a^b)2*(ab)等和 4.3 的运行结果说明即可不必亲自搭建编译环境。下面是一个完整可编译的 MBA 替换 Pass将a b替换为(a ^ b) 2 * (a b)将a - b替换为(a ^ b) - 2 * (~a b)。4.1 完整源码MBASubstitutionPass.cpp// MBASubstitutionPass.cpp // 将加法和减法替换为等价的 MBA (Mixed Boolean-Arithmetic) 表达式 // // a b → (a ^ b) 2 * (a b) // a - b → (a ^ b) - 2 * (~a b) // // 编译 // clang -shared -fPIC -o libMBASub.so MBASubstitutionPass.cpp \ // $(llvm-config --cxxflags --ldflags --libs core support passes) \ // -fno-rtti // // 使用 // opt -load-pass-plugin./libMBASub.so -passesmba-sub input.ll -S -o output.ll #include llvm/IR/Function.h #include llvm/IR/IRBuilder.h #include llvm/IR/Instructions.h #include llvm/IR/PassManager.h #include llvm/Passes/PassBuilder.h #include llvm/Passes/PassPlugin.h #include llvm/Support/raw_ostream.h #include llvm/ADT/Statistic.h #include llvm/Support/Debug.h #include random #define DEBUG_TYPE mba-sub STATISTIC(NumAddSubstituted, Number of ADD instructions substituted); STATISTIC(NumSubSubstituted, Number of SUB instructions substituted); using namespace llvm; namespace { /// 将 ADD 指令 (a b) 替换为 MBA 等价形式: (a ^ b) 2 * (a b) /// 数学证明 /// a b (a ^ b) ((a b) 1) /// 因为 XOR 给出不进位的加法结果AND 给出进位左移 1 即乘以 2 static bool substituteAdd(BinaryOperator *BinOp) { IRBuilder Builder(BinOp); Value *A BinOp-getOperand(0); Value *B BinOp-getOperand(1); // (a ^ b) Value *Xor Builder.CreateXor(A, B, mba.xor); // (a b) Value *And Builder.CreateAnd(A, B, mba.and); // 2 * (a b) 即 (a b) 1 Value *Shl Builder.CreateShl(And, ConstantInt::get(And-getType(), 1), mba.shl); // (a ^ b) 2 * (a b) Value *Result Builder.CreateAdd(Xor, Shl, mba.add); BinOp-replaceAllUsesWith(Result); NumAddSubstituted; return true; } /// 将 SUB 指令 (a - b) 替换为 MBA 等价形式: (a ^ b) - 2 * (~a b) /// 数学证明 /// a - b a (~b 1) (a ^ b) - 2 * (~a b) static bool substituteSub(BinaryOperator *BinOp) { IRBuilder Builder(BinOp); Value *A BinOp-getOperand(0); Value *B BinOp-getOperand(1); // (a ^ b) Value *Xor Builder.CreateXor(A, B, mba.xor); // ~a Value *NotA Builder.CreateNot(A, mba.not); // (~a b) Value *And Builder.CreateAnd(NotA, B, mba.and); // 2 * (~a b) 即 (~a b) 1 Value *Shl Builder.CreateShl(And, ConstantInt::get(And-getType(), 1), mba.shl); // (a ^ b) - 2 * (~a b) Value *Result Builder.CreateSub(Xor, Shl, mba.sub); BinOp-replaceAllUsesWith(Result); NumSubSubstituted; return true; } struct MBASubstitutionPass : public PassInfoMixinMBASubstitutionPass { /// 可选控制替换概率0.0 ~ 1.0避免全部替换导致性能下降过于明显 double SubstitutionProbability 1.0; PreservedAnalyses run(Function F, FunctionAnalysisManager AM) { bool Changed false; std::mt19937 RNG(42); // 固定种子保证可重现 std::uniform_real_distributiondouble Dist(0.0, 1.0); // 收集需要替换的指令不能在迭代中修改 SmallVectorBinaryOperator *, 16 AddInsts; SmallVectorBinaryOperator *, 16 SubInsts; for (auto BB : F) { for (auto I : BB) { if (auto *BinOp dyn_castBinaryOperator(I)) { // 只处理整数类型 if (!BinOp-getType()-isIntegerTy()) continue; if (BinOp-getOpcode() Instruction::Add) { if (Dist(RNG) SubstitutionProbability) AddInsts.push_back(BinOp); } else if (BinOp-getOpcode() Instruction::Sub) { if (Dist(RNG) SubstitutionProbability) SubInsts.push_back(BinOp); } } } } // 执行替换 for (auto *BinOp : AddInsts) { substituteAdd(BinOp); BinOp-eraseFromParent(); Changed true; } for (auto *BinOp : SubInsts) { substituteSub(BinOp); BinOp-eraseFromParent(); Changed true; } LLVM_DEBUG(dbgs() MBASub: F.getName() — substituted NumAddSubstituted ADDs, NumSubSubstituted SUBs\n); return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); } }; } // anonymous namespace // 注册为 out-of-tree Pass 插件 extern C LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() { return { LLVM_PLUGIN_API_VERSION, MBASubstitution, LLVM_VERSION_STRING, [](PassBuilder PB) { // 注册 Pass 名称 mba-sub PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager FPM, ArrayRefPassBuilder::PipelineElement) { if (Name mba-sub) { FPM.addPass(MBASubstitutionPass()); return true; } return false; }); // 也可以注册为 O0 优化管线的 EP自动触发 PB.registerOptimizerEarlyEPCallback( [](ModulePassManager MPM, OptimizationLevel Level) { if (Level OptimizationLevel::O0) { // O0 时不自动启用需显式指定 return; } FunctionPassManager FPM; FPM.addPass(MBASubstitutionPass()); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); }); } }; }4.2 CMakeLists.txtcmake_minimum_required(VERSION 3.20) project(MBASubstitution) find_package(LLVM REQUIRED CONFIG) message(STATUS Found LLVM ${LLVM_PACKAGE_VERSION}) message(STATUS Using LLVMConfig.cmake in: ${LLVM_DIR}) include_directories(${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) add_definitions(${LLVM_DEFINITIONS_LIST}) # Pass 必须禁用 RTTI与 LLVM 保持一致 if(NOT LLVM_ENABLE_RTTI) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -fno-rtti) endif() add_library(MBASub MODULE MBASubstitutionPass.cpp) # 不链接 LLVM 库作为插件加载时由 opt/clang 提供符号 set_target_properties(MBASub PROPERTIES CXX_STANDARD 17 PREFIX lib SUFFIX .so )4.3 编译与运行# 构建 Pass 插件 mkdir build cd build cmake .. -DLLVM_DIR/path/to/llvm/build/lib/cmake/llvm make -j$(nproc) # 生成测试 IR cat test.c EOF int add_example(int a, int b) { return a b; } int sub_example(int a, int b) { return a - b; } EOF clang -O0 -emit-llvm -S test.c -o test.ll # 运行 Pass opt -load-pass-plugin./libMBASub.so -passesmba-sub test.ll -S -o output.ll # 查看替换结果 cat output.ll # 原来的 add 指令将变成 xor and shl add 的序列 # 原来的 sub 指令将变成 xor not and shl sub 的序列4.4 多层嵌套增强对输出再次运行 PassMBA 表达式会继续展开指数级增加复杂度# 第二轮替换已有的 MBA 中的 add/sub 会被再次展开 opt -load-pass-plugin./libMBASub.so -passesmba-sub output.ll -S -o output2.ll # 第三轮 opt -load-pass-plugin./libMBASub.so -passesmba-sub output2.ll -S -o output3.ll5. 在 Android NDK 中集成知识定位实践应用面向需要把自定义 Pass 真正用到移动端项目构建中的开发者。如果你不是在做 Android native 混淆集成这里只需知道写好的 Pass 可以通过-fpass-plugin挂到 NDK 编译流程里这个事实。# CMakeLists.txt set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -fpass-plugin${CMAKE_SOURCE_DIR}/libs/libMyObfPass.so)注意Pass 插件需要针对 NDK 使用的 LLVM 版本编译。6. 调试 Pass 的方法知识定位实践应用。这是开发 Pass 过程中排错的工具箱命令行调试、IR dump 对比、FileCheck 回归测试、GDB 断点只有在你真正写 Pass 遇到问题时才会用到了解性阅读可以跳过具体命令只需知道Pass 出 bug 时有这几类调试手段可用。6.1 使用 opt 命令行调试# 以 debug 模式运行 Pass需要 LLVM Debug 构建或 STATISTIC 宏 opt -load-pass-plugin./libMBASub.so -passesmba-sub \ -debug -stats test.ll -S -o /dev/null # 只打印特定 Pass 的调试信息 opt -load-pass-plugin./libMBASub.so -passesmba-sub \ -debug-onlymba-sub test.ll -S -o /dev/null # 打印 Pass 前后的 IR diff非常有用 opt -load-pass-plugin./libMBASub.so \ -passesprintmodule,mba-sub,printmodule \ test.ll -S -o /dev/null 21 | less6.2 Dump IR 进行对比# 使用 -print-after-all 打印每个 Pass 执行后的 IR opt -load-pass-plugin./libMBASub.so -passesmba-sub \ -print-after-all test.ll -S -o output.ll 2 ir_dumps.txt # 使用 -print-before / -print-after 精确控制 opt -load-pass-plugin./libMBASub.so \ -passesmba-sub \ -print-beforemba-sub \ -print-aftermba-sub \ test.ll -S -o output.ll 2 before_after.txt # diff 对比 Pass 前后变化 diff (opt -passesprintmodule test.ll -S 21) \ (opt -load-pass-plugin./libMBASub.so -passesmba-sub test.ll -S 21) \ --color6.3 使用 FileCheck 做回归测试FileCheck 是 LLVM 内置的模式匹配测试工具用于验证 Pass 的输出是否符合预期。; RUN: opt -load-pass-plugin%plugindir/libMBASub.so \ ; RUN: -passesmba-sub -S %s | FileCheck %s ; 测试 ADD 替换 define i32 test_add(i32 %a, i32 %b) { entry: %result add i32 %a, %b ret i32 %result } ; CHECK-LABEL: test_add ; CHECK-NOT: add i32 %a, %b ; CHECK: %mba.xor xor i32 %a, %b ; CHECK: %mba.and and i32 %a, %b ; CHECK: %mba.shl shl i32 %mba.and, 1 ; CHECK: %mba.add add i32 %mba.xor, %mba.shl ; CHECK: ret i32 %mba.add ; 测试 SUB 替换 define i32 test_sub(i32 %a, i32 %b) { entry: %result sub i32 %a, %b ret i32 %result } ; CHECK-LABEL: test_sub ; CHECK-NOT: sub i32 %a, %b ; CHECK: %mba.xor xor i32 %a, %b ; CHECK: %mba.not xor i32 %a, -1 ; CHECK: %mba.and and i32 %mba.not, %b ; CHECK: %mba.shl shl i32 %mba.and, 1 ; CHECK: %mba.sub sub i32 %mba.xor, %mba.shl ; CHECK: ret i32 %mba.sub ; 测试浮点数不应被替换 define float test_float_add(float %a, float %b) { entry: %result fadd float %a, %b ret float %result } ; CHECK-LABEL: test_float_add ; CHECK: fadd float %a, %b运行测试# 单个测试文件 llvm-lit -v test/mba_sub_test.ll # 批量运行 llvm-lit -v test/6.4 使用 GDB/LLDB 调试 Pass 本身# 使用 gdb 调试 opt 进程中的 Pass 逻辑 gdb --args opt -load-pass-plugin./libMBASub.so -passesmba-sub test.ll -S # 在 gdb 中设置断点 (gdb) break MBASubstitutionPass::run (gdb) run # 到达断点后可以检查 Function、BasicBlock、Instruction 等 LLVM 数据结构 (gdb) p F.getName().str() (gdb) p BinOp-getOpcodeName()7. 常见 Pass 编写陷阱知识定位实践应用是开发者踩坑后总结的检查清单PHI 节点、异常处理、metadata、迭代器失效。真正写 Pass 时建议对照检查非开发读者可以把它当作原来混淆 Pass 要考虑这么多编译器内部细节的背景认知不需要记住每条规则的具体写法。7.1 PHI 节点处理PHI 节点是 SSA 形式中合并不同前驱基本块值的特殊指令必须位于基本块的最前面。常见错误在 PHI 节点前插入指令// 错误示范直接在 BasicBlock 开头插入可能破坏 PHI 节点 IRBuilder Builder(*BB.begin()); // 如果 BB 以 PHI 开头这里就出问题了 Builder.CreateXor(A, B); // 正确做法跳过 PHI 节点 IRBuilder Builder(*BB.getFirstInsertionPt()); Builder.CreateXor(A, B);常见错误替换指令时未更新 PHI 的 incoming value// 如果被替换的值在其他基本块的 PHI 中被引用 // replaceAllUsesWith 会自动处理但如果你手动遍历 use要注意 PHI for (auto U : OldVal-uses()) { if (auto *PHI dyn_castPHINode(U.getUser())) { // PHI 节点的 operand 与 BasicBlock 是成对的 // 不能单独修改 value 而不考虑对应的 block } } // 最安全的方式始终使用 replaceAllUsesWith OldInst-replaceAllUsesWith(NewVal); OldInst-eraseFromParent();常见错误在 PHI 节点所在的块前面分裂边时未更新 PHI// 如果你的 Pass 会分裂关键边critical edge // 需要调用 SplitCriticalEdge 并确保 PHI 自动更新 // 使用 LLVM 提供的工具函数而非手动操作 #include llvm/Transforms/Utils/BasicBlockUtils.h SplitCriticalEdge(TermInst, SuccIdx);7.2 异常处理兼容性C 异常处理引入了invoke、landingpad、resume、catchswitch、catchpad、cleanuppad等特殊指令混淆 Pass 必须正确处理。规则 1不要将 invoke 当作普通 call// invoke 有两个后继正常路径和异常路径 // 错误只检查 CallInst if (auto *CI dyn_castCallInst(I)) { ... } // 正确同时处理 CallInst 和 InvokeInst或使用 CallBase if (auto *CB dyn_castCallBase(I)) { // 这同时匹配 call、invoke 和 callbr }规则 2不要在 landingpad 的基本块中插入非法指令// landingpad 必须是基本块的第一条非 PHI 指令 // 如果你的 Pass 需要在这类块中操作使用 getFirstInsertionPt() if (BB.isLandingPad()) { // 谨慎操作或直接跳过此块 continue; }规则 3控制流平坦化不能破坏 EH (Exception Handling) 结构// 如果你的 Pass 实现 CFFControl Flow Flattening控制流平坦化 // 遇到包含 invoke/landingpad 的函数时最安全的做法是跳过 bool hasEH false; for (auto BB : F) { if (BB.isEHPad() || isaInvokeInst(BB.getTerminator())) { hasEH true; break; } } if (hasEH) { LLVM_DEBUG(dbgs() Skipping F.getName() : contains exception handling\n); return PreservedAnalyses::all(); }7.3 Metadata 保留LLVM IR 中的 metadata如!dbg、!tbaa、!range、!prof对调试信息、类型别名分析和 PGO 至关重要。规则 1新指令应继承原指令的调试位置// 错误新指令没有 debug location调试时无法定位源码行 Value *NewInst Builder.CreateXor(A, B); // 正确复制原指令的 debug location IRBuilder Builder(OrigInst); Builder.SetCurrentDebugLocation(OrigInst-getDebugLoc()); Value *NewInst Builder.CreateXor(A, B); // 自动继承 Builder 的 debug location规则 2不要丢失 TBAA metadata// 如果替换 load/store 指令新指令应保留 TBAA 信息 if (auto *LI dyn_castLoadInst(OrigInst)) { auto *NewLoad Builder.CreateLoad(LI-getType(), NewPtr); // 复制所有 metadata NewLoad-copyMetadata(*LI); }规则 3正确声明 Preserved Analyses// 如果你的 Pass 只修改指令内容但不改变 CFG return PreservedAnalyses::allInSetCFGAnalyses(); // 如果你的 Pass 修改了 CFG如 CFF return PreservedAnalyses::none(); // 不要过度声明 Preserved导致后续 Pass 使用过时的 Analysis 缓存 // 也不要过度声明 Invalidated导致不必要的重新计算拖慢编译7.4 迭代器失效// 错误在遍历过程中删除/替换指令会导致迭代器失效 for (auto I : BB) { if (shouldReplace(I)) { I.eraseFromParent(); // 崩溃迭代器已失效 } } // 正确先收集再处理 SmallVectorInstruction *, 16 ToReplace; for (auto I : BB) { if (shouldReplace(I)) ToReplace.push_back(I); } for (auto *I : ToReplace) { // 替换并删除 I-replaceAllUsesWith(NewVal); I-eraseFromParent(); } // 或者使用 make_early_inc_rangeC20 / LLVM 提供 for (auto I : llvm::make_early_inc_range(BB)) { if (shouldReplace(I)) { I.replaceAllUsesWith(NewVal); I.eraseFromParent(); } }参考资料LLVM 官方文档 — Writing an LLVM Passhttps://llvm.org/docs/WritingAnLLVMNewPMPass.html 新 Pass Manager 下编写 Pass 的权威指南。LLVM 官方文档 — Using New Pass Managerhttps://llvm.org/docs/NewPassManager.html New PM 的架构设计和使用方式详解。LLVM Programmers Manualhttps://llvm.org/docs/ProgrammersManual.html LLVM IR 数据结构Value、Instruction、BasicBlock 等的完整参考。LLVM 官方文档 — LLVM Testing Infrastructure Guidehttps://llvm.org/docs/TestingGuide.html FileCheck、llvm-lit 等测试工具的使用说明。Hikari 源码https://github.com/HikariObfuscator/Hikari 最活跃的 OLLVM 分支是学习混淆 Pass 工程实践的优秀参考。banach-space/llvm-tutorhttps://github.com/banach-space/llvm-tutor 一系列由浅入深的 LLVM Pass 教学示例强烈推荐初学者。Trail of Bits — Building a Pass for LLVMhttps://blog.trailofbits.com/ Trail of Bits 博客中有多篇关于 LLVM Pass 开发的实战文章。OLLVM 原始论文J. Junod et al., Obfuscator-LLVM — Software Protection for the Masses, SPRO 2015. 理解 OLLVM 三大核心 PassCFF、BCFBogus Control Flow虚假控制流、Sub的设计思路。