OLLVM 增加 C&C++ 字符串加密功能

版权归作者所有,如有转发,请注明文章出处: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...

相关推荐
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型
c++·计算机网络·ip·tcp
故事与他6455 小时前
Thinkphp(TP)框架漏洞攻略
android·服务器·网络·中间件·tomcat
IYU_5 小时前
VulnHub-Web-Machine-N7通关攻略
服务器·安全·web安全·网络安全
lmy3477712327 小时前
东软鸿蒙C++开发面经
开发语言·c++
每次的天空8 小时前
项目总结:GetX + Kotlin 协程实现跨端音乐播放实时同步
android·开发语言·kotlin
你觉得2058 小时前
天津大学第二讲:《深度解读DeepSeek:部署、使用、安全》|附PPT下载方法
大数据·人工智能·安全·机器学习·ai·知识图谱·内容运营
珊瑚里的鱼8 小时前
第三讲 | C/C++内存管理完全手册
c语言·c++·笔记·程序人生·visualstudio·visual studio
网络安全指导员8 小时前
威胁驱动的网络安全方法论
开发语言·学习·安全·web安全·php
柯ran8 小时前
C++|面试准备二(常考)
开发语言·c++·面试