版权归作者所有,如有转发,请注明文章出处:cyrus-studio.github.io/blog/
前言
当我们如果没有对字符串进行加密,使用 IDA 反汇编一下 so 可以看到 C++ 代码中的字符串就直接暴露了。
字符串加密原理
sobf.c
arduino
#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@ 使用
perl
; 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 8
store ptr %1, ptr %3, align 8
store ptr %0, ptr %4, align 8
call 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 4
call void @llvm.va_end(ptr %6)
%11 = load i32, ptr %5, align 4
ret 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 8
store ptr %2, ptr %4, align 8
store ptr %1, ptr %5, align 8
store 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 8
store ptr %2, ptr %4, align 8
store i64 %1, ptr %5, align 8
store ptr %0, ptr %6, align 8
call 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 4
call void @llvm.va_end(ptr %8)
%14 = load i32, ptr %7, align 4
ret 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 8
store ptr %3, ptr %5, align 8
store ptr %2, ptr %6, align 8
store i64 %1, ptr %7, align 8
store 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 8
store i32 0, ptr %1, align 4
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
}
; 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 8
store ptr %0, ptr %2, align 8
call 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 4
call void @llvm.va_end(ptr %4)
%9 = load i32, ptr %3, align 4
ret 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 8
store ptr %3, ptr %5, align 8
store ptr %2, ptr %6, align 8
store ptr %1, ptr %7, align 8
store 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 4
store ptr %4, ptr %6, align 8
store ptr %3, ptr %7, align 8
store ptr %2, ptr %8, align 8
store i64 %1, ptr %9, align 8
store 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, 0
br i1 %22, label %23, label %24
23: ; preds = %5
br label %26
24: ; preds = %5
%25 = load i32, ptr %11, align 4
br label %26
26: ; 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 8
store ptr %3, ptr %5, align 8
store ptr %2, ptr %6, align 8
store ptr %1, ptr %7, align 8
store 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) #2
declare dso_local i32 @__stdio_common_vfprintf(i64 noundef, ptr noundef, ptr noundef, ptr noundef, ptr noundef) #2
attributes #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@:表示一个字符串常量,后面是哈希值和字符串名称的编码。
只要在定义的地方把字符串加密,引用时候再解密就可以实现字符串的自动加解密了。
字符串常量
perl
@"??_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()函数
perl
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
arduino
#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 not
std::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 not
Function *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 方法迭代每一条指令,收集字符串常量
ini
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 加密字符串 和 构造解密函数
ini
// 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 创建一个初始化函数
为字符串变量创建一个初始化函数,用于在运行时执行解密操作。
ini
// 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() 找到所有使用加密字符串的地方,替换为解密后的变量。
scss
// 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);
}
完整代码
ini
#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 strings
LLVMContext &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 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);
}
// 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;
}
}
// 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 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);
}
// delete unused global variables
deleteUnusedGlobalVariable();
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; // input
PlainString->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 instructions
BasicBlock *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 use
bool 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 user
CSUser *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 string
CSPEntry *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. 注册命令行
rust
static cl::opt<bool> s_obf_sobf("sobf", cl::init(false), cl::desc("String Obfuscation"));
4. 注册Pass
scss
PassBuilder::PassBuilder(TargetMachine *TM, PipelineTuningOptions PTO,
std::optional<PGOOptions> PGOOpt,
PassInstrumentationCallbacks *PIC)
: TM(TM), PTO(PTO), PGOOpt(PGOOpt), PIC(PIC) {
// 注册 Obfuscation 相关 Pass
this->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 中添加如下配置启用字符串加密
bash
# 全局启用指令替换、字符串加密
add_definitions("-mllvm -sub -mllvm -sobf")
C++ 源码如下:
编译完成后,通过 IDA 打开 so 可以看到字符串常量 "cyrus" 已经被替换成 &unk_6DE20
替换成了一个未初始化的全局变量
app 运行正常
完整源码
LLVM 源码地址:github.com/CYRUS-STUDI...
Android 示例源码地址:github.com/CYRUS-STUDI...