版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/
前言
当我们如果没有对字符串进行加密,使用 IDA 反汇编一下 so 可以看到 C++ 代码中的字符串就直接暴露了。
字符串加密原理
sobf.c
#include <stdio.h>int main() {// 定义字符串常量const char *greeting = "Hello, World!";const char *name = "Cyrus";const char *message = "Welcome to C programming.";// 打印字符串常量printf("%s\n", greeting);printf("My name is %s.\n", name);printf("%s\n", message);return 0;
}
生成 ir 文件
clang -S -emit-llvm sobf.c -o sobf.ll
通过生成 ir 文件可以看到字符串的定义都是 @“??C@ ,引用的的地方通过 @”??C@ 使用
; ModuleID = 'sobf.c'
source_filename = "sobf.c"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.42.34433"$sprintf = comdat any$vsprintf = comdat any$_snprintf = comdat any$_vsnprintf = comdat any$printf = comdat any$_vsprintf_l = comdat any$_vsnprintf_l = comdat any$__local_stdio_printf_options = comdat any$_vfprintf_l = comdat any$"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = comdat any$"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = comdat any$"??_C@_05KECNEMLJ@Cyrus?$AA@" = comdat any$"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = comdat any@"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = linkonce_odr dso_local unnamed_addr constant [14 x i8] c"Hello, World!\00", comdat, align 1
@"??_C@_03OFAPEBGM@?$CFs?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [4 x i8] c"%s\0A\00", comdat, align 1
@"??_C@_05KECNEMLJ@Cyrus?$AA@" = linkonce_odr dso_local unnamed_addr constant [6 x i8] c"Cyrus\00", comdat, align 1
@"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [16 x i8] c"My name is %s.\0A\00", comdat, align 1
@__local_stdio_printf_options._OptionsStorage = internal global i64 0, align 8; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @sprintf(ptr noundef %0, ptr noundef %1, ...) #0 comdat {%3 = alloca ptr, align 8%4 = alloca ptr, align 8%5 = alloca i32, align 4%6 = alloca ptr, align 8store ptr %1, ptr %3, align 8store ptr %0, ptr %4, align 8call void @llvm.va_start(ptr %6)%7 = load ptr, ptr %6, align 8%8 = load ptr, ptr %3, align 8%9 = load ptr, ptr %4, align 8%10 = call i32 @_vsprintf_l(ptr noundef %9, ptr noundef %8, ptr noundef null, ptr noundef %7)store i32 %10, ptr %5, align 4call void @llvm.va_end(ptr %6)%11 = load i32, ptr %5, align 4ret i32 %11
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @vsprintf(ptr noundef %0, ptr noundef %1, ptr noundef %2) #0 comdat {%4 = alloca ptr, align 8%5 = alloca ptr, align 8%6 = alloca ptr, align 8store ptr %2, ptr %4, align 8store ptr %1, ptr %5, align 8store ptr %0, ptr %6, align 8%7 = load ptr, ptr %4, align 8%8 = load ptr, ptr %5, align 8%9 = load ptr, ptr %6, align 8%10 = call i32 @_vsnprintf_l(ptr noundef %9, i64 noundef -1, ptr noundef %8, ptr noundef null, ptr noundef %7)ret i32 %10
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_snprintf(ptr noundef %0, i64 noundef %1, ptr noundef %2, ...) #0 comdat {%4 = alloca ptr, align 8%5 = alloca i64, align 8%6 = alloca ptr, align 8%7 = alloca i32, align 4%8 = alloca ptr, align 8store ptr %2, ptr %4, align 8store i64 %1, ptr %5, align 8store ptr %0, ptr %6, align 8call void @llvm.va_start(ptr %8)%9 = load ptr, ptr %8, align 8%10 = load ptr, ptr %4, align 8%11 = load i64, ptr %5, align 8%12 = load ptr, ptr %6, align 8%13 = call i32 @_vsnprintf(ptr noundef %12, i64 noundef %11, ptr noundef %10, ptr noundef %9)store i32 %13, ptr %7, align 4call void @llvm.va_end(ptr %8)%14 = load i32, ptr %7, align 4ret i32 %14
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsnprintf(ptr noundef %0, i64 noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca i64, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store i64 %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load i64, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call i32 @_vsnprintf_l(ptr noundef %12, i64 noundef %11, ptr noundef %10, ptr noundef null, ptr noundef %9)ret i32 %13
}; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {%1 = alloca i32, align 4%2 = alloca ptr, align 8store i32 0, ptr %1, align 4store ptr @"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@", ptr %2, align 8%3 = load ptr, ptr %2, align 8%4 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_03OFAPEBGM@?$CFs?6?$AA@", ptr noundef %3)%5 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@", ptr noundef @"??_C@_05KECNEMLJ@Cyrus?$AA@")ret i32 0
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @printf(ptr noundef %0, ...) #0 comdat {%2 = alloca ptr, align 8%3 = alloca i32, align 4%4 = alloca ptr, align 8store ptr %0, ptr %2, align 8call void @llvm.va_start(ptr %4)%5 = load ptr, ptr %4, align 8%6 = load ptr, ptr %2, align 8%7 = call ptr @__acrt_iob_func(i32 noundef 1)%8 = call i32 @_vfprintf_l(ptr noundef %7, ptr noundef %6, ptr noundef null, ptr noundef %5)store i32 %8, ptr %3, align 4call void @llvm.va_end(ptr %4)%9 = load i32, ptr %3, align 4ret i32 %9
}; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_start(ptr) #1; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsprintf_l(ptr noundef %0, ptr noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store ptr %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load ptr, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call i32 @_vsnprintf_l(ptr noundef %12, i64 noundef -1, ptr noundef %11, ptr noundef %10, ptr noundef %9)ret i32 %13
}; Function Attrs: nocallback nofree nosync nounwind willreturn
declare void @llvm.va_end(ptr) #1; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vsnprintf_l(ptr noundef %0, i64 noundef %1, ptr noundef %2, ptr noundef %3, ptr noundef %4) #0 comdat {%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8%9 = alloca i64, align 8%10 = alloca ptr, align 8%11 = alloca i32, align 4store ptr %4, ptr %6, align 8store ptr %3, ptr %7, align 8store ptr %2, ptr %8, align 8store i64 %1, ptr %9, align 8store ptr %0, ptr %10, align 8%12 = load ptr, ptr %6, align 8%13 = load ptr, ptr %7, align 8%14 = load ptr, ptr %8, align 8%15 = load i64, ptr %9, align 8%16 = load ptr, ptr %10, align 8%17 = call ptr @__local_stdio_printf_options()%18 = load i64, ptr %17, align 8%19 = or i64 %18, 1%20 = call i32 @__stdio_common_vsprintf(i64 noundef %19, ptr noundef %16, i64 noundef %15, ptr noundef %14, ptr noundef %13, ptr noundef %12)store i32 %20, ptr %11, align 4%21 = load i32, ptr %11, align 4%22 = icmp slt i32 %21, 0br i1 %22, label %23, label %2423: ; preds = %5br label %2624: ; preds = %5%25 = load i32, ptr %11, align 4br label %2626: ; preds = %24, %23%27 = phi i32 [ -1, %23 ], [ %25, %24 ]ret i32 %27
}declare dso_local i32 @__stdio_common_vsprintf(i64 noundef, ptr noundef, i64 noundef, ptr noundef, ptr noundef, ptr noundef) #2; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local ptr @__local_stdio_printf_options() #0 comdat {ret ptr @__local_stdio_printf_options._OptionsStorage
}; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_vfprintf_l(ptr noundef %0, ptr noundef %1, ptr noundef %2, ptr noundef %3) #0 comdat {%5 = alloca ptr, align 8%6 = alloca ptr, align 8%7 = alloca ptr, align 8%8 = alloca ptr, align 8store ptr %3, ptr %5, align 8store ptr %2, ptr %6, align 8store ptr %1, ptr %7, align 8store ptr %0, ptr %8, align 8%9 = load ptr, ptr %5, align 8%10 = load ptr, ptr %6, align 8%11 = load ptr, ptr %7, align 8%12 = load ptr, ptr %8, align 8%13 = call ptr @__local_stdio_printf_options()%14 = load i64, ptr %13, align 8%15 = call i32 @__stdio_common_vfprintf(i64 noundef %14, ptr noundef %12, ptr noundef %11, ptr noundef %10, ptr noundef %9)ret i32 %15
}declare dso_local ptr @__acrt_iob_func(i32 noundef) #2declare dso_local i32 @__stdio_common_vfprintf(i64 noundef, ptr noundef, ptr noundef, ptr noundef, ptr noundef) #2attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { nocallback nofree nosync nounwind willreturn }
attributes #2 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}!0 = !{i32 1, !"wchar_size", i32 2}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 1, !"MaxTLSAlign", i32 65536}
!4 = !{!"clang version 18.1.8 (https://github.com/CYRUS-STUDIO/LLVM.git fb76c7a7578309cbd36b56fa8b17b4c6538d41ef)"}
- ??_C@:表示一个字符串常量,后面是哈希值和字符串名称的编码。
只要在定义的地方把字符串加密,引用时候再解密就可以实现字符串的自动加解密了。
字符串常量
@"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@" = linkonce_odr dso_local unnamed_addr constant [14 x i8] c"Hello, World!\00", comdat, align 1
@"??_C@_05KECNEMLJ@Cyrus?$AA@" = linkonce_odr dso_local unnamed_addr constant [6 x i8] c"Cyrus\00", comdat, align 1
@"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@" = linkonce_odr dso_local unnamed_addr constant [16 x i8] c"My name is %s.\0A\00", comdat, align 1
-
“Hello, World!”
-
“Cyrus”
-
“My name is %s.”
main()函数
define dso_local i32 @main() #0 {store ptr @"??_C@_0O@KLMCIIGF@Hello?0?5World?$CB?$AA@", ptr %2, align 8%3 = load ptr, ptr %2, align 8%4 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_03OFAPEBGM@?$CFs?6?$AA@", ptr noundef %3)%5 = call i32 (ptr, ...) @printf(ptr noundef @"??_C@_0BA@ENLIINEF@My?5name?5is?5?$CFs?4?6?$AA@", ptr noundef @"??_C@_05KECNEMLJ@Cyrus?$AA@")ret i32 0
}
-
读取 “Hello, World!” 并通过 printf 输出。
-
使用 printf 打印 “My name is Cyrus.”,通过 %s 占位符传入 “Cyrus”。
实现字符串加密
1. StringEncryption.h
#ifndef LLVM_STRING_ENCRYPTION_H
#define LLVM_STRING_ENCRYPTION_H
// LLVM libs
#include "llvm/Transforms/Utils/GlobalStatus.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
//#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/SHA1.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"// User libs
#include "CryptoUtils.h"
#include "Utils.h"
// System libs
#include <map>
#include <set>
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <vector>#include "ObfuscationOptions.h"using namespace std;
namespace llvm {
struct EncryptedGV {GlobalVariable *GV;uint64_t key;uint32_t len;
};class StringEncryptionPass : public PassInfoMixin<StringEncryptionPass> {
public:bool flag;struct CSPEntry {CSPEntry(): ID(0), Offset(0), DecGV(nullptr), DecStatus(nullptr),DecFunc(nullptr) {}unsigned ID;unsigned Offset;GlobalVariable *DecGV;GlobalVariable *DecStatus; // is decrypted or notstd::vector<uint8_t> Data;std::vector<uint8_t> EncKey;Function *DecFunc;};struct CSUser {CSUser(Type *ETy, GlobalVariable *User, GlobalVariable *NewGV): Ty(ETy), GV(User), DecGV(NewGV), DecStatus(nullptr),InitFunc(nullptr) {}Type *Ty;GlobalVariable *GV;GlobalVariable *DecGV;GlobalVariable *DecStatus; // is decrypted or notFunction *InitFunc; // InitFunc will use decryted string to// initialize DecGV};ObfuscationOptions *Options;CryptoUtils RandomEngine;std::vector<CSPEntry *> ConstantStringPool;std::map<GlobalVariable *, CSPEntry *> CSPEntryMap;std::map<GlobalVariable *, CSUser *> CSUserMap;GlobalVariable *EncryptedStringTable;std::set<GlobalVariable *> MaybeDeadGlobalVars;map<Function * /*Function*/, GlobalVariable * /*Decryption Status*/>encstatus;StringEncryptionPass(bool flag) {this->flag = flag;Options = new ObfuscationOptions;//EncryptedStringTable = new GlobalVariable;}PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); // Pass实现函数bool do_StrEnc(Module &M, ModuleAnalysisManager &AM);void collectConstantStringUser(GlobalVariable *CString,std::set<GlobalVariable *> &Users);bool isValidToEncrypt(GlobalVariable *GV);bool processConstantStringUse(Function *F);void deleteUnusedGlobalVariable();Function *buildDecryptFunction(Module *M, const CSPEntry *Entry);Function *buildInitFunction(Module *M, const CSUser *User);void getRandomBytes(std::vector<uint8_t> &Bytes, uint32_t MinSize,uint32_t MaxSize);void lowerGlobalConstant(Constant *CV, IRBuilder<> &IRB, Value *Ptr,Type *Ty);void lowerGlobalConstantStruct(ConstantStruct *CS, IRBuilder<> &IRB,Value *Ptr, Type *Ty);void lowerGlobalConstantArray(ConstantArray *CA, IRBuilder<> &IRB, Value *Ptr,Type *Ty);static bool isRequired() { return true; } // 直接返回true即可
};
StringEncryptionPass *createStringEncryption(bool flag); // 创建字符串加密
}
#endif
2. StringEncryption.cpp
2.1 收集字符串常量
在 run 方法迭代每一条指令,收集字符串常量
for (GlobalVariable &GV : M.globals()) {if (!GV.isConstant() || !GV.hasInitializer()) {continue;}if (ConstantDataSequential *CDS = dyn_cast<ConstantDataSequential>(GV.getInitializer())) {if (CDS->isCString()) {CSPEntry *Entry = new CSPEntry();StringRef Data = CDS->getRawDataValues();for (unsigned i = 0; i < Data.size(); ++i) {Entry->Data.push_back(static_cast<uint8_t>(Data[i]));}Entry->ID = static_cast<unsigned>(ConstantStringPool.size());}}
}
-
遍历 Module 内的 GlobalVariable。
-
只处理常量字符串 ConstantDataSequential。
-
存入 ConstantStringPool(字符串池)。
2.2 加密字符串 和 构造解密函数
// encrypt those strings, build corresponding decrypt function
for (CSPEntry *Entry : ConstantStringPool) {getRandomBytes(Entry->EncKey, 16, 32);for (unsigned i = 0; i < Entry->Data.size(); ++i) {Entry->Data[i] ^= Entry->EncKey[i % Entry->EncKey.size()];}Entry->DecFunc = buildDecryptFunction(&M, Entry);
}
-
getRandomBytes() 生成 16-32 字节的随机密钥。
-
使用 XOR 操作加密字符串。
2.3 创建一个初始化函数
为字符串变量创建一个初始化函数,用于在运行时执行解密操作。
// build initialization function for supported constant string users
for (GlobalVariable *GV : ConstantStringUsers) {if (isValidToEncrypt(GV)) {Type *EltType = GV->getValueType();ConstantAggregateZero *ZeroInit = ConstantAggregateZero::get(EltType);GlobalVariable *DecGV =new GlobalVariable(M, EltType, false, GlobalValue::PrivateLinkage,ZeroInit, "dec_" + GV->getName());DecGV->setAlignment(MaybeAlign(GV->getAlignment()));GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + GV->getName());CSUser *User = new CSUser(EltType, GV, DecGV);User->DecStatus = DecStatus;User->InitFunc = buildInitFunction(&M, User);CSUserMap[GV] = User;}
}
2.4 修改使用字符串的地方
processConstantStringUse() 找到所有使用加密字符串的地方,替换为解密后的变量。
// decrypt string back at every use, change the plain string use to the
// decrypted one
bool Changed = false;
for (Function &F : M) {if (F.isDeclaration())continue;Changed |= processConstantStringUse(&F);
}for (auto &I : CSUserMap) {CSUser *User = I.second;Changed |= processConstantStringUse(User->InitFunc);
}
完整代码
#include "StringEncryption.h"#define DEBUG_TYPE "strenc"using namespace llvm;bool StringEncryptionPass::do_StrEnc(Module &M, ModuleAnalysisManager &AM) {std::set<GlobalVariable *> ConstantStringUsers;// collect all c stringsLLVMContext &Ctx = M.getContext();ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);for (GlobalVariable &GV : M.globals()) {if (!GV.isConstant() || !GV.hasInitializer() ||GV.hasDLLExportStorageClass() || GV.isDLLImportDependent()) {continue;}Constant *Init = GV.getInitializer();if (Init == nullptr)continue;if (ConstantDataSequential *CDS = dyn_cast<ConstantDataSequential>(Init)) {if (CDS->isCString()) {CSPEntry *Entry = new CSPEntry();StringRef Data = CDS->getRawDataValues();Entry->Data.reserve(Data.size());for (unsigned i = 0; i < Data.size(); ++i) {Entry->Data.push_back(static_cast<uint8_t>(Data[i]));}Entry->ID = static_cast<unsigned>(ConstantStringPool.size());ConstantAggregateZero *ZeroInit =ConstantAggregateZero::get(CDS->getType());GlobalVariable *DecGV = new GlobalVariable(M, CDS->getType(), false, GlobalValue::PrivateLinkage, ZeroInit,"dec" + Twine::utohexstr(Entry->ID) + GV.getName());GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + Twine::utohexstr(Entry->ID) + GV.getName());DecGV->setAlignment(MaybeAlign(GV.getAlignment()));Entry->DecGV = DecGV;Entry->DecStatus = DecStatus;ConstantStringPool.push_back(Entry);CSPEntryMap[&GV] = Entry;collectConstantStringUser(&GV, ConstantStringUsers);}}}// encrypt those strings, build corresponding decrypt functionfor (CSPEntry *Entry : ConstantStringPool) {getRandomBytes(Entry->EncKey, 16, 32);for (unsigned i = 0; i < Entry->Data.size(); ++i) {Entry->Data[i] ^= Entry->EncKey[i % Entry->EncKey.size()];}Entry->DecFunc = buildDecryptFunction(&M, Entry);}// build initialization function for supported constant string usersfor (GlobalVariable *GV : ConstantStringUsers) {if (isValidToEncrypt(GV)) {Type *EltType = GV->getValueType();ConstantAggregateZero *ZeroInit = ConstantAggregateZero::get(EltType);GlobalVariable *DecGV =new GlobalVariable(M, EltType, false, GlobalValue::PrivateLinkage,ZeroInit, "dec_" + GV->getName());DecGV->setAlignment(MaybeAlign(GV->getAlignment()));GlobalVariable *DecStatus = new GlobalVariable(M, Type::getInt32Ty(Ctx), false, GlobalValue::PrivateLinkage, Zero,"dec_status_" + GV->getName());CSUser *User = new CSUser(EltType, GV, DecGV);User->DecStatus = DecStatus;User->InitFunc = buildInitFunction(&M, User);CSUserMap[GV] = User;}}// emit the constant string pool// | junk bytes | key 1 | encrypted string 1 | junk bytes | key 2 | encrypted// string 2 | ...std::vector<uint8_t> Data;std::vector<uint8_t> JunkBytes;JunkBytes.reserve(32);for (CSPEntry *Entry : ConstantStringPool) {JunkBytes.clear();getRandomBytes(JunkBytes, 16, 32);Data.insert(Data.end(), JunkBytes.begin(), JunkBytes.end());Entry->Offset = static_cast<unsigned>(Data.size());Data.insert(Data.end(), Entry->EncKey.begin(), Entry->EncKey.end());Data.insert(Data.end(), Entry->Data.begin(), Entry->Data.end());}Constant *CDA =ConstantDataArray::get(M.getContext(), ArrayRef<uint8_t>(Data));EncryptedStringTable =new GlobalVariable(M, CDA->getType(), true, GlobalValue::PrivateLinkage,CDA, "EncryptedStringTable");// decrypt string back at every use, change the plain string use to the// decrypted onebool Changed = false;for (Function &F : M) {if (F.isDeclaration())continue;Changed |= processConstantStringUse(&F);}for (auto &I : CSUserMap) {CSUser *User = I.second;Changed |= processConstantStringUse(User->InitFunc);}// delete unused global variablesdeleteUnusedGlobalVariable();for (CSPEntry *Entry : ConstantStringPool) {if (Entry->DecFunc->use_empty()) {Entry->DecFunc->eraseFromParent();}}return Changed;
}PreservedAnalyses StringEncryptionPass::run(Module &M, ModuleAnalysisManager &AM) {if (this->flag) {outs() << "[Soule] force.run.StringEncryptionPass\n";if (do_StrEnc(M, AM))return PreservedAnalyses::none();}return PreservedAnalyses::all();
}void StringEncryptionPass::getRandomBytes(std::vector<uint8_t> &Bytes,uint32_t MinSize, uint32_t MaxSize) {uint32_t N = RandomEngine.get_uint32_t();uint32_t Len;assert(MaxSize >= MinSize);if (MinSize == MaxSize) {Len = MinSize;} else {Len = MinSize + (N % (MaxSize - MinSize));}char *Buffer = new char[Len];RandomEngine.get_bytes(Buffer, Len);for (uint32_t i = 0; i < Len; ++i) {Bytes.push_back(static_cast<uint8_t>(Buffer[i]));}delete[] Buffer;
}//
// static void goron_decrypt_string(uint8_t *plain_string, const uint8_t *data)
//{
// const uint8_t *key = data;
// uint32_t key_size = 1234;
// uint8_t *es = (uint8_t *) &data[key_size];
// uint32_t i;
// for (i = 0;i < 5678;i ++) {
// plain_string[i] = es[i] ^ key[i % key_size];
// }
//}Function *StringEncryptionPass::buildDecryptFunction(Module *M, const StringEncryptionPass::CSPEntry *Entry) {LLVMContext &Ctx = M->getContext();IRBuilder<> IRB(Ctx);FunctionType *FuncTy = FunctionType::get(Type::getVoidTy(Ctx), {IRB.getPtrTy(), IRB.getPtrTy()}, false);Function *DecFunc = Function::Create(FuncTy, GlobalValue::PrivateLinkage,"goron_decrypt_string_" + Twine::utohexstr(Entry->ID), M);auto ArgIt = DecFunc->arg_begin();Argument *PlainString = ArgIt; // output++ArgIt;Argument *Data = ArgIt; // inputPlainString->setName("plain_string");PlainString->addAttr(Attribute::NoCapture);Data->setName("data");Data->addAttr(Attribute::NoCapture);Data->addAttr(Attribute::ReadOnly);BasicBlock *Enter = BasicBlock::Create(Ctx, "Enter", DecFunc);BasicBlock *LoopBody = BasicBlock::Create(Ctx, "LoopBody", DecFunc);BasicBlock *UpdateDecStatus =BasicBlock::Create(Ctx, "UpdateDecStatus", DecFunc);BasicBlock *Exit = BasicBlock::Create(Ctx, "Exit", DecFunc);IRB.SetInsertPoint(Enter);ConstantInt *KeySize =ConstantInt::get(Type::getInt32Ty(Ctx), Entry->EncKey.size());Value *EncPtr = IRB.CreateInBoundsGEP(IRB.getInt8Ty(), Data, KeySize);Value *DecStatus =IRB.CreateLoad(Entry->DecStatus->getValueType(), Entry->DecStatus);Value *IsDecrypted = IRB.CreateICmpEQ(DecStatus, IRB.getInt32(1));IRB.CreateCondBr(IsDecrypted, Exit, LoopBody);IRB.SetInsertPoint(LoopBody);PHINode *LoopCounter = IRB.CreatePHI(IRB.getInt32Ty(), 2);LoopCounter->addIncoming(IRB.getInt32(0), Enter);Value *EncCharPtr =IRB.CreateInBoundsGEP(IRB.getInt8Ty(), EncPtr, LoopCounter);Value *EncChar = IRB.CreateLoad(IRB.getInt8Ty(), EncCharPtr);Value *KeyIdx = IRB.CreateURem(LoopCounter, KeySize);Value *KeyCharPtr = IRB.CreateInBoundsGEP(IRB.getInt8Ty(), Data, KeyIdx);Value *KeyChar = IRB.CreateLoad(IRB.getInt8Ty(), KeyCharPtr);Value *DecChar = IRB.CreateXor(EncChar, KeyChar);Value *DecCharPtr =IRB.CreateInBoundsGEP(IRB.getInt8Ty(), PlainString, LoopCounter);IRB.CreateStore(DecChar, DecCharPtr);Value *NewCounter =IRB.CreateAdd(LoopCounter, IRB.getInt32(1), "", true, true);LoopCounter->addIncoming(NewCounter, LoopBody);Value *Cond = IRB.CreateICmpEQ(NewCounter, IRB.getInt32(static_cast<uint32_t>(Entry->Data.size())));IRB.CreateCondBr(Cond, UpdateDecStatus, LoopBody);IRB.SetInsertPoint(UpdateDecStatus);IRB.CreateStore(IRB.getInt32(1), Entry->DecStatus);IRB.CreateBr(Exit);IRB.SetInsertPoint(Exit);IRB.CreateRetVoid();return DecFunc;
}Function *
StringEncryptionPass::buildInitFunction(Module *M,const StringEncryptionPass::CSUser *User) {LLVMContext &Ctx = M->getContext();IRBuilder<> IRB(Ctx);FunctionType *FuncTy = FunctionType::get(Type::getVoidTy(Ctx),{User->DecGV->getType()}, false);Function *InitFunc = Function::Create(FuncTy, GlobalValue::PrivateLinkage,"__global_variable_initializer_" + User->GV->getName(), M);auto ArgIt = InitFunc->arg_begin();Argument *thiz = ArgIt;thiz->setName("this");thiz->addAttr(Attribute::NoCapture);// convert constant initializer into a series of instructionsBasicBlock *Enter = BasicBlock::Create(Ctx, "Enter", InitFunc);BasicBlock *InitBlock = BasicBlock::Create(Ctx, "InitBlock", InitFunc);BasicBlock *Exit = BasicBlock::Create(Ctx, "Exit", InitFunc);IRB.SetInsertPoint(Enter);Value *DecStatus =IRB.CreateLoad(User->DecStatus->getValueType(), User->DecStatus);Value *IsDecrypted = IRB.CreateICmpEQ(DecStatus, IRB.getInt32(1));IRB.CreateCondBr(IsDecrypted, Exit, InitBlock);IRB.SetInsertPoint(InitBlock);Constant *Init = User->GV->getInitializer();lowerGlobalConstant(Init, IRB, User->DecGV, User->Ty);IRB.CreateStore(IRB.getInt32(1), User->DecStatus);IRB.CreateBr(Exit);IRB.SetInsertPoint(Exit);IRB.CreateRetVoid();return InitFunc;
}void StringEncryptionPass::lowerGlobalConstant(Constant *CV, IRBuilder<> &IRB,Value *Ptr, Type *Ty) {if (isa<ConstantAggregateZero>(CV)) {IRB.CreateStore(CV, Ptr);return;}if (ConstantArray *CA = dyn_cast<ConstantArray>(CV)) {lowerGlobalConstantArray(CA, IRB, Ptr, Ty);} else if (ConstantStruct *CS = dyn_cast<ConstantStruct>(CV)) {lowerGlobalConstantStruct(CS, IRB, Ptr, Ty);} else {IRB.CreateStore(CV, Ptr);}
}void StringEncryptionPass::lowerGlobalConstantArray(ConstantArray *CA,IRBuilder<> &IRB, Value *Ptr,Type *Ty) {for (unsigned i = 0, e = CA->getNumOperands(); i != e; ++i) {Constant *CV = CA->getOperand(i);Value *GEP = IRB.CreateGEP(Ty, Ptr, {IRB.getInt32(0), IRB.getInt32(i)});lowerGlobalConstant(CV, IRB, GEP, CV->getType());}
}void StringEncryptionPass::lowerGlobalConstantStruct(ConstantStruct *CS,IRBuilder<> &IRB, Value *Ptr,Type *Ty) {for (unsigned i = 0, e = CS->getNumOperands(); i != e; ++i) {Constant *CV = CS->getOperand(i);Value *GEP = IRB.CreateGEP(Ty, Ptr, {IRB.getInt32(0), IRB.getInt32(i)});lowerGlobalConstant(CV, IRB, GEP, CV->getType());}
}bool StringEncryptionPass::processConstantStringUse(Function *F) {if (!toObfuscate(flag, F, "cse")) {return false;}if (Options && Options->skipFunction(F->getName())) {return false;}LowerConstantExpr(*F);SmallPtrSet<GlobalVariable *, 16>DecryptedGV; // if GV has multiple use in a block, decrypt only at the// first usebool Changed = false;for (BasicBlock &BB : *F) {DecryptedGV.clear();if (BB.isEHPad()) {continue;}for (Instruction &Inst : BB) {if (Inst.isEHPad()) {continue;}if (PHINode *PHI = dyn_cast<PHINode>(&Inst)) {for (unsigned int i = 0; i < PHI->getNumIncomingValues(); ++i) {if (GlobalVariable *GV = dyn_cast<GlobalVariable>(PHI->getIncomingValue(i))) {auto Iter1 = CSPEntryMap.find(GV);auto Iter2 = CSUserMap.find(GV);if (Iter2 !=CSUserMap.end()) { // GV is a constant string userCSUser *User = Iter2->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, User->DecGV);} else {Instruction *InsertPoint =PHI->getIncomingBlock(i)->getTerminator();IRBuilder<> IRB(InsertPoint);IRB.CreateCall(User->InitFunc, {User->DecGV});Inst.replaceUsesOfWith(GV, User->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}} else if (Iter1 !=CSPEntryMap.end()) { // GV is a constant stringCSPEntry *Entry = Iter1->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, Entry->DecGV);} else {Instruction *InsertPoint =PHI->getIncomingBlock(i)->getTerminator();IRBuilder<> IRB(InsertPoint);Value *OutBuf = IRB.CreateBitCast(Entry->DecGV, IRB.getPtrTy());Value *Data = IRB.CreateInBoundsGEP(EncryptedStringTable->getValueType(),EncryptedStringTable,{IRB.getInt32(0), IRB.getInt32(Entry->Offset)});IRB.CreateCall(Entry->DecFunc, {OutBuf, Data});Inst.replaceUsesOfWith(GV, Entry->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}}}}} else {for (User::op_iterator op = Inst.op_begin();op != Inst.op_end(); ++op) {if (GlobalVariable *GV = dyn_cast<GlobalVariable>(*op)) {auto Iter1 = CSPEntryMap.find(GV);auto Iter2 = CSUserMap.find(GV);if (Iter2 != CSUserMap.end()) {CSUser *User = Iter2->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, User->DecGV);} else {IRBuilder<> IRB(&Inst);IRB.CreateCall(User->InitFunc, {User->DecGV});Inst.replaceUsesOfWith(GV, User->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}} else if (Iter1 != CSPEntryMap.end()) {CSPEntry *Entry = Iter1->second;if (DecryptedGV.count(GV) > 0) {Inst.replaceUsesOfWith(GV, Entry->DecGV);} else {IRBuilder<> IRB(&Inst);Value *OutBuf = IRB.CreateBitCast(Entry->DecGV, IRB.getPtrTy());Value *Data = IRB.CreateInBoundsGEP(EncryptedStringTable->getValueType(),EncryptedStringTable,{IRB.getInt32(0), IRB.getInt32(Entry->Offset)});IRB.CreateCall(Entry->DecFunc, {OutBuf, Data});Inst.replaceUsesOfWith(GV, Entry->DecGV);MaybeDeadGlobalVars.insert(GV);DecryptedGV.insert(GV);Changed = true;}}}}}}}return Changed;
}void StringEncryptionPass::collectConstantStringUser(GlobalVariable *CString, std::set<GlobalVariable *> &Users) {SmallPtrSet<Value *, 16> Visited;SmallVector<Value *, 16> ToVisit;ToVisit.push_back(CString);while (!ToVisit.empty()) {Value *V = ToVisit.pop_back_val();if (Visited.count(V) > 0)continue;Visited.insert(V);for (Value *User : V->users()) {if (auto *GV = dyn_cast<GlobalVariable>(User)) {Users.insert(GV);} else {ToVisit.push_back(User);}}}
}bool StringEncryptionPass::isValidToEncrypt(GlobalVariable *GV) {if (GV->isConstant() && GV->hasInitializer()) {return GV->getInitializer() != nullptr;} else {return false;}
}void StringEncryptionPass::deleteUnusedGlobalVariable() {bool Changed = true;while (Changed) {Changed = false;for (auto Iter = MaybeDeadGlobalVars.begin();Iter != MaybeDeadGlobalVars.end();) {GlobalVariable *GV = *Iter;if (!GV->hasLocalLinkage()) {++Iter;continue;}GV->removeDeadConstantUsers();if (GV->use_empty()) {if (GV->hasInitializer()) {Constant *Init = GV->getInitializer();GV->setInitializer(nullptr);if (isSafeToDestroyConstant(Init))Init->destroyConstant();}Iter = MaybeDeadGlobalVars.erase(Iter);GV->eraseFromParent();Changed = true;} else {++Iter;}}}
}StringEncryptionPass *llvm::createStringEncryption(bool flag){return new StringEncryptionPass(flag);
}
3. 注册命令行
static cl::opt<bool> s_obf_sobf("sobf", cl::init(false), cl::desc("String Obfuscation"));
4. 注册Pass
PassBuilder::PassBuilder(TargetMachine *TM, PipelineTuningOptions PTO,std::optional<PGOOptions> PGOOpt,PassInstrumentationCallbacks *PIC): TM(TM), PTO(PTO), PGOOpt(PGOOpt), PIC(PIC) {// 注册 Obfuscation 相关 Passthis->registerPipelineStartEPCallback([](llvm::ModulePassManager &MPM,llvm::OptimizationLevel Level) {MPM.addPass(StringEncryptionPass(s_obf_sobf)); // 先进行字符串加密 出现字符串加密基本块以后再进行基本块分割和其他混淆 加大解密难度});
}
测试
关于 OLLVM 的编译可以参考这篇文章:移植 OLLVM 到 LLVM 18,C&C++代码混淆
启用字符串加密并编译 C 代码
clang -mllvm -sobf sobf.c -o sobf.exe
使用 IDA 反汇编
未启用字符串加密
clang sobf.c -o nosobf.exe
使用 IDA 反汇编
程序运行正常
移植到 Android NDK
详细移植过程可以参考这篇文章:移植 OLLVM 到 Android NDK,Android Studio 中使用 OLLVM
在 CMakeLists.txt 中添加如下配置启用字符串加密
# 全局启用指令替换、字符串加密
add_definitions("-mllvm -sub -mllvm -sobf")
C++ 源码如下:
编译完成后,通过 IDA 打开 so 可以看到字符串常量 “cyrus” 已经被替换成 &unk_6DE20
替换成了一个未初始化的全局变量
app 运行正常
完整源码
LLVM 源码地址:https://github.com/CYRUS-STUDIO/LLVM
Android 示例源码地址:https://github.com/CYRUS-STUDIO/AndroidExample