这个项目可以作为我看了这么久 LLVM 的 Docs 的一个小总结吧。这个项目主要就是给函数使用的常量字符串进行加密,在程序被静态分析的时候干扰分析。当然找到思路后,这个混淆还是很容易解开的。
代码已上传到 Github chenx6/baby_obfuscator
吐槽下,网上的文章质量参差不齐,写的让人不知所云的,真的恐怖。所以还是面向 Stackoverflow 和官方文档编程吧...
为了写 LLVM Pass ,首先得看看 LLVM Programmers' Manual,里面讲了许多代码样例,API 讲解和类的层次结构。当然这只是基础,具体的使用得看使用 doxygen 生成的文档。
当然也得对 LLVM IR 也得有一定了解 https://llvm.org/docs/LangRef.html
Pass 思路概述
首先是找到字符串,其次是找到用这个字符串的函数,在这个函数被调用前,先调用解密函数进行解密。最后加密原本字符串。
开发环境准备
编译环境请参考上一篇文章,只是写个 Pass 而已,不要再从整个 LLVM 项目开始编译了好吗...
这里使用的是 VSCode + WSL 搭建开发环境,在项目文件夹的 ".vscode/c_cpp_properties.json" 加上对应的 "includePath" 就有智能提示了。
{
"includePath": [
"${workspaceFolder}/**",
"/usr/include/llvm-8",
"/usr/include/llvm-c-8"
]
}
由于使用 CMake 来管理编译依赖,所以给 VSCode 加上 CMake 插件,可以实现小部分 CMake GUI 的功能。
代码讲解
基本框架
首先是 include 的头文件,项目里用了 LLVM 自己的 SmallVector
,还有 IR 下面的 Function
, GlobalVariable
, IRBuilder
, Instructions
,还有些 Pass 必备的一些头文件,raw_ostream
用来调试输出。
#include "llvm/ADT/SmallVector.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include <map>
#include <vector>
然后是 LLVM Pass 的基本框架。这里实现的是 Module Pass,所以这个 Pass 继承于 ModulePass
。最后两行代码是用于注册 Pass,注册之后在 opt -load libpass.so -help
就能找到这个 Pass 了。
加密函数只用了简单的 xor 来进行加密。需要注意的是不要把字符串的最后一位 '0' 给 xor 了。
using namespace llvm;
namespace {
/// encrypt strings with xor.
/// param s input string for encrypt.
void encrypt(std::string &s) {
for (int i = 0; i < s.length() - 1; i++) {
s[i] ^= 42;
}
}
/// A pass for obfuscating const string in modules.
struct ObfuscatePass : public ModulePass {
static char ID;
ObfuscatePass() : ModulePass(ID) {}
virtual bool runOnModule(Module &M) {
return true;
}
};
} // namespace
char ObfuscatePass::ID = 0;
static RegisterPass<ObfuscatePass> X("obfstr", "obfuscate string");
runOnModule
函数
然后开始讲解具体的代码。下面的代码是在寻找全局变量,并且找到他的使用者。
首先通过 GlobalVariable GVar->users()
来找到使用者。如果使用者不是单独的指令,而是类似 i8* getelementptr ...
这样的语句,则寻找这个语句的使用者。如果发现这个语句只有 CallInst
类型的使用者,且只有一个函数使用了这个常量,则对字符串进行加密,并去除对应字符串的常量属性。
virtual bool runOnModule(Module &M) {
for (GlobalValue &GV : M.globals()) {
GlobalVariable *GVar = d