-
-
[原创] LLVM NEW PASS Learning Notes
-
发表于: 2025-3-4 18:07 3858
-
一直对代码混淆比较感兴趣,过去的半年刚好有个机会能让我学习并进行了一定的实践,希望将其中的相关经验分享给师傅们,第一篇是对 LLVM NEW Pass 的学习归纳,后续会分享一些混淆的魔改思路与实现思路
实验环境:Ubuntu 24.04 LLVM 17
项目地址:https://github.com/zzzcccyyyggg/Kotoamatsukami
LLVM 通过一系列的passes来优化IR文件,不同的Pass作用在IR文件不同粒度上,如Function,Module。而Pass中的操作分为两种,Transformation 与 Analysis ,分别用于转化IR文件与收集IR文件中的信息,而一系列的pass则合集则被称为pass pipeline。
编译相关步骤可参考:https://llvm.org/docs/CMake.html
我的编译命令
编译完成后使用以下命令完成安装,会将编译产物安装到DCMAKE_INSTALL_PREFIX
指定的文件夹
这里最好安装一下,不然没有LLVMConfig.cmake
此文件,无法在CMakeLists.txt
中使用find_package()
为了提升框架的灵活程度,LLVM将源文件->可执行文件的步骤打碎,分为不同的Passes
而控制这些Passes正确运行的,就是Pass Manager,而Pass 往往按照其工作范围分为以下几类:
Module Pass 以整个模块为输入。它在给定模块上执行其工作,可以用于模块内的过程间操作。由于它处理的是整个模块,模块 pass 可以访问和修改模块中的所有函数和全局变量。这使得它特别适合那些需要跨多个函数进行分析或优化的任务。
Call Graph Pass 操作的是调用图的强连通分量(SCCs)。调用图是一个图结构,其中节点表示函数,边表示函数之间的调用关系。SCC 是图中所有节点互相可达的最大子图。调用图 pass 从底向上遍历这些组件,即从被调用函数开始,逐步向上处理调用它们的函数。
Function Pass 以单个函数为输入,只对该函数进行操作。它在函数的级别上执行分析或转换,不涉及其他函数。这使得函数 pass 简单且高效,因为它只需要处理一个函数的代码,而无需考虑模块中其他部分的状态。
Loop Pass作用于函数内的循环。它只处理特定循环的内容,进行针对性的优化和转换。
如开头所提到,在 LLVM 中,除了用于改变IR代码的Transformation Pass
外 ,还存在着Analysis Pass
用于分析IR
得到其CFG
等相关分析结果 而这些分析结果需要在Pass之间传递,但是当Pass对代码进行改动时,原有的分析结果可能会发生改变,这就需要我们决定是否保存相关分析结果,或是重新分析。所以编写我们的新Pass的时候,需要注意的分析结果的保存与丢弃,尽量减少重复分析。
参考上图,pass 以流水线方式执行。例如,如果有几个函数 pass 应按顺序执行,pass 管理器会在第一个函数上运行每个函数 pass,然后在第二个函数上运行所有函数 pass,依此类推。这种方法的基本思想是通过在有限的数据集(一个 IR 函数)上执行转换,然后移动到下一个有限的数据集,来改善缓存行为。
那么如何实现一个New Pass,首先需要将我们的Pass注册到PassManager中,详情参考代码:https://github.com/zzzcccyyyggg/Kotoamatsukami/blob/llvm-17-plugins/src/PassPlugin.cpp
可以看到以上代码有两处的MPM.addPass
,其分别对应着两种对Pass的调用方式,其中第一种方式是通过 opt 主动调用对应的pass,使用示例如下
而第二种方式则是将 Pass 插入到Clang、opt等工具提供的 Pipeline 中的某些节点,当这些Pipeline被调用时,我们的Pass就会在Pipeline的对应位置被调用,使用示例如下
而调用Pass有两种方式,一种是将其编进LLVM源码树,另一种是将其编译为Pass Plugin,在需要使用的时候加载对应的Plugin,我这里采用的是源码树外构建Pass Plugin的方式,个人感觉比较方便,源码树内构建的话可以参考https://github.com/bluesadi/Pluto
参考CMakeLists.txt如下
这里CMakeLists
搞了好一阵子,主要是研究了下find_package
的用法,参考以下两篇文章:https://blog.csdn.net/qq_39466755/article/details/130912344 、https://zhuanlan.zhihu.com/p/50829542
这里首先明确find_package
有两种模式,如下
module模式
在这个模式下会查找一个名为find.cmake的文件,首先去CMAKE_MODULE_PATH指定的路径下去查找,然后去cmake安装提供的查找模块中查找(安装cmake时生成的一些cmake文件)。找到之后会检查版本,生成一些需要的信息。
config模式
在这个模式下会查找一个名为-config.cmake(<小写包名>-config.cmake)或者Config.cmake 的文件,如果指定了版本信息也会搜索名为-config-version.cmake 或者 ConfigVersion.cmake的文件。
而LLVM编译安装后会在/install/lib/llvm下存在相应的LLVMConfig.cmake
,而其主要搜寻路径如下
故只需要设置LLVM_DIR并将find_package改为搜寻config模式即可
编译成功后即可使用clang或opt调用
《Learn_LLVM_17_A_beginner's_guide_to_learning_LLVM_2nd》
git clone --depth 1 -b release
/17
.x https:
//github
.com
/llvm/llvm-project
.git
git clone --depth 1 -b release
/17
.x https:
//github
.com
/llvm/llvm-project
.git
cmake -G Ninja -DLLVM_ENABLE_PROJECTS=
"clang;lld"
-DLLVM_TARGETS_TO_BUILD=
"X86;ARM;AArch64"
-DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_RTTI=ON -DCMAKE_INSTALL_PREFIX=.
/build/
..
/llvm-project/llvm
ninja -j4
cmake -G Ninja -DLLVM_ENABLE_PROJECTS=
"clang;lld"
-DLLVM_TARGETS_TO_BUILD=
"X86;ARM;AArch64"
-DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF -DLLVM_ENABLE_RTTI=ON -DCMAKE_INSTALL_PREFIX=.
/build/
..
/llvm-project/llvm
ninja -j4
ninja
install
ninja
install
#include "llvm/Passes/PassPlugin.h"
#include "AntiDebugPass.h"
#include "BogusControlFlow.h"
#include "Flatten.h"
#include "GVEncrypt.h"
#include "IndirectBranch.h"
#include "IndirectCall.h"
#include "SplitBasicBlock.h"
#include "Substitution.h"
#include "include/AddJunkCodePass.h"
#include "include/Branch2Call.h"
#include "include/Branch2Call_32.h"
#include "include/ForObsPass.h"
#include "include/Loopen.hpp"
#include "llvm/Passes/PassBuilder.h"
using namespace llvm;
llvm::PassPluginLibraryInfo getKotoamatsukamiPluginInfo()
{
return
{
LLVM_PLUGIN_API_VERSION,
"Kotoamatsukami"
, LLVM_VERSION_STRING,
[](PassBuilder& PB) {
/
/
first way to use the
pass
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager& MPM, ArrayRef<PassBuilder::PipelineElement>) {
if
(Name
=
=
"gv-encrypt"
){
MPM.addPass(GVEncrypt());
return
true;
}
else
if
(Name
=
=
"split-basic-block"
) {
MPM.addPass(SplitBasicBlock());
return
true;
}
else
if
……
return
false;
});
/
/
second way to use the
pass
PB.registerPipelineStartEPCallback(
[](ModulePassManager& MPM, OptimizationLevel Level) {
MPM.addPass(AntiDebugPass());
MPM.addPass(SplitBasicBlock());
MPM.addPass(GVEncrypt());
MPM.addPass(BogusControlFlow());
MPM.addPass(AddJunkCodePass());
MPM.addPass(Loopen());
MPM.addPass(ForObsPass());
MPM.addPass(Branch2Call_32());
MPM.addPass(Branch2Call());
MPM.addPass(IndirectCall());
MPM.addPass(IndirectBranch());
MPM.addPass(Flatten());
MPM.addPass(Substitution());
});
}
};
}
__attribute__((visibility(
"default"
))) extern
"C"
LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo()
{
return
getKotoamatsukamiPluginInfo();
}
#include "llvm/Passes/PassPlugin.h"
#include "AntiDebugPass.h"
#include "BogusControlFlow.h"
#include "Flatten.h"
#include "GVEncrypt.h"
#include "IndirectBranch.h"
#include "IndirectCall.h"
#include "SplitBasicBlock.h"
#include "Substitution.h"
#include "include/AddJunkCodePass.h"
#include "include/Branch2Call.h"
#include "include/Branch2Call_32.h"
#include "include/ForObsPass.h"
#include "include/Loopen.hpp"
#include "llvm/Passes/PassBuilder.h"
using namespace llvm;
llvm::PassPluginLibraryInfo getKotoamatsukamiPluginInfo()
{
return
{
LLVM_PLUGIN_API_VERSION,
"Kotoamatsukami"
, LLVM_VERSION_STRING,
[](PassBuilder& PB) {
/
/
first way to use the
pass
PB.registerPipelineParsingCallback(
[](StringRef Name, ModulePassManager& MPM, ArrayRef<PassBuilder::PipelineElement>) {
赞赏
- [原创] LLVM NEW PASS Learning Notes 3859
- [原创]Branch2Call 一种基于LLVM的混淆 8481
- [原创]强网杯MINIRE 12275
- [原创]第七届强网拟态RE WP 13780
- [原创] 2 1548