使用 LLVM Pass 实现字符串加密
2020-01-01 #re #llvm这个项目可以作为我看了这么久 LLVM 的 Docs 助眠的一个小总结吧。这个项目主要就是给函数使用的常量字符串进行加密,在程序被静态分析的时候干扰分析。当然找到思路后,这个混淆还是很容易解开的。
吐槽下,网上的文章质量参差不齐,写的让人不知所云的,真的恐怖。所以还是面向 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" 就有智能提示了。
由于使用 CMake 来管理编译依赖,所以给 VSCode 加上 CMake 插件,可以实现小部分 CMake GUI 的功能。
代码讲解
基本框架
首先是 include 的头文件,项目里用了 LLVM 自己的 SmallVector
,还有 IR 下面的 Function
, GlobalVariable
, IRBuilder
, Instructions
,还有些 Pass 必备的一些头文件,raw_ostream
用来调试输出。
然后是 LLVM Pass 的基本框架。这里实现的是 Module Pass,所以这个 Pass 继承于 ModulePass
。最后两行代码是用于注册 Pass,注册之后在 opt -load libpass.so -help
就能找到这个 Pass 了。
加密函数只用了简单的 xor 来进行加密。需要注意的是不要把字符串的最后一位 '\0' 给 xor 了。
using namespace llvm;
// namespace
char ObfuscatePass::ID = 0;
static RegisterPass<ObfuscatePass> ;
runOnModule
函数
然后开始讲解具体的代码。下面的代码是在寻找全局变量,并且找到他的使用者。
首先通过 GlobalVariable GVar->users()
来找到使用者。如果使用者不是单独的指令,而是类似 i8* getelementptr ...
这样的语句,则寻找这个语句的使用者。如果发现这个语句只有 CallInst
类型的使用者,则对字符串进行加密,并去除对应字符串的常量属性。
virtual bool
obfuscateString
函数
函数中主要分为下面三个部分
- 替换字符串为加密字符串
- 创建函数声明
- 创建调用指令
下面是加密字符串常量的语句,这里先将常量转换成 ConstantDataArray
,用 getAsString()
提取字符串,最后用加密后的字符串创建 Constant
替换原来字符串。
// Encrypt origin string and replace it encrypted string.
ConstantDataArray *GVarArr =
dyn_cast<ConstantDataArray>;
if
std::string Origin;
if else if
;
Constant *NewConstStr = ;
GVarArr->;
然后是使用 IRBuilder
创建相关语句和函数调用。首先通过 getOrInsertFunction
插入或获取函数,然后使用 builder.CreateCall();
语句创建调用解密和加密函数的指令。
由于调用顺序是 解密字符串函数 -> 使用字符串函数 -> 加密字符串函数,加密函数得插入使用指令的后方,而不是和 IRBuilder
创建的指令一样待在使用指令的前方。所以这里不使用 IRBuilder
创建指令,而是手动创建指令 + 手动插入。
// Insert decrypt function above Inst with IRBuilder.
IRBuilder<> ;
Type *Int8PtrTy = builder.;
// Create decrypt function in GlobalValue / Get decrypt function.
SmallVector<Type *, 1> FuncArgs = ;
FunctionType *FuncType = ;
Constant *DecryptFunc = M.;
Constant *EncryptFunc = M.;
// Create call instrucions.
SmallVector<Value *, 1> CallArgs = ;
CallInst *DecryptInst = builder.;
CallInst *EncryptInst = ;
EncryptInst->;
__encrypt
和 __decrypt
函数
由于只是 xor,所以函数的代码很相似。
char *
char *
Pass 的简单使用
首先将需要加密的文件和含有加密函数的文件编译成后缀为 .ll
的 LLVM IR 形式,然后使用 opt
来对需要加密的文件进行加密,最后则是将含有加密函数的文件和需要加密的文件进行链接,生成二进制文件。
将下面的脚本保存为 "run.sh" 后,就可以使用 bash run.sh test.c
来编译程序了。
fullname=
basename=
混淆效果
下面是测试程序的代码。
int
下面是使用 clang-8 -emit-llvm -S test.c -o test.ll
直接编译出的 IR。
@.str = private unnamed_addr constant [26 x i8] c"This is a testing string!\00", align 1
@.str.1 = private unnamed_addr constant [8 x i8] c"6666%c\0A\00", align 1
@.str.2 = private unnamed_addr constant [7 x i8] c"WTF?!\0A\00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i8, align 1
store i32 0, i32* %1, align 4
%3 = call i32 @puts(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str, i32 0, i32 0))
%4 = call i32 @getchar()
%5 = trunc i32 %4 to i8
store i8 %5, i8* %2, align 1
%6 = sext i8 %5 to i32
%7 = icmp eq i32 %6, 54
br i1 %7, label %8, label %12
; <label>:8: ; preds = %0
%9 = load i8, i8* %2, align 1
%10 = sext i8 %9 to i32
%11 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i32 0, i32 0), i32 %10)
br label %14
; <label>:12: ; preds = %0
%13 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.2, i32 0, i32 0))
br label %14
; <label>:14: ; preds = %12, %8
ret i32 0
}
下面是使用 opt-8 -p -load ../build/obfuscate/libobfuscate.so -obfstr test.ll -o test.bc
调用 Pass 得到的混淆过的 IR,可以发现字符串已经被混淆成功了。
@.str = private unnamed_addr global [26 x i8] c"~BCY\0ACY\0AK\0A^OY^CDM\0AY^XCDM\0B\00", align 1
@.str.1 = private unnamed_addr global [8 x i8] c"\1C\1C\1C\1C\0FI \00", align 1
@.str.2 = private unnamed_addr global [7 x i8] c"}~l\15\0B \00", align 1
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i8, align 1
store i32 0, i32* %1, align 4
%3 = call i8* @__decrypt(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str, i32 0, i32 0))
%4 = call i32 @puts(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str, i32 0, i32 0))
%5 = call i8* @__encrypt(i8* getelementptr inbounds ([26 x i8], [26 x i8]* @.str, i32 0, i32 0))
%6 = call i32 @getchar()
%7 = trunc i32 %6 to i8
store i8 %7, i8* %2, align 1
%8 = sext i8 %7 to i32
%9 = icmp eq i32 %8, 54
br i1 %9, label %10, label %16
; <label>:10: ; preds = %0
%11 = load i8, i8* %2, align 1
%12 = sext i8 %11 to i32
%13 = call i8* @__decrypt(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i32 0, i32 0))
%14 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i32 0, i32 0), i32 %12)
%15 = call i8* @__encrypt(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.1, i32 0, i32 0))
br label %20
; <label>:16: ; preds = %0
%17 = call i8* @__decrypt(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.2, i32 0, i32 0))
%18 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.2, i32 0, i32 0))
%19 = call i8* @__encrypt(i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.str.2, i32 0, i32 0))
br label %20
; <label>:20: ; preds = %16, %10
ret i32 0
}
在 IDA 中也可以看到混淆的效果。
完整代码
obfuscate.cpp
//===-- obfuscate/obfuscate.cpp - main file of obfuscate_string -*- C++ -*-===//
//
// The main file of the obfusate_string pass.
// LLVM project is under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the module pass class and decrypt function.
///
//===----------------------------------------------------------------------===//
using namespace llvm;
// namespace
char ObfuscatePass::ID = 0;
static RegisterPass<ObfuscatePass> ;
encrypt.c
char *
char *