让LLVM16在windows上再次优雅起来
2023-12-14 17:54:7 Author: mp.weixin.qq.com(查看原文) 阅读量:17 收藏


前言

有许多文章介绍了可以在windows动态加载的pass插件的方式使用LLVM,但都是针对一些老版本的LLVM,譬如12、8等。本文以LLVM16进行动态编译适配VS2022 pro。


前提准备

前提准备按照此https://bbs.kanxue.com/thread-272346.htm的前提准备即可,需要注意的是一切版本安装最新的即可。


编译动态库版本 LLVM

3.1 下载并解压 LLVM 源码

和前面文章的不同,我们下载的源码文件比他多了一个clang源码,注意此源码是必须的,经过教训得出这个结论,下面是我的源码目录结构:

https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/clang-16.0.4.src.tar.xz
https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/cmake-16.0.4.src.tar.xz
https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/lld-16.0.4.src.tar.xz
https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.4/llvm-16.0.4.src.tar.xz

3.2 修改 LLVM 源码

这里需要修改一下LLVM的源码,首先是llvm\lib\CMakeLists.txt文件,因为本身在window上编译是没有Mac的环境,因此会报一些Mac的头文件错误,我们只需要MACRO的组件去掉就行了。

还有就是注释完此行之后会有一些地方在引用MACRO会产生一些报错,直接修改就完事了,策略就是哪里报错改哪里(因为我也记不住改的哪里了)。

编译源码的时候可以按照前文的方式进行编译,编译的时候推荐加上-DCMAKE_VERBOSE_MAKEFILE=ON选项,这样可以输出详细的构建信息,方便修改源码。下面是我构建完成的LLVM。

3.4 给 LLVM 打上补丁

因为是使用gcc编译的会缺少一些dll,因此我们要把这些dll加入路径中。


通过新通行证编写PASS插件

1、llvm的PASS介绍

简单来说就是llvm在编译的时候加了一层IR,如下图所示,而LLVM又给我们提供了一些列的接口,可以通过Pass插件操控和分析LLVM IR的生成。因以vs的cl编译为例对llvm pass的介绍这里就不做过的的赘述了,详细的介绍可以参考此文文章https://kiprey.github.io/2020/06/LLVM-IR-pass。

在进行下面的知识以前有一些预热的知识关于,关于ll文件bc文件的含义参考:https://zhuanlan.zhihu.com/p/596579389?utm_id=0。

# 生成ll 文件
clang++ -S -O0 -emit-llvm test.cpp

# 生成bc文件
opt test.ll -o test.bc

2、新的通行证管理器下的Hello World!

和老版本的Hello World!不同新通行证,这个pass的功能非常简单,就是打印函数名字和参数个数,示例如下:

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"

using namespace llvm;

namespace {

// 通过PassInfoMixin模板类简化注册过程
struct HelloWorld : PassInfoMixin<HelloWorld> {
// Pass插件主入口点,为回调函数,会把源码的函数IR对象和和函数函数管理对象传递此函数
PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {

// 打印了函数名字和参数个数
errs() << "Hello from: "<< F.getName() << "\n";
errs() << "number of arguments: " << F.arg_size() << "\n";

//表示该 Pass 不会改变任何分析结果。
return PreservedAnalyses::all();
}
static bool isRequired() { return true; }
};
}

// 导出函数返回LLVM 插件的信息,包括 LLVM插件API版本、插件名称、LLVM 版本字符串以及注册插件的回调函数
llvm::PassPluginLibraryInfo getHelloWorldPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "HelloWorld", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name, FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "hello-world") {
FPM.addPass(HelloWorld());
return true;
}
return false;
});
}};
}

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return getHelloWorldPluginInfo();
}

编写完源码之后我们构建CMakeLists.txt,下面是我的CMakeLists可以进行参考:

#项目名称
project(hello-world)

#cmake最低版本
cmake_minimum_required(VERSION 3.16)

# 设置LLVM的安装路径
if(NOT DEFINED ENV{LLVM_HOME})
# User must define the LLVM_HOME environment that point to the root installation dir of llvm
message(FATAL_ERROR "Environment variable $LLVM_HOME is not defined, user should define it before running cmake!")
endif()

message(STATUS "LLVM_HOME = [$ENV{LLVM_HOME}]")

# Default llvm config file path
set(ENV{LLVM_DIR} "$ENV{LLVM_HOME}\\lib\\cmake\\llvm")
message(STATUS "LLVM_DIR : ${LLVM_DIR}")

# Check the path
if (NOT EXISTS $ENV{LLVM_DIR})
message(STATUS "Path ($ENV{LLVM_DIR}) not found!")

# If default llvm config path not found, try this one,
# which is config with [-DLLVM_LIBDIR_SUFFIX=64] before building llvm
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib64/cmake/llvm)
if (NOT EXISTS $ENV{LLVM_DIR})
message(FATAL_ERROR "Path ($ENV{LLVM_DIR}) not found!")
else()
message(STATUS "LLVM_DIR ($ENV{LLVM_DIR}) found!")
endif()
else()
message(STATUS "LLVM_DIR ($ENV{LLVM_DIR}) found!")
endif()

# 查找并加载LLVM的CMake模块
find_package(LLVM REQUIRED CONFIG)

# 将LLVM的CMake模块路径添加到CMake模块路径中
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")

# 添加LLVM的头文件路径
include_directories(${LLVM_INCLUDE_DIRS})

# 添加LLVM的库路径
link_directories(${LLVM_LIBRARY_DIRS})

# 添加LLVM的依赖库
add_definitions(${LLVM_DEFINITIONS})

# Debug
message(STATUS "CMAKE_MODULE_PATH : ${LLVM_CMAKE_DIR}")
message(STATUS "LLVM_DEFINITIONS : ${LLVM_DEFINITIONS}")
message(STATUS "LLVM_INCLUDE_DIRS : ${LLVM_INCLUDE_DIRS}")
message(STATUS "LLVM_LIBRARY_DIRS : ${LLVM_LIBRARY_DIRS}")

# 添加项目文件
add_library( hello-world SHARED
hello-world.cpp
)

# Use C++11 to compile your pass (i.e., supply -std=c++11).
target_compile_features(hello-world PRIVATE cxx_range_for cxx_auto_type)

include_directories(./)

# LLVM is (typically) built with no C++ RTTI. We need to match that;
# otherwise, we'll get linker errors about missing RTTI data.
set_target_properties(hello-world PROPERTIES COMPILE_FLAGS "-fno-rtti")

# Get proper shared-library behavior (where symbols are not necessarily
# resolved when the shared library is linked) on OS X.
if(APPLE)
set_target_properties(hello-world PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
endif(APPLE)

# 添加多线程支持
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
target_link_libraries(hello-world Threads::Threads)

# 添加链接库
target_link_libraries(hello-world
libLLVMCore.dll.a
libLLVMSupport.dll.a
libLLVMipo.dll.a
libLLVMPasses.dll.a
libLLVMTransformUtils.dll.a
libclang.dll.a
libclangAnalysis.dll.a
libclangAnalysisFlowSensitive.dll.a
libclangAnalysisFlowSensitiveModels.dll.a
libclangAPINotes.dll.a
libclangARCMigrate.dll.a
libclangAST.dll.a
libclangASTMatchers.dll.a
libclangBasic.dll.a
libclangCodeGen.dll.a
libclangCrossTU.dll.a
libclangDependencyScanning.dll.a
libclangDirectoryWatcher.dll.a
libclangDriver.dll.a
libclangDynamicASTMatchers.dll.a
libclangEdit.dll.a
libclangExtractAPI.dll.a
libclangFormat.dll.a
libclangFrontend.dll.a
libclangFrontendTool.dll.a
libclangHandleCXX.dll.a
libclangHandleLLVM.dll.a
libclangIndex.dll.a
libclangIndexSerialization.dll.a
libclangInterpreter.dll.a
)

写完cmake之后构建编译脚本,下面是我的编译脚本,其中路径信息根据自己的情况进行修改。

:: Set MSYS2 env
set PATH=%PATH%;C:\msys64\mingw64\bin

:: Set LLVM_HOME
set LLVM_HOME=C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\Llvm\x64
set CC=%LLVM_HOME%/bin/clang.exe
set CXX=%LLVM_HOME%/bin/clang++.exe

rd /Q /S .\build

:: Build release version
cmake -S ./ -B ./build -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON -G "MinGW Makefiles" -DCMAKE_VERBOSE_MAKEFILE=ON
:: Build debug version
:: cmake -S ./ -B ./build -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_ASSERTIONS=ON

:: Build project
cmake --build ./build -j 4

pause

3、运行自己的Hello-World通行证

编译好LLVMPass插件之后,我们就可以使用这个插件了,下面我以test.cpp为例:

#include <stdio.h>
void fun1(const char* log)
{
puts(log);
}
void fun2(int n1, int n2)
{
printf("%d:::%d", n1, n2);
}

int main(int argc, char* argv[]){
fun1("hello Pass!");
fun2(5, 6);
return 0;
}

首先我们要用clang编译器生成我们的ll文件,会生成如下的代码,此时已经生成了LLVM IR代码。

clang -O1 -S -emit-llvm test.cpp -o test.ll

生成LLVM IR代码之后,我们要使用我们的pass插件修改LLVM IR代码,用以下命令,可以看到已经成功调用我们的插件了,成功的输出了我们的函数名称和参数。

opt -load-pass-plugin libhello-world.dll --passes="hello-world" test.ll -o test.bc

上面生成的是bc文件,我们也可以把bc文件再次翻译成ll文件使用以下命令:

llvm-dis test.bc -o test2.ll

当生成bc文件之后,我们就可以使用llc把bc文件翻译.s文件了,或者直接编译成obj文件,通过一下命令进行编译:

llc test.bc -o test.s/test.obj

最后在使用clang编译器编译成可执行文件:

clang test.s -o test.exe

4、clang自动加载Pass

使用上面的方法加载pass无疑是非常麻烦的,因此我们可以直接使用clang编译器加载Pass,使用以下命令进行加载,使用此方式必须有一个前提,那就是registerPipelineStartEPCallback注册管道回调函数。下面就让我们来改造我们的HelloWorld!

clang.exe -Xclang -fpass-plugin="libHelloWorld.dll" .\test.cpp -o test.exe

PreservedAnalyses run(Function &F, FunctionAnalysisManager &)这就不能使用函数管理对象了,要修改成模块管理对象。修改如下:

  

PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) {
for (Function& F : M)
{
errs() << "(llvm-tutor) Hello from: " << F.getName() << "\n";
errs() << "number of arguments: " << F.arg_size() << "\n";
}
return PreservedAnalyses::all();
}

后面就是使用registerPipelineStartEPCallback注册回调了,下面是修改完之后的代码:

llvm::PassPluginLibraryInfo getHelloWorldPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "HelloWorld", LLVM_VERSION_STRING,
[](llvm::PassBuilder &PB) {
PB.registerPipelineStartEPCallback(
[&](llvm::ModulePassManager &MPM,
OptimizationLevel Level) {
MPM.addPass(HelloWorld());
});
}};
}

完整的代码如下:

#include "llvm/Pass.h"
#include "llvm/Passes/OptimizationLevel.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/PassManager.h"
using namespace llvm;
namespace{
struct HelloWorld : PassInfoMixin<HelloWorld> {
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) {
for (Function& F : M)
{
errs() << "(llvm-tutor) Hello from: " << F.getName() << "\n";
errs() << "number of arguments: " << F.arg_size() << "\n";
}
return PreservedAnalyses::all();
}
static bool isRequired() { return true; }
};
} // namespace

llvm::PassPluginLibraryInfo getHelloWorldPluginInfo() {
return {LLVM_PLUGIN_API_VERSION, "HelloWorld", LLVM_VERSION_STRING,
[](llvm::PassBuilder &PB) {
PB.registerPipelineStartEPCallback(
[&](llvm::ModulePassManager &MPM,
OptimizationLevel Level) {
MPM.addPass(HelloWorld());
});
}};
}

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return getHelloWorldPluginInfo();
}

下面就是效果,至此我们的就可以直接在VS2022 pro上直接动态加载PASS插件了,食用方法可参考https://bbs.kanxue.com/thread-272346.htm,VS效果如下。


字符串混淆PASS

写了两个HelloWorld之后下面我们写个字符串混淆的PASS热热身。首先注册通行证,注册同行正我们使用registerPipelineStartEPCallback回调函数进行注册,因为我们要给VS使用。

#include "llvm/Pass.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Passes/OptimizationLevel.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"

using namespace llvm;

namespace {
struct StringObfuscatorModPass : public PassInfoMixin<StringObfuscatorModPass> {
PreservedAnalyses run(Module& M, ModuleAnalysisManager& MAM) {
return PreservedAnalyses::all();
};
static bool isRequired() { return true; }
};

llvm::PassPluginLibraryInfo getStringObfuscatorPlusPluginInfo() {
return { LLVM_PLUGIN_API_VERSION, "StringObfuscatorPlus", LLVM_VERSION_STRING,
[](llvm::PassBuilder& PB) {
PB.registerPipelineStartEPCallback(
[&](llvm::ModulePassManager& MPM,
OptimizationLevel Level) {
MPM.addPass(StringObfuscatorModPass());
});
} };
}

extern "C" LLVM_ATTRIBUTE_WEAK::llvm::PassPluginLibraryInfo
llvmGetPassPluginInfo() {
return getStringObfuscatorPlusPluginInfo();
}

再写字符串加密之前我们先做一下准备工作,首先我们有一个结构体或类能够保存我们全局字符串的信息。通过这个类,可以更方便地管理和识别 LLVM IR 中的全局字符串。

class GlobalString {
public:
GlobalVariable* Glob;
unsigned int index;
int type;
int string_length;
static const int SIMPLE_STRING_TYPE = 1;
static const int STRUCT_STRING_TYPE = 2;

GlobalString(GlobalVariable* Glob, int length) : Glob(Glob), index(-1), string_length(length), type(SIMPLE_STRING_TYPE) {}
GlobalString(GlobalVariable* Glob, unsigned int index, int length) : Glob(Glob), index(index), string_length(length), type(STRUCT_STRING_TYPE) {}
};

准备完之后了接下来写字符串加密函数,我们的加密策略先简单一些,就对每个字符串的ascii值加上一就可以。

char* EncodeString(const char* Data, unsigned int Length) {
// 加密字符串
char* NewData = (char*)malloc(Length);
for (unsigned int i = 0; i < Length; i++) {
NewData[i] = Data[i] + 1;
}
return NewData;
}

写完加密函数之后我们就要获取全局字符串常量进行加密了,通过Module可以直接获取全局常量,然后判断常量是否是字符串,是的话进行加密。

vector<GlobalString*> encodeGlobalStrings(Module& M) {
vector<GlobalString*> GlobalStrings;
auto& Ctx = M.getContext();

// 开始进行编码
for (GlobalVariable& Glob : M.globals()) {
// 确保全局变量有初始化器。如果全局变量没有初始化器,可能是外部定义的,这种情况下就不进行编码。
// 确保全局变量没有外部链接。外部链接的全局变量可能在其他模块中定义。
if (!Glob.hasInitializer() || Glob.hasExternalLinkage())
continue;

// 获取全局变量的初始化器。
Constant* Initializer = Glob.getInitializer();

// 判断初始化器是否是ConstantDataArray类型。ConstantDataArray是 LLVM 中表示常量数据数组的类。
if (isa<ConstantDataArray>(Initializer)) {
// 将初始化器强制转换为ConstantDataArray类型。
auto CDA = cast<ConstantDataArray>(Initializer);
// 检查ConstantDataArray对象是否表示一个字符串。
if (!CDA->isString())
continue;

// 获取字符指针,和大小
StringRef StrVal = CDA->getAsString();
const char* Data = StrVal.begin();
const int Size = StrVal.size();

// 编码字符串,创建一个新的ConstantDataArray对象。
char* NewData = EncodeString(Data, Size);
Constant* NewConst = ConstantDataArray::getString(Ctx, StringRef(NewData, Size), false);

// 设置为全局变量的新初始化器。
Glob.setInitializer(NewConst);

// 将一个指向全局变量及其大小的GlobalString对象添加到GlobalStrings容器中
GlobalStrings.push_back(new GlobalString(&Glob, Size));
Glob.setConstant(false);
}
// 判断初始化器是不是结构体类型
else if (isa<ConstantStruct>(Initializer)) {
// 处理结构体
auto CS = cast<ConstantStruct>(Initializer);
if (Initializer->getNumOperands() > 1)
continue;

// 变量结构体成员
for (unsigned int i = 0; i < Initializer->getNumOperands(); i++) {
auto CDA = dyn_cast<ConstantDataArray>(CS->getOperand(i));
// 判断是否是字符串
if (!CDA || !CDA->isString())
continue;

// 是字符串获取字符串
StringRef StrVal = CDA->getAsString();
const char* Data = StrVal.begin();
const int Size = StrVal.size();

// 创建编码的字符串
char* NewData = EncodeString(Data, Size);
Constant* NewConst = ConstantDataArray::getString(Ctx, StringRef(NewData, Size), false);

// 覆盖结构体成员
CS->setOperand(i, NewConst);

GlobalStrings.push_back(new GlobalString(&Glob, i, Size));
Glob.setConstant(false);
}
}
}

return GlobalStrings;
}

将每个全局常量字符串加密之后,我们就要进行解密,首先我们要创建一个解密函数。

Function* createDecodeFunc(Module& M) {
auto& Ctx = M.getContext();

// 创建函数声明,参数是int8指针类型,也就是char*,和int32类型,字符串的长度, 返回值void类型
FunctionCallee DecodeFuncCallee = M.getOrInsertFunction("decode",
/*ret*/ Type::getVoidTy(Ctx),
/*args*/ Type::getInt8PtrTy(Ctx, 8),
Type::getInt32Ty(Ctx));

// 通过函数声明创建函数,并把调用约定改成C约定
Function* DecodeFunc = cast<Function>(DecodeFuncCallee.getCallee());
DecodeFunc->setCallingConv(CallingConv::C);

// 获取解密函数的参数
Function::arg_iterator Args = DecodeFunc->arg_begin();
Value* var0 = Args++;
Value* var1 = Args++;

// 在解密函数中创建基本块bb2
BasicBlock* bb2 = BasicBlock::Create(Ctx, "", DecodeFunc);

// 获取基本块bb2的IIRBuilder
IRBuilder<>* Builder2 = new IRBuilder<>(bb2);

// 创建比较指令,也就是参数一,字符串指针是否为NULL,把比较结果放入var3中
auto* var3 = Builder2->CreateICmpNE(var0, Constant::getNullValue(Type::getInt8PtrTy(Ctx, 8)), "var3");

// 创建有符号比较指令,字符串长度是否大于0,结果放入var4
auto* var4 = Builder2->CreateICmpSGT(var1, ConstantInt::get(IntegerType::get(Ctx, 32), 0));

// 创建and指令把var4和var3的与结果放入var5中
auto* var5 = Builder2->CreateAnd(var4, var3, "var5");

// 在解密函数中创建基本块bb6 和 bb14。
BasicBlock* bb6 = BasicBlock::Create(Ctx, "bb6", DecodeFunc);
BasicBlock* bb14 = BasicBlock::Create(Ctx, "bb14", DecodeFunc);

// 创建条件跳转指令,根据var5的结果跳转的bb6 或 bb14
Builder2->CreateCondBr(var5, bb6, bb14);

// 获取基本块bb6的IIRBuilder
IRBuilder<>* Builder6 = new IRBuilder<>(bb6);

// 创建两个phi节点用于跟踪循环变量。
PHINode* phi_var7 = Builder6->CreatePHI(Type::getInt8PtrTy(Ctx, 8), 2, "var7");
PHINode* phi_var8 = Builder6->CreatePHI(Type::getInt32Ty(Ctx), 2, "var8");

// 字符串长度减一, 字符串指针加一 控制循环
auto* var9 = Builder6->CreateNSWAdd(phi_var8, ConstantInt::getSigned(IntegerType::get(Ctx, 32), -1), "var9");
auto* var10 = Builder6->CreateGEP(IntegerType::get(Ctx, 8), phi_var7, ConstantInt::get(IntegerType::get(Ctx, 8), 0), "var10");

// 从phi节点字符串指针,
auto* var11 = Builder6->CreateLoad(phi_var7->getType()->getInt8PtrTy(Ctx), phi_var7, "var2");

auto* var12 = Builder6->CreateAdd(var11, ConstantInt::getSigned(IntegerType::get(Ctx,8), -1), "var12");
//auto* var12 = Builder6->CreateGEP(var11->getType()->getPointerElementType(), var11, ConstantInt::getSigned(IntegerType::get(Ctx, 32), -1), "var12");
Builder6->CreateStore(var12, phi_var7);

// 比较字符串长度是否大于0,结果保存在var13中
auto* var13 = Builder6->CreateICmpSGT(phi_var8, ConstantInt::get(IntegerType::get(Ctx, 32), 0), "var13");

// 创建条件跳转指令,根据var5的结果跳转的bb6 或 bb14,继续循环,或者结束循环
Builder6->CreateCondBr(var13, bb6, bb14);

// 获取基本块bb14的IIRBuilder, 并创建ret void指令
IRBuilder<>* Builder14 = new IRBuilder<>(bb14);
Builder14->CreateRetVoid();

//%7 = phi i8* [ %10, %6 ], [ %0, %2 ]
phi_var7->addIncoming(var0, bb2);
phi_var7->addIncoming(var10, bb6);
// %8 = phi i32 [ %9, %6 ], [ %1, %2 ]
phi_var8->addIncoming(var1, bb2);
phi_var8->addIncoming(var9, bb6);

return DecodeFunc;
}

创建完解密函数之后,我们就要为每个全局常量字符串进行解密了,下面创建为每个常量字符串解密的函数。

Function* createDecodeStubFunc(Module& M, vector<GlobalString*>& GlobalStrings, Function* DecodeFunc) {
auto& Ctx = M.getContext();

// 创建全体全局常量字符串解密函数,没有参数,调用约定为C约定。
FunctionCallee DecodeStubCallee = M.getOrInsertFunction("decode_stub",
/*ret*/ Type::getVoidTy(Ctx));
Function* DecodeStubFunc = cast<Function>(DecodeStubCallee.getCallee());
DecodeStubFunc->setCallingConv(CallingConv::C);

// 创建entry基本块
BasicBlock* BB = BasicBlock::Create(Ctx, "entry", DecodeStubFunc);
IRBuilder<> Builder(BB);

// 循环解密所有加密的字符串
for (GlobalString* GlobString : GlobalStrings) {
// 如果是直接是字符串那么直接进行解密,传入字符指针和长度
if (GlobString->type == GlobString->SIMPLE_STRING_TYPE) {
auto* StrPtr = Builder.CreatePointerCast(GlobString->Glob, Type::getInt8PtrTy(Ctx, 8));
llvm::Value* le = llvm::ConstantInt::get(
llvm::IntegerType::getInt32Ty(Ctx), GlobString->string_length, false);
Builder.CreateCall(DecodeFunc, { StrPtr, le });
}
// 若为结构体则先获取字符指针。
else if (GlobString->type == GlobString->STRUCT_STRING_TYPE) {
auto* String = Builder.CreateStructGEP(GlobString->Glob->getType(), GlobString->Glob, GlobString->index);
auto* StrPtr = Builder.CreatePointerCast(String, Type::getInt8PtrTy(Ctx, 8));
llvm::Value* le = llvm::ConstantInt::get(
llvm::IntegerType::getInt32Ty(Ctx), GlobString->string_length);
Builder.CreateCall(DecodeFunc, { StrPtr, le });
}
}
Builder.CreateRetVoid();

return DecodeStubFunc;
}

至此解密函数和解密动作已经创建完成,下面我们就要把解密动作插入main函数中,当main函数执行时进行解密。

void createDecodeStubBlock(Function* F, Function* DecodeStubFunc) {
auto& Ctx = F->getContext();
BasicBlock& EntryBlock = F->getEntryBlock();

BasicBlock* NewBB = BasicBlock::Create(Ctx, "DecodeStub", EntryBlock.getParent(), &EntryBlock);
IRBuilder<> Builder(NewBB);

Builder.CreateCall(DecodeStubFunc);
Builder.CreateBr(&EntryBlock);
}

下面就是在插件运行的之后调用这些函数了。

PreservedAnalyses run(Module& M, ModuleAnalysisManager& MAM) {
auto GlobalStrings = encodeGlobalStrings(M);

// Inject functions
Function* DecodeFunc = createDecodeFunc(M);
Function* DecodeStub = createDecodeStubFunc(M, GlobalStrings, DecodeFunc);

// Inject a call to DecodeStub from main
Function* MainFunc = M.getFunction("main");
createDecodeStubBlock(MainFunc, DecodeStub);
return PreservedAnalyses::all();
};

写完了这些代码就是对我们的pass插件进行测试了,

我们先查看以下llvm IR代码对不对。先写一个测试文件。

就是用test.cpp进行测试,生成代码如下,

可以看到我们已经成功在main函数中插入解密函数,并且进行了解密。

; ModuleID = 'test.bc'
source_filename = "test.cpp"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-w64-windows-gnu"

$_Z6printfPKcz = comdat any

@.str = private unnamed_addr global [8 x i8] c"&e;;;&e\01", align 1
@.str.1 = private unnamed_addr global [8 x i8] c"Ifmmp\222\01", align 1
@.str.2 = private unnamed_addr global [8 x i8] c"Ifmmp\223\01", align 1
@.str.3 = private unnamed_addr global [8 x i8] c"Ifmmp\224\01", align 1
@.str.4 = private unnamed_addr global [8 x i8] c"Ifmmp\225\01", align 1
@.str.5 = private unnamed_addr global [9 x i8] c"Ifmmp\2221\01", align 1
@.str.6 = private unnamed_addr global [12 x i8] c"ifmmp!Qbtt\22\01", align 1
@reltable.main = private unnamed_addr constant [5 x i32] [i32 trunc (i64 sub (i64 ptrtoint (ptr @.str.1 to i64), i64 ptrtoint (ptr @reltable.main to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @.str.4 to i64), i64 ptrtoint (ptr @reltable.main to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @.str.2 to i64), i64 ptrtoint (ptr @reltable.main to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @.str.4 to i64), i64 ptrtoint (ptr @reltable.main to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @.str.3 to i64), i64 ptrtoint (ptr @reltable.main to i64)) to i32)], align 4

; Function Attrs: mustprogress nofree nounwind uwtable
define dso_local void @_Z4fun1PKc(ptr nocapture noundef readonly %0) local_unnamed_addr #0 {
%2 = tail call i32 @puts(ptr noundef nonnull dereferenceable(1) %0)
ret void
}

; Function Attrs: nofree nounwind
declare dso_local noundef i32 @puts(ptr nocapture noundef readonly) local_unnamed_addr #1

; Function Attrs: mustprogress uwtable
define dso_local void @_Z4fun2ii(i32 noundef %0, i32 noundef %1) local_unnamed_addr #2 {
%3 = tail call noundef i32 (ptr, ...) @_Z6printfPKcz(ptr noundef @.str, i32 noundef %0, i32 noundef %1)
ret void
}

; Function Attrs: inlinehint mustprogress uwtable
define linkonce_odr dso_local noundef i32 @_Z6printfPKcz(ptr noundef nonnull %0, ...) local_unnamed_addr #3 comdat {
%2 = alloca ptr, align 8
call void @llvm.lifetime.start.p0(i64 8, ptr nonnull %2) #10
call void @llvm.va_start(ptr nonnull %2)
%3 = call ptr @__acrt_iob_func(i32 noundef 1)
%4 = load ptr, ptr %2, align 8, !tbaa !4
%5 = call i32 @__mingw_vfprintf(ptr noundef %3, ptr noundef nonnull %0, ptr noundef %4) #10
call void @llvm.va_end(ptr %2)
call void @llvm.lifetime.end.p0(i64 8, ptr nonnull %2) #10
ret i32 %5
}

; Function Attrs: mustprogress norecurse uwtable
define dso_local noundef i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr #4 {
DecodeStub:
call void @decode_stub()
br label %2

2: ; preds = %DecodeStub
%3 = add i32 %0, -5
%4 = icmp ult i32 %3, 5
br i1 %4, label %5, label %9

5: ; preds = %2
%6 = sext i32 %3 to i64
%7 = shl i64 %6, 2
%8 = call ptr @llvm.load.relative.i64(ptr @reltable.main, i64 %7)
br label %9

9: ; preds = %5, %2
%10 = phi ptr [ %8, %5 ], [ @.str.4, %2 ]
%11 = tail call i32 @puts(ptr noundef nonnull dereferenceable(1) %10)
%12 = icmp slt i32 %0, 5
br i1 %12, label %13, label %15

13: ; preds = %9
%14 = tail call i32 @puts(ptr noundef nonnull dereferenceable(1) @.str.5)
br label %15

15: ; preds = %13, %9
%16 = tail call i32 @puts(ptr noundef nonnull dereferenceable(1) @.str.6)
%17 = tail call noundef i32 (ptr, ...) @_Z6printfPKcz(ptr noundef @.str, i32 noundef 5, i32 noundef 6)
ret i32 0
}

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #5

; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_start(ptr) #6

; Function Attrs: nounwind
declare dso_local i32 @__mingw_vfprintf(ptr noundef, ptr noundef, ptr noundef) local_unnamed_addr #7

declare dllimport ptr @__acrt_iob_func(i32 noundef) local_unnamed_addr #8

; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_end(ptr) #6

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #5

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
declare ptr @llvm.load.relative.i64(ptr, i64) #9

define void @decode(ptr addrspace(8) %0, i32 %1) {
%var3 = icmp ne ptr addrspace(8) %0, null
%3 = icmp sgt i32 %1, 0
%var5 = and i1 %3, %var3
br i1 %var5, label %bb6, label %bb14

bb6: ; preds = %bb6, %2
%var7 = phi ptr addrspace(8) [ %0, %2 ], [ %var10, %bb6 ]
%var8 = phi i32 [ %1, %2 ], [ %var9, %bb6 ]
%var9 = add nsw i32 %var8, -1
%var10 = getelementptr i8, ptr addrspace(8) %var7, i8 1
%var2 = load i8, ptr addrspace(8) %var7, align 1
%var12 = add i8 %var2, -1
store i8 %var12, ptr addrspace(8) %var7, align 1
%var13 = icmp sgt i32 %var8, 0
br i1 %var13, label %bb6, label %bb14

bb14: ; preds = %bb6, %2
ret void
}

define void @decode_stub() {
entry:
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str to ptr addrspace(8)), i32 7)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.1 to ptr addrspace(8)), i32 7)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.2 to ptr addrspace(8)), i32 7)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.3 to ptr addrspace(8)), i32 7)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.4 to ptr addrspace(8)), i32 7)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.5 to ptr addrspace(8)), i32 8)
call void @decode(ptr addrspace(8) addrspacecast (ptr @.str.6 to ptr addrspace(8)), i32 11)
ret void
}

attributes #0 = { mustprogress nofree nounwind uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nofree nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #2 = { mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #3 = { inlinehint mustprogress uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #4 = { mustprogress norecurse uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #5 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #6 = { nocallback nofree nosync nounwind willreturn }
attributes #7 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #8 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #9 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
attributes #10 = { nounwind }

!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}

!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{!"clang version 16.0.4"}
!4 = !{!5, !5, i64 0}
!5 = !{!"any pointer", !6, i64 0}
!6 = !{!"omnipotent char", !7, i64 0}
!7 = !{!"Simple C++ TBAA"}

直接使用clang加载插件,成功插入解密函数。

使用vs加载插件。

参考:

https://bbs.kanxue.com/thread-272346.htm#msg_header_h3_10
http://www.qfrost.com/posts/llvm/llvmwindows%E4%B8%8B%E4%BC%98%E9%9B%85%E4%BD%BF%E7%94%A8llvmpass/
https://llvm.org/docs/NewPassManager.html
https://kiprey.github.io/2020/06/LLVM-IR-pass/
https://github.com/tsarpaul/llvm-string-obfuscator/blob/master/StringObfuscator
https://github.com/banach-space/llvm-tutor/

看雪ID:l140w4n9

https://bbs.kanxue.com/user-home-973137.htm

*本文为看雪论坛优秀文章,由 l140w4n9 原创,转载请注明来自看雪社区

# 往期推荐

1、2023 SDC 议题回顾 | 芯片安全和无线电安全底层渗透技术

2、SWPUCTF 2021 新生赛-老鼠走迷宫

3、OWASP 实战分析 level 1

4、【远控木马】银狐组织最新木马样本-分析

5、自研Unidbg trace工具实战ollvm反混淆

6、2023 SDC 议题回顾 | 深入 Android 可信应用漏洞挖掘

球分享

球点赞

球在看


文章来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458531566&idx=1&sn=e287d158de4772a3f95f6f09d0e0ab47&chksm=b18d066486fa8f726044a845b1615c7aadd16c58164bceede98f9af8b7945fb02532c82e6881&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh