OLLVM学习

OLLVM学习

LLVM

LLVM简介

  LLVM(Low Level Virtual Machine)是苹果公司的开源编译器框架, 包含包括Clang在内的一系列编译相关工具, 于2000年左右开发, LLVM/Clang从XCode8起作为XCode默认编译器, LLVM作为以下语言的开发工具链: C, C++, Objective-C, Swift, Ruby, Python, Haskell, Rust, D, PHP, Pure, Lua, Julia. 相比同样庞大但臃肿的GCC, LLVM的模块化设计更利于扩展和维护, 因此LLVM取代GCC是必然趋势. LLVM包含如下组件:

  • Clang, 用于做C/C++/Objective-C的编译前端
  • LLDB, 调试器
  • libc++, 提供c++基础库
  • compiler-rt
  • MLIR
  • OpenMP
  • libclc
  • klee
  • LLD 链接器
  • BOLT

第三方:

  • rustc, 用于rust的编译前端
  • swiftc, 用于swift的编译前端
  • codon, 用于python的编译前端

历史更新功能点:

  • 由Chris Lattner于2000创建
  • LLVM1.0(2003), 首次公开发布
  • LLVM3.0(2012), 引入了新的JIT编译器, 支持C++11, 基于SSA的内存安全转换, 全局ISel重构
  • LLVM3.7(2015), 支持OpenMP3.1, Clang Static Analyzer增强,AArch64支持
  • LLVM5.0(2016), 支持C++14, 引入了新的代码分析和优化技术
  • LLVM9.0(2019), 支持C++17, JIT支持WebAssembly, 优化RISC-V, 优化IR
  • LLVM12.0(2021), 支持C++20, 引入LTO优化, 支持arm64e

XCode与LLVM版本对应:

XCode LLVM
11.x 11
12.x 12
13.x 13
14.x 14
15.x 15

LLVM IR

IR简介

  IR(Intermediate Representation), 是一种LLVM定义的介于源码和汇编的中间语言, 语法类似于汇编. IR主要用于解决跨平台编译的问题, 同时也能解决优化/混淆/扩展问题. IR手册 https://llvm.org/docs/LangRef.html, 以下是IR相关的命令:

  • llc 将bitcode转换为asm/obj
  • lld 将多个bitcode/obj编译为二进制
  • lli bitcode解释器
  • opt 优化bitcode
  • llvm-ar 操作archive
  • llvm-as 将ll转换为bitcode, ll为人类可读字节码格式
  • llvm-cxxfilt c++修饰名转普通
  • llvm-dis bitcode转ll
  • llvm-extract 从bitcode提取函数
  • llvm-link 将多个bitcode合并为一个bitcode
  • clang -emit-llvm -c 源码编译为bitcode
  • clang -emit-llvm -S 源码编译为ll

第三方:

  • swiftc -emit-assembly /tmp/1.swift -o /tmp/1.bc Swift源码编译为汇编
  • swiftc -emit-bc /tmp/1.swift -o /tmp/1.bc Swift源码编译为bitcode
  • swiftc -emit-ir /tmp/1.swift -o /tmp/1.ll Swift源码编译为ll
  • cargo rustc -- --emit=asmrustc --emit=asm 1.rs Rust源码编译为汇编
  • cargo rustc -- --emit=llvm-bcrustc --emit=llvm-bc 1.rs Rust源码编译为bitcode
  • cargo rustc -- --emit=llvm-irrustc --emit=llvm-ir 1.rs Rust源码编译为ll
  • codon build -llvm 1.py Python源码编译为ll

测试用例:

// 1.cpp
#include <stdio.h>
int main(int argc, char** argv) {
  printf("Hello World!\n");
  return 0;
}

源码交叉编译为bitcode/ll

# for MacOS x86_64
./clang -isysroot `xcrun --sdk macosx --show-sdk-path` -arch x86_64 -emit-llvm -c /tmp/1.cpp --output=/tmp/1.bc
./clang -isysroot `xcrun --sdk macosx --show-sdk-path` -arch x86_64 -emit-llvm -S /tmp/1.cpp --output=/tmp/1.ll
# 如果要用XCode自带clang需使用xcrun, 以下同, 不建议用XCode clang, 因为不同版本Clang/llc/lld/lli互相不兼容, 且XCode不提供llc/lld/lli
xcrun --sdk macosx clang -arch x86_64 -emit-llvm -c /tmp/1.cpp --output=/tmp/1.bc
; ModuleID = '/tmp/1.cpp'
source_filename = "/tmp/1.cpp"
target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx11.3.0"

@.str = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1

; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable
define noundef i32 @main(i32 noundef %0, ptr noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca ptr, align 8
  store i32 0, ptr %3, align 4
  store i32 %0, ptr %4, align 4
  store ptr %1, ptr %5, align 8
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret i32 0
}

declare i32 @printf(ptr noundef, ...) #1

attributes #0 = { mustprogress noinline norecurse optnone ssp uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cmov,+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }
attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="penryn" "target-features"="+cmov,+cx16,+cx8,+fxsr,+mmx,+sahf,+sse,+sse2,+sse3,+sse4.1,+ssse3,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 11, i32 3]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 8, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{i32 7, !"frame-pointer", i32 2}
!5 = !{!"clang version 19.0.0git"}
# for iOS arm64
./clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` -arch arm64 -emit-llvm -c /tmp/1.cpp --output=/tmp/1.bc
./clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` -arch arm64 -emit-llvm -S /tmp/1.cpp --output=/tmp/1.ll
; ModuleID = '/tmp/1.cpp'
source_filename = "/tmp/1.cpp"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-ios14.5.0"

@.str = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1

; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable(sync)
define noundef i32 @main(i32 noundef %0, ptr noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca ptr, align 8
  store i32 0, ptr %3, align 4
  store i32 %0, ptr %4, align 4
  store ptr %1, ptr %5, align 8
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret i32 0
}

declare i32 @printf(ptr noundef, ...) #1

attributes #0 = { mustprogress noinline norecurse optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+fp-armv8,+neon,+perfmon,+sha2,+v8a,+zcm,+zcz" }
attributes #1 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+fp-armv8,+neon,+perfmon,+sha2,+v8a,+zcm,+zcz" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 14, i32 5]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 8, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 1}
!5 = !{!"clang version 19.0.0git"}; ModuleID = '/tmp/1.cpp'
source_filename = "/tmp/1.cpp"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-ios14.5.0"

@.str = private unnamed_addr constant [14 x i8] c"Hello World!\0A\00", align 1

; Function Attrs: mustprogress noinline norecurse optnone ssp uwtable(sync)
define noundef i32 @main(i32 noundef %0, ptr noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca ptr, align 8
  store i32 0, ptr %3, align 4
  store i32 %0, ptr %4, align 4
  store ptr %1, ptr %5, align 8
  %6 = call i32 (ptr, ...) @printf(ptr noundef @.str)
  ret i32 0
}

declare i32 @printf(ptr noundef, ...) #1

attributes #0 = { mustprogress noinline norecurse optnone ssp uwtable(sync) "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+fp-armv8,+neon,+perfmon,+sha2,+v8a,+zcm,+zcz" }
attributes #1 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+fp-armv8,+neon,+perfmon,+sha2,+v8a,+zcm,+zcz" }

!llvm.module.flags = !{!0, !1, !2, !3, !4}
!llvm.ident = !{!5}

!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 14, i32 5]}
!1 = !{i32 1, !"wchar_size", i32 4}
!2 = !{i32 8, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 1}
!4 = !{i32 7, !"frame-pointer", i32 1}
!5 = !{!"clang version 19.0.0git"}

bitcode/ll编译为asm/obj

./llc --filetype=asm /tmp/1.ll -o /tmp/1.asm
./llc --filetype=obj /tmp/1.ll -o /tmp/1.obj
./llc --filetype=asm /tmp/1.bc -o /tmp/1.asm
./llc --filetype=obj /tmp/1.bc -o /tmp/1.obj
	.section	__TEXT,__text,regular,pure_instructions
	.build_version ios, 14, 5	sdk_version 14, 5
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #32
	stp	x29, x30, [sp, #16]             ; 16-byte Folded Spill
	add	x29, sp, #16
	.cfi_def_cfa w29, 16
	.cfi_offset w30, -8
	.cfi_offset w29, -16
	stur	wzr, [x29, #-4]
	str	w0, [sp, #8]
	str	x1, [sp]
	adrp	x0, l_.str@PAGE
	add	x0, x0, l_.str@PAGEOFF
	bl	_printf
	mov	w0, #0                          ; =0x0
	ldp	x29, x30, [sp, #16]             ; 16-byte Folded Reload
	add	sp, sp, #32
	ret
	.cfi_endproc
                                        ; -- End function
	.section	__TEXT,__cstring,cstring_literals
l_.str:                                 ; @.str
	.asciz	"Hello World!\n"

.subsections_via_symbols

bitcode编译为可执行程序:

lld是通用程序, 不同平台需要调用不同二进制

  • Unix: ld.lld
  • macOS: ld64.lld
  • Windows: lld-link
  • WebAssembly: wasm-ld
./ld64.lld -arch arm64 -platform_version ios 12.0 14.5 -dylib /tmp/1.bc -o /tmp/1.exe

运行bitcode

./lli /tmp/1.ll
./lli /tmp/1.bc
# 均输出"Hello World!"

IR指令

Instruction
UnaryInstruction        一元指令
  UnaryOperator         一元操作
  CastInst              强制转换
    PossiblyNonNegInst  非负指令
BinaryOperator          二进制操作
  PossiblyDisjointInst
CmpInst                 比较操作
CallBase                调用操作
FuncletPadInst    

                    Super
AllocaInst          UnaryInstruction    An instruction to allocate memory on the stack.
LoadInst            UnaryInstruction    An instruction for reading from memory. This uses the SubclassData
                                        field in Value to store whether or not the load is volatile.
StoreInst           Instruction         An instruction for storing to memory.
FenceInst           Instruction         An instruction for ordering other memory operations.
AtomicCmpXchgInst   Instruction         An instruction that atomically checks whether a specified value 
                                        is in a memory location, and, if it is, stores a new value there. 
                                        The value returned by this instruction is a pair containing the 
                                        original value as first element, and an i1 indicating success 
                                        (true) or failure (false) as second element.
AtomicRMWInst       Instruction         An instruction that atomically reads a memory location, combines 
                                        it with another value, and then stores the result back.  Returns 
                                        the old value.
GetElementPtrInst   Instruction         An instruction for type-safe pointer arithmetic to access elements 
                                        of arrays and structs
ICmpInst            CmpInst             This instruction compares its operands according to the predicate 
                                        given to the constructor. It only operates on integers or pointers. 
                                        The operands must be identical types. Represent an integer comparison 
                                        operator.
FCmpInst            CmpInst             This instruction compares its operands according to the predicate 
                                        given to the constructor. It only operates on floating point values 
                                        or packed vectors of floating point values. The operands must be 
                                        identical types. Represents a floating point comparison operator.
CallInst            CallBase            This class represents a function call, abstracting a target machine's 
                                        calling convention. This class uses low bit of the SubClassData
                                        field to indicate whether or not this is a tail call. The rest 
                                        of the bits hold the calling convention of the call.
SelectInst          Instruction         This class represents the LLVM 'select' instruction.
VAArgInst           UnaryInstruction    This class represents the va_arg llvm instruction, which returns 
                                        an argument of the specified type given a va_list and increments 
                                        that list
ExtractElementInst  Instruction         This instruction extracts a single (scalar) element from a VectorType value
InsertElementInst   Instruction         This instruction inserts a single (scalar) element into a VectorType value
ShuffleVectorInst   Instruction         This instruction constructs a fixed permutation of two input vectors. 
                                        For each element of the result vector, the shuffle mask selects an 
                                        element from one of the input vectors to copy to the result. 
                                        Non-negative elements in the mask represent an index into the 
                                        concatenated pair of input vectors. PoisonMaskElem (-1) specifies 
                                        that the result element is poison. For scalable vectors, all the 
                                        elements of the mask must be 0 or -1. This requirement may be 
                                        relaxed in the future.
ExtractValueInst    UnaryInstruction    This instruction extracts a struct member or array element value 
                                        from an aggregate value.
InsertValueInst     Instruction         This instruction inserts a struct field of array element value 
                                        into an aggregate value.
PHINode             Instruction         PHINode - The PHINode class is used to represent the magical mystical 
                                        PHI node, that can not exist in nature, but can be synthesized in a 
                                        computer scientist's overactive imagination.
LandingPadInst      Instruction         The landingpad instruction holds all of the information necessary 
                                        to generate correct exception handling. The landingpad instruction 
                                        cannot be moved from the top of a landing pad block, which itself 
                                        is accessible only from the 'unwind' edge of an invoke. This uses 
                                        the SubclassData field in Value to store whether or not the landingpad 
                                        is a cleanup.
ReturnInst          Instruction         Return a value (possibly void), from a function. Execution does 
                                        not continue in this function any longer.
BranchInst          Instruction         Conditional or Unconditional Branch instruction.
SwitchInst          Instruction         Multiway switch.
IndirectBrInst      Instruction         Indirect Branch Instruction.
InvokeInst          CallBase            Invoke instruction. The SubclassData field is used to hold the 
                                        calling convention of the call.
CallBrInst          CallBase            CallBr instruction, tracking function calls that may not return 
                                        control but instead transfer it to a third location. The SubclassData 
                                        field is used to hold the calling convention of the call.
ResumeInst          Instruction         Resume the propagation of an exception.
CatchSwitchInst     Instruction
CleanupPadInst      FuncletPadInst
CatchPadInst        FuncletPadInst
CatchReturnInst     Instruction
CleanupReturnInst   Instruction
UnreachableInst     Instruction         This function has undefined behavior. In particular, the presence 
                                        of this instruction indicates some higher level knowledge that 
                                        the end of the block cannot be reached.
TruncInst           CastInst            This class represents a truncation of integer types.
ZExtInst            CastInst            This class represents zero extension of integer types.
SExtInst            CastInst            This class represents a sign extension of integer types.
FPTruncInst         CastInst            This class represents a truncation of floating point types.
FPExtInst           CastInst            This class represents an extension of floating point types.
UIToFPInst          CastInst            This class represents a cast unsigned integer to floating point.
SIToFPInst          CastInst            This class represents a cast from signed integer to floating point.
FPToUIInst          CastInst            This class represents a cast from floating point to unsigned integer.
FPToSIInst          CastInst            This class represents a cast from floating point to signed integer.
IntToPtrInst        CastInst            This class represents a cast from an integer to a pointer.
PtrToIntInst        CastInst            This class represents a cast from a pointer to an integer.
BitCastInst         CastInst            This class represents a no-op cast from one type to another.
AddrSpaceCastInst   CastInst            This class represents a conversion between pointers from one address
                                        space to another.
FreezeInst          UnaryInstruction    This class represents a freeze function that returns random concrete 
                                        value if an operand is either a poison value or an undef value

LLVM Pass

简介

  LLVM Pass是LLVM提供的用于优化/分析/处理IR的组件, 第三方可以自由开发Pass从而干涉编译过程, 实现代码优化/静态分析/代码混淆. 上一节学习了CMake的基本用法, 现在来用CMake实现最简单的LLVM Pass. 笔者的环境仍是MacOS. 需要注意的是LLVM Pass从LLVM版本支持可分为Legacy Pass和New Pass, 前者是历史遗留, 具体兼容性如下表.

LLVM 默认 可选
5-12 LegacyPassManager -fexperimental-new-pass-manager 启用New, 该功能有限
13-14 NewPassManager -flegacy-pass-manager 启用 Legacy
15-? NewPassManager

   Pass从类型可分为FunctionPass, ModulePass, LoopPass, RegionPass, MachineFunctionPass, AnalysePass, CallGraphSCCPass等, FunctionPass用于做函数层面的操作, ModulePass用于做模块层面的操作(模块包括函数, 全局变量等, 所以也可以包含FunctionPass的功能), AnalysePass主要做性能测试/压力测试/调优/分析/日志等.

  • 一个项目由多个Module构成, 一个Module约等于一个存在函数实体的文件, 头文件或者被包含的文件不算
  • 一个Module由多个Function/GlobalVariable/GlobalAlias/GlobalIFunc/NamedMDNode构成, 前两个最常用
  • 一个Function由多个BasicBlock构成, 继承过程: Function - GlobalObject - GlobalValue - Constant - User - Value
  • 一个BasicBlock由多个Instruction构成, 每个BasicBlock都有一个结束指令, 继承过程: BasicBlock - Value
  • 一个Instruction即为一条IR指令, 最终会编译为一条或多条汇编指令, 继承过程: Instruction - User - Value
  • GlobalVariable继承过程: GlobalVariable - GlobalObject - GlobalValue - Constant - User - Value

什么情况下使用LLVM Pass?   如第一篇所述, LLVM衍生出众多编译器前端, 如clang, swiftc, rustc等. 目前第三方代码嵌入LLVM有如下三种方式:

  • 动态Pass方式, 前端运行时动态加载Pass, 开发成本最低
  • 静态Pass方式, 编译时静态链接Pass
  • 修改LLVM源码强行嵌入, 是大部分Ollvm采用的方式; 如果编译器前端不支持Pass则是唯一选择

AppleClang的Pass   AppleClang, 即XCode自带的Clang, 苹果因为安全性考虑阉割掉了LLVM Pass, 因此常规方法并不能加载起来. 当然如果如果逆向技术过关, 也很容易将Pass改为兼容AppleClang的.

编码

  只需要demo.cppCMakeLists.txt两个文件. 因为LLVM的版本较多, 网上开源的LLVM Pass项目只支持部分版本, 笔者根据刚学习的CMake将其改造为兼容LLVM8-18.

项目地址:https://github.com/lich4/llvm-pass-hikari

demo.cpp

#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#if LLVM_VERSION_MAJOR <= 15
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif

#include "llvm/IRReader/IRReader.h"
#include "llvm/Transforms/Utils/Cloning.h"
using namespace llvm;

#include <iostream>

#define PASSNAME          "MyPassDemo"

#if LLVM_VERSION_MAJOR <= 13
#define getPtElemType getPointerElementType
#else
#define getPtElemType getNonOpaquePointerElementType
#endif

// ---------------- Legacy Pass ---------------- //
class MyPassDemoLegacy : public FunctionPass {
public:
    static char ID;
    MyPassDemoLegacy() : FunctionPass(ID) {}
    virtual bool runOnFunction(Function& F) override {
        errs() << "MyPassDemoLegacy\n";
        return false;
    }
};
char MyPassDemoLegacy::ID = 0;
#if LLVM_VERSION_MAJOR <= 15
static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible, 
    [](const PassManagerBuilder &, legacy::PassManagerBase &PM) {
        PM.add(new MyPassDemoLegacy());
    }
);
#else
static RegisterPass<MyPassDemoLegacy> RegisterMyPass(PASSNAME, PASSNAME, false, false);
#endif
// ---------------- Legacy Pass ---------------- //

// ---------------- New Pass ---------------- //
#if LLVM_VERSION_MAJOR <= 13
#define OptimizationLevel PassBuilder::OptimizationLevel
#endif

class MyPassDemo : public PassInfoMixin<MyPassDemo> {
public:
    PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
        errs() << "MyPassDemo\n";
        return PreservedAnalyses::all();
    };
    static bool isRequired() { return true; }
};

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
    return {
        .APIVersion = LLVM_PLUGIN_API_VERSION,
        .PluginName = PASSNAME,
        .PluginVersion = "1.0",
        .RegisterPassBuilderCallbacks = [](PassBuilder &PB) {
            PB.registerPipelineStartEPCallback(
                [](ModulePassManager &MPM
#if LLVM_VERSION_MAJOR >= 12
                , OptimizationLevel Level
#endif
                ) {
                    MPM.addPass(MyPassDemo());
            });
            PB.registerPipelineParsingCallback(
                [](StringRef Name, ModulePassManager& MPM, ArrayRef<PassBuilder::PipelineElement>) {
                    MPM.addPass(MyPassDemo());
                    return true;
            });
        }
    };
}
// ---------------- New Pass ---------------- //

__attribute__((constructor)) void onInit() {
    printf("MyPassDemo onInit\n");
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.6)
project(MyPassDemo)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_BUILD_TYPE "Debug")

find_package(LLVM REQUIRED CONFIG)            # LLVMConfig.cmake初始化环境

list(APPEND CMAKE_MODULE_PATH "${LLVM_DIR}")  # 兼容LLVM<=13

include(AddLLVM)                              # 导入add_llvm_pass_plugin函数
include(HandleLLVMOptions)

add_definitions(${LLVM_DEFINITIONS})
include_directories(${LLVM_INCLUDE_DIRS})
link_directories(${LLVM_LIBRARY_DIRS}) 

if(NOT COMMAND add_llvm_pass_plugin)          # 兼容LLVM<=9
  message(WARNING "add_llvm_pass_plugin not exist")
  function(add_llvm_pass_plugin name)         
    cmake_parse_arguments(ARG "NO_MODULE" "SUBPROJECT" "" ${ARGN})
    set(link_into_tools_default OFF)
    add_llvm_library(${name} MODULE ${ARG_UNPARSED_ARGUMENTS})
    message(STATUS "Registering ${name} as a pass plugin (static build: ${LLVM_${name_upper}_LINK_INTO_TOOLS})")
  endfunction(add_llvm_pass_plugin)
endif()

add_llvm_pass_plugin(MyPassDemo${LLVM_VERSION_MAJOR}
    demo.cpp
)

测试

编译:

export LLVM_DIR=/path/to/llvm12/build/lib/cmake/llvm
cmake -B build --fresh
cmake --build build
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -Xclang -load -Xclang build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM<=12      Legacy Pass (等价于上面)
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fplugin=build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM=9/10/11 New Pass (O3生效)
llvm11/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fexperimental-new-pass-manager -fpass-plugin=build/MyPassDemo11.dylib /tmp/1.cpp -O3
# for LLVM=12      New Pass
llvm12/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fexperimental-new-pass-manager -fpass-plugin=build/MyPassDemo12.dylib /tmp/1.cpp
# for LLVM=13/14    Legacy Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -flegacy-pass-manager -fplugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM=13/14    New Pass
llvm13/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo13.dylib /tmp/1.cpp
# for LLVM>=15      New Pass
llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib /tmp/1.cpp

opt支持ll需要llvm>=15

llvm15/build/bin/opt --O3 -S -o /tmp/test_new.ll /tmp/test.ll 
llvm15/build/bin/opt -load-pass-plugin build/MyPassDemo15.dylib -passes all -S -o /tmp/test_new.ll /tmp/test.ll 

经过测试可以发现, Pass的LLVM版本需要和LLVM大版本一致, 否则也会产生错误, 但总体来说Pass解决了LLVM编译代码量过大的问题, 所以仍然值得采用, 测试结果: hello MyPassDemo

Pass加载时机

// EP_Peephole                  PeepholeEPCallbacks
void registerPeepholeEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_LoopOptimizerEnd          LateLoopOptimizationsEPCallbacks
void registerLateLoopOptimizationsEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_LateLoopOptimizations     LoopOptimizerEndEPCallbacks
void registerLoopOptimizerEndEPCallback(const std::function<void(LoopPassManager&, OptimizationLevel)>&);
// EP_ScalarOptimizerLate       ScalarOptimizerLateEPCallbacks
void registerScalarOptimizerLateEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_CGSCCOptimizerLate        CGSCCOptimizerLateEPCallbacks
void registerCGSCCOptimizerLateEPCallback(const std::function<void(CGSCCPassManager&, OptimizationLevel)>&);
// EP_VectorizerStart           VectorizerStartEPCallbacks
void registerVectorizerStartEPCallback(const std::function<void(FunctionPassManager&, OptimizationLevel)>&);
// EP_EarlyAsPossible           PipelineStartEPCallbacks
void registerPipelineStartEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_ModuleOptimizerEarly      PipelineEarlySimplificationEPCallbacks
void registerPipelineEarlySimplificationEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// 
void registerOptimizerEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
// EP_OptimizerLast             OptimizerLastEPCallbacks
void registerOptimizerLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationEarlyEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);
//
void registerFullLinkTimeOptimizationLastEPCallback(const std::function<void(ModulePassManager&, OptimizationLevel)>&);

使用helloworld测试EP顺序:

  • Debug: PipelineStart -> PipelineEarlySimplification -> OptimizerLast
  • Release: PipelineStart -> PipelineEarlySimplification -> Peephole -> Peephole -> Peephole -> ScalarOptimizerLate -> Peephole -> VectorizerStart -> OptimizerLast

OLLVM

现存OLLVM项目汇总

  之前提到LLVM的架构优于Gcc, 因此可以在其上可以更好的实现编译器层级的代码混淆. 这就是ollvm(obfuscated-llvm). 笔者第一次接触到ollvm是在2017年做漏洞挖掘的时候. 下图为写这篇文章时现存的开源ollvm项目

  瑞士西北应用科技大学安全实验室于2010年6月份发起的一个项目, 支持LLVM3.3-4.0. 之后出现呢的所有ollvm项目均基于该项目开发. 该项目首次提出LLVM方式进行“控制流伪造”(BCF),“控制流平坦化”(FLA),“指令替换”(SUB)的代码混淆方式.

2018年开始的Ollvm项目, 支持LLVM6.0-8.0, 与obfuscator相比增加了如下功能:

  • OC混淆, 混淆Objective-C类名及selector, 防止你优雅的逆向, 看一眼函数就知道干嘛的了
  • 函数包装, 将一个函数调用变成深层嵌套函数, 功能不强, 可以恶心一些小白
  • 字符串加密, 通过异或的方式加密字符串到数据区, 首次使用时解密
  • 间接分支, 将跳转地址改为跳转寄存器, 大大增强FLA难度
  • 拆分基本块, 功能也很强, 但容易产生崩溃
  • 函数调用混淆, 把函数调用改成dlopen+dlsym的动态调用

2022年开始的替代Hikari的项目, Hikari原作者不开发了. 支持LLVM15+, 与Hikari相比增加了如下功能:

  • 支持Swift
  • 反调试
  • 反Hook
  • 常量加密
  • 支持arm64e

2021年开始的Ollvm项目, 支持LLVM14, 与obfuscator相比增加了如下功能:

  • 增强的FLA
  • 增强BCF为“随机控制流”(RCF)
  • 反Angr, Angr是一种利用符号执行动态分析Ollvm的方式
  • MBA(Mixed Boolean-Arithmetic)混淆, 将一个常量表达式用一系列运算代替, 算伪造指令流, 但比花指令高级多了

混合布尔算术(Mixed Boolean Arithmetic)是2007年提出的一种混淆算法, 这种算法由算数运算(例如ADD/SUB/MUL)和布尔运算(例如AND/OR/NOT)的混合使用组成

2019年开始的Ollvm项目, 支持LLVM7/8/9/10, 与obfuscator相比增加了如下功能:

  • 间接调用, 将引用的函数地址变换后存到数据区, 再动态调用
  • 间接全局变量, 将引用的变量地址变换后存到数据区, 再动态调用

2022年开始的基于goron的项目, goron原作者不开发了. 支持LLVM14+, 功能和goron一致

用LLVM实现的VMP, 支持LLVM8, 只支持Debug不支持Release

  • 其他Ollvm

https://github.com/DeNA/DeClang, 用于Swift混淆, 不支持C++
https://github.com/open-obfuscator/o-mvll, 基于LLVMPass, 支持Python, 扩展性较强
https://github.com/25077667/VMPilot, 也是基于LLVM的VMP

  关于基于Ollvm的VMP, iOS上因为有内存保护, 非越狱无法动态更改指令, 因此iOS上的VMP只能沦为IR解释器, 因此其解密难度比Lua/JS等解释器低得多. 所以还不如用跨语言方式去增加整体复杂度.

  就跨平台兼容性和稳定性而言, 原版Ollvm > Hikari > 其他Ollvm, 这里的不稳定指的是编译失败, 或运行时未达到混淆目的, 或运行时因为混淆导致问题; 就功能而言, Hikari > 原版Ollvm, 其他Ollvm各有特色. 可以根据自己实际情况使用

混淆控制方式

简介

  在很多实际项目中, 由于以下原因无法对整个项目完全混淆, 实际操作时, 常常需要根据业务敏感程度使用不同程度的混淆, 比如攻防模块多用一些混淆:

  • 项目较大, 依赖较多, 或使用了很多header-only的库, 混淆了很多不需要混淆的代码, 导致编译出来的二进制过大
  • 项目较大, 依赖较多, 使用了平坦化(或其他方式)混淆了很多不需要混淆的代码, 导致编译极其缓慢, Ollvm比较耗内存
  • 混淆了复杂算法, 导致运行时耗时比正常大很多, 一般使用平坦化后耗时会增加10%以上
  • 混淆过多可能不允许上架AppStore, GooglePlay等

  不同的Ollvm采用的方式大同小异, 无非是以下几种:

  • 对需要混淆的模块单独指定命令行参数, 如-mllvm -fla, 这种方式兼容所有支持llvm命令行参数的编译器前端
  • 使用环境变量指定混淆参数
  • 对需要混淆的函数指定注解, 如__attribute__((__annotate__(("fla"))))(新式语法[[clang::annotate("fla")]]), 这种方式仅支持C/C++, Objective-C和其他语言均不支持
  • 对需要混淆的函数指定标记函数, 如下所示, 这种方式支持Objective-C
extern void hikari_fla(void);
@implementation foo2:NSObject
+(void)foo{
  hikari_fla();
  NSLog(@"FOOOO2");
}
@end
  • 使用配置文件来指定需要混淆的函数和模块, 这种方式兼容所有编译器前端, 用于解决前几种方式搞不定的情况

现存控制方式

  • 命令行参数
static cl::opt<bool> EnableFlattening("enable-cffobf", cl::init(false),
                                      cl::NotHidden,
                                      cl::desc("Enable Flattening."));
  • 函数注解
std::string readAnnotate(Function *f) {
  std::string annotation = "";

  // Get annotation variable
  GlobalVariable *glob =
      f->getParent()->getGlobalVariable("llvm.global.annotations");

  if (glob != NULL) {
    // Get the array
    if (ConstantArray *ca = dyn_cast<ConstantArray>(glob->getInitializer())) {
      for (unsigned i = 0; i < ca->getNumOperands(); ++i) {
        // Get the struct
        if (ConstantStruct *cs = dyn_cast<ConstantStruct>(ca->getOperand(i))) {
          if (ConstantExpr *expr = dyn_cast<ConstantExpr>(cs->getOperand(0))) {
            // If it's a bitcast we can check if the annotation is concerning
            // the current function
            if (expr->getOpcode() == Instruction::BitCast && expr->getOperand(0) == f) {
              ConstantExpr *note = cast<ConstantExpr>(cs->getOperand(1));
              // If it's a GetElementPtr, that means we found the variable
              // containing the annotations
              if (note->getOpcode() == Instruction::GetElementPtr) {
                if (GlobalVariable *annoteStr =
                        dyn_cast<GlobalVariable>(note->getOperand(0))) {
                  if (ConstantDataSequential *data =
                          dyn_cast<ConstantDataSequential>(
                              annoteStr->getInitializer())) {
                    if (data->isString()) {
                      annotation += data->getAsString().lower() + " ";
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
  return annotation;
}
  • 标记函数
bool readFlag(Function *f, std::string attribute) {
  for (inst_iterator I = inst_begin(f); I != inst_end(f); I++) {
    Instruction *Inst = &*I;
    if (CallInst *CI = dyn_cast<CallInst>(Inst)) {
      if (CI->getCalledFunction() != nullptr &&
          CI->getCalledFunction()->getName().contains("hikari_" + attribute)) {
        CI->eraseFromParent();
        return true;
      }
    }
  }
  return false;
}

控制编译器优化

  由于现代编译器的卓越能力, 一些混淆手段很可能在Release下被还原导致实际未混淆, 这种情况下Ollvm项目以Debug编译反而能达到效果, 而笔者认为正确的解决方式为, 在静态区构造特殊数据, 用于关联待混淆常量及函数, 并将特殊数据指定为used避免被优化. 以下是Clang支持的针对函数和变量优化的语法, 对函数关闭优化可以同时防止内联, 对变量关闭优化可以防止其被优化成常量.

  • __attribute__((optnone)) 对函数关闭优化 (如果是Gcc可以指定优化等级)
  • #pragma clang optimize off #pragma clang optimize on 对区间内的函数关闭优化
  • volatile 对变量关闭优化
  • __attribute__((used)) 对全局变量关闭优化

常量加密

Hikari StringEncryption模块分析

  void HandleFunction(Function *Func) {
    FixFunctionConstantExpr(Func);
    set<GlobalVariable *> Globals;
    set<User *> Users;
    for (BasicBlock &BB : *Func) {
      for (Instruction &I : BB) {
        for (Value *Op : I.operands()) {
          if (GlobalVariable *G = dyn_cast<GlobalVariable>(Op->stripPointerCasts())) {
            if(User* U=dyn_cast<User>(Op)){
              Users.insert(U);
            }
            Users.insert(&I);
            Globals.insert(G);
          }
        }
      }
    }
    set<GlobalVariable *> rawStrings;
    set<GlobalVariable *> objCStrings;
    map<GlobalVariable *, pair<Constant *, GlobalVariable *>> GV2Keys;
    map<GlobalVariable * /*old*/, pair<GlobalVariable * /*encrypted*/, GlobalVariable * /*decrypt space*/>> old2new;
    for (GlobalVariable *GV : Globals) {
      if (GV->hasInitializer() &&
          GV->getSection() != StringRef("llvm.metadata") &&
          GV->getSection().find(StringRef("__objc")) == string::npos &&
          GV->getName().find("OBJC") == string::npos) {
        if (GV->getInitializer()->getType() ==
            Func->getParent()->getTypeByName("struct.__NSConstantString_tag")) {
          objCStrings.insert(GV);
          rawStrings.insert(
              cast<GlobalVariable>(cast<ConstantStruct>(GV->getInitializer())
                                       ->getOperand(2)
                                       ->stripPointerCasts()));

        } else if (isa<ConstantDataSequential>(GV->getInitializer())) {
          rawStrings.insert(GV);
        }else if(isa<ConstantArray>(GV->getInitializer())){
           ConstantArray* CA=cast<ConstantArray>(GV->getInitializer());
           for(unsigned i=0;i<CA->getNumOperands();i++){
             Value* op=CA->getOperand(i)->stripPointerCasts();
             if(GlobalVariable* GV=dyn_cast<GlobalVariable>(op)){
               Globals.insert(GV);
             }
           }

        }
      }
    }
    for (GlobalVariable *GV : rawStrings) {
      if (GV->getInitializer()->isZeroValue() ||
          GV->getInitializer()->isNullValue()) {
        continue;
      }
      ConstantDataSequential *CDS =
          cast<ConstantDataSequential>(GV->getInitializer());
      Type *memberType = CDS->getElementType();
      // Ignore non-IntegerType
      if (!isa<IntegerType>(memberType)) {
        continue;
      }
      IntegerType *intType = cast<IntegerType>(memberType);
      Constant *KeyConst = NULL;
      Constant *EncryptedConst = NULL;
      Constant *DummyConst = NULL;
      if (intType == Type::getInt8Ty(GV->getParent()->getContext())) {
        vector<uint8_t> keys;
        vector<uint8_t> encry;
        vector<uint8_t> dummy;
        for (unsigned i = 0; i < CDS->getNumElements(); i++) {
          uint8_t K = cryptoutils->get_uint8_t();
          uint64_t V = CDS->getElementAsInteger(i);
          keys.push_back(K);
          encry.push_back(K ^ V);
          dummy.push_back(rand());
        }
        KeyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                          ArrayRef<uint8_t>(keys));
        EncryptedConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint8_t>(encry));
        DummyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint8_t>(dummy));

      } else if (intType == Type::getInt16Ty(GV->getParent()->getContext())) {
        vector<uint16_t> keys;
        vector<uint16_t> encry;
        vector<uint16_t> dummy;
        for (unsigned i = 0; i < CDS->getNumElements(); i++) {
          uint16_t K = cryptoutils->get_uint16_t();
          uint64_t V = CDS->getElementAsInteger(i);
          keys.push_back(K);
          encry.push_back(K ^ V);
          dummy.push_back(rand());
        }
        KeyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                          ArrayRef<uint16_t>(keys));
        EncryptedConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint16_t>(encry));
        DummyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint16_t>(dummy));
      } else if (intType == Type::getInt32Ty(GV->getParent()->getContext())) {
        vector<uint32_t> keys;
        vector<uint32_t> encry;
        vector<uint32_t> dummy;
        for (unsigned i = 0; i < CDS->getNumElements(); i++) {
          uint32_t K = cryptoutils->get_uint32_t();
          uint64_t V = CDS->getElementAsInteger(i);
          keys.push_back(K);
          encry.push_back(K ^ V);
          dummy.push_back(rand());
        }
        KeyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                          ArrayRef<uint32_t>(keys));
        EncryptedConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint32_t>(encry));
        DummyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint32_t>(dummy));
      } else if (intType == Type::getInt64Ty(GV->getParent()->getContext())) {
        vector<uint64_t> keys;
        vector<uint64_t> encry;
        vector<uint64_t> dummy;
        for (unsigned i = 0; i < CDS->getNumElements(); i++) {
          uint64_t K = cryptoutils->get_uint64_t();
          uint64_t V = CDS->getElementAsInteger(i);
          keys.push_back(K);
          encry.push_back(K ^ V);
          dummy.push_back(rand());
        }
        KeyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                          ArrayRef<uint64_t>(keys));
        EncryptedConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint64_t>(encry));
        DummyConst = ConstantDataArray::get(GV->getParent()->getContext(),
                                                ArrayRef<uint64_t>(dummy));
      } else {
        errs() << "Unsupported CDS Type\n";
        abort();
      }
      // Prepare new rawGV
      GlobalVariable *EncryptedRawGV = new GlobalVariable(
          *(GV->getParent()), EncryptedConst->getType(), false,
          GV->getLinkage(), EncryptedConst, "EncryptedString", nullptr,
          GV->getThreadLocalMode(), GV->getType()->getAddressSpace());
      GlobalVariable *DecryptSpaceGV = new GlobalVariable(
          *(GV->getParent()), DummyConst->getType(), false,
          GV->getLinkage(), DummyConst, "DecryptSpace", nullptr,
          GV->getThreadLocalMode(), GV->getType()->getAddressSpace());
      old2new[GV] = make_pair(EncryptedRawGV, DecryptSpaceGV);
      GV2Keys[DecryptSpaceGV] = make_pair(KeyConst, EncryptedRawGV);
    }
    // Now prepare ObjC new GV
    for (GlobalVariable *GV : objCStrings) {
      ConstantStruct *CS = cast<ConstantStruct>(GV->getInitializer());
      GlobalVariable *oldrawString = cast<GlobalVariable>(CS->getOperand(2)->stripPointerCasts());
      if (old2new.find(oldrawString) == old2new.end()) { // Filter out zero initializers
        continue;
      }
      GlobalVariable *EncryptedOCGV = ObjectivCString(GV, "EncryptedStringObjC", oldrawString, old2new[oldrawString].first, CS);
      GlobalVariable *DecryptSpaceOCGV = ObjectivCString(GV, "DecryptSpaceObjC", oldrawString, old2new[oldrawString].second, CS);
      old2new[GV] = make_pair(EncryptedOCGV, DecryptSpaceOCGV);
    } // End prepare ObjC new GV
    if(old2new.empty() || GV2Keys.empty())
      return;
    // Replace Uses
    for (User *U : Users) {
      for (map<GlobalVariable *, pair<GlobalVariable *, GlobalVariable *>>::iterator iter =
               old2new.begin();
           iter != old2new.end(); ++iter) {
        U->replaceUsesOfWith(iter->first, iter->second.second);
        iter->first->removeDeadConstantUsers();
      }
    } // End Replace Uses
    // CleanUp Old ObjC GVs
    for (GlobalVariable *GV : objCStrings) {
      if (GV->getNumUses() == 0) {
        GV->dropAllReferences();
        old2new.erase(GV);
        GV->eraseFromParent();
      }
    }
    // CleanUp Old Raw GVs
    for (map<GlobalVariable *, pair<GlobalVariable *, GlobalVariable *>>::iterator iter =
             old2new.begin();
         iter != old2new.end(); ++iter) {
      GlobalVariable *toDelete = iter->first;
      toDelete->removeDeadConstantUsers();
      if (toDelete->getNumUses() == 0) {
        toDelete->dropAllReferences();
        toDelete->eraseFromParent();
      }
    }
    GlobalVariable *StatusGV = encstatus[Func];
    /*
      - Split Original EntryPoint BB into A and C.
      - Create new BB as Decryption BB between A and C. Adjust the terminators
      into: A (Alloca a new array containing all)
              |
              B(If not decrypted)
              |
              C
    */
    BasicBlock *A = &(Func->getEntryBlock());
    BasicBlock *C = A->splitBasicBlock(A->getFirstNonPHIOrDbgOrLifetime());
    C->setName("PrecedingBlock");
    BasicBlock *B =
        BasicBlock::Create(Func->getContext(), "StringDecryptionBB", Func, C);
    // Change A's terminator to jump to B
    // We'll add new terminator to jump C later
    BranchInst *newBr = BranchInst::Create(B);
    ReplaceInstWithInst(A->getTerminator(), newBr);
    IRBuilder<> IRB(A->getFirstNonPHIOrDbgOrLifetime());
    // Insert DecryptionCode
    HandleDecryptionBlock(B, C, GV2Keys);
    // Add atomic load checking status in A
    LoadInst *LI = IRB.CreateLoad(StatusGV, "LoadEncryptionStatus");
    LI->setAtomic(AtomicOrdering::Acquire); // Will be released at the start of
                                            // C
    LI->setAlignment(4);
    Value *condition = IRB.CreateICmpEQ(
        LI, ConstantInt::get(Type::getInt32Ty(Func->getContext()), 0));
    A->getTerminator()->eraseFromParent();
    BranchInst::Create(B, C, condition, A);
    // Add StoreInst atomically in C start
    // No matter control flow is coming from A or B, the GVs must be decrypted
    IRBuilder<> IRBC(C->getFirstNonPHIOrDbgOrLifetime());
    StoreInst *SI = IRBC.CreateStore(
        ConstantInt::get(Type::getInt32Ty(Func->getContext()), 1), StatusGV);
    SI->setAlignment(4);
    SI->setAtomic(AtomicOrdering::Release); // Release the lock acquired in LI

  } // End of HandleFunction

  GlobalVariable *ObjectivCString(GlobalVariable *GV, string name, GlobalVariable *oldrawString, GlobalVariable *newString, ConstantStruct *CS) {
      Value *zero = ConstantInt::get(Type::getInt32Ty(GV->getContext()), 0);
      vector<Constant *> vals;
      vals.push_back(CS->getOperand(0));
      vals.push_back(CS->getOperand(1));
      Constant *GEPed = ConstantExpr::getInBoundsGetElementPtr(nullptr, newString, {zero, zero});
      if (GEPed->getType() == CS->getOperand(2)->getType()) {
        vals.push_back(GEPed);
      } else {
        Constant *BitCasted = ConstantExpr::getBitCast(newString, CS->getOperand(2)->getType());
        vals.push_back(BitCasted);
      }
      vals.push_back(CS->getOperand(3));
      Constant *newCS = ConstantStruct::get(CS->getType(), ArrayRef<Constant *>(vals));
      return new GlobalVariable(
          *(GV->getParent()), newCS->getType(), false, GV->getLinkage(), newCS,
          name.c_str(), nullptr, GV->getThreadLocalMode(),
          GV->getType()->getAddressSpace());
  }

  void HandleDecryptionBlock(BasicBlock *B, BasicBlock *C,
                             map<GlobalVariable *, pair<Constant *, GlobalVariable *>> &GV2Keys) {
    IRBuilder<> IRB(B);
    Value *zero = ConstantInt::get(Type::getInt32Ty(B->getContext()), 0);
    for (map<GlobalVariable *, pair<Constant *, GlobalVariable *>>::iterator iter = GV2Keys.begin();
         iter != GV2Keys.end(); ++iter) {
      ConstantDataArray *CastedCDA = cast<ConstantDataArray>(iter->second.first);
      // Prevent optimization of encrypted data
      appendToCompilerUsed(*iter->second.second->getParent(),
                           {iter->second.second});
      // Element-By-Element XOR so the fucking verifier won't complain
      // Also, this hides keys
      for (unsigned i = 0; i < CastedCDA->getType()->getNumElements(); i++) {
        Value *offset = ConstantInt::get(Type::getInt32Ty(B->getContext()), i);
        Value *EncryptedGEP = IRB.CreateGEP(iter->second.second, {zero, offset});
        Value *DecryptedGEP = IRB.CreateGEP(iter->first, {zero, offset});
        LoadInst *LI = IRB.CreateLoad(EncryptedGEP, "EncryptedChar");
        Value *XORed = IRB.CreateXor(LI, CastedCDA->getElementAsConstant(i));
        IRB.CreateStore(XORed, DecryptedGEP);
      }
    }
    IRB.CreateBr(C);
  } // End of HandleDecryptionBlock

未混淆IR

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 @puts(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}
declare i32 @puts(i8*) #1

已混淆IR(-mllvm -enable-strcry)

@0 = private global i32 0
@EncryptedString = private global [6 x i8] c"*d\D2\15-A"
@DecryptSpace = private global [6 x i8] c"\A7\F1\D9*\82\C8"
@llvm.compiler.used = appending global [1 x i8*] [i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 0)], section "llvm.metadata"

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %LoadEncryptionStatus = load atomic i32, i32* @0 acquire, align 4
  %0 = icmp eq i32 %LoadEncryptionStatus, 0
  br i1 %0, label %StringDecryptionBB, label %PrecedingBlock

StringDecryptionBB:                               ; preds = %entry
  %EncryptedChar = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 0)
  %1 = xor i8 %EncryptedChar, 66
  store i8 %1, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 0)
  %EncryptedChar1 = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 1)
  %2 = xor i8 %EncryptedChar1, 1
  store i8 %2, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 1)
  %EncryptedChar2 = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 2)
  %3 = xor i8 %EncryptedChar2, -66
  store i8 %3, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 2)
  %EncryptedChar3 = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 3)
  %4 = xor i8 %EncryptedChar3, 121
  store i8 %4, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 3)
  %EncryptedChar4 = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 4)
  %5 = xor i8 %EncryptedChar4, 66
  store i8 %5, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 4)
  %EncryptedChar5 = load i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @EncryptedString, i32 0, i32 5)
  %6 = xor i8 %EncryptedChar5, 65
  store i8 %6, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 5)
  br label %PrecedingBlock

PrecedingBlock:                                   ; preds = %entry, %StringDecryptionBB
  store atomic i32 1, i32* @0 release, align 4
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %7 = getelementptr inbounds [6 x i8], [6 x i8]* @DecryptSpace, i32 0, i32 0
  %call = call i32 @puts(i8* %7)
  ret i32 0
}

declare i32 @puts(i8*) #1
flowchart LR
A([entry]) --> B([StringDecryptionBB])
A([entry]) --> C([PrecedingBlock])
B([StringDecryptionBB]) --> C([PrecedingBlock])
C([PrecedingBlock]) --> ret([ret])

  Hikari是用异或方式将静态区字符串在编译期加密,并在函数入口处动态解密到预分配的静态区,支持C/OC字符串

  • 因为字符串属于模块范围可操作的元素而非函数, 因此需要注册为ModulePass, 入口点为runOnModule
  • 入口点使用toObfuscate判断是否需要字符串加密, HandleFunction为加密处理函数
  • HandleFunction区分出哪些全局数据是字符串, 以及哪些字符串需要混淆
  • 需要处理编译期优化, 防止静态区数据丢失, 或者混淆逻辑被还原, 混淆和优化其实是2个方向相反的过程

加解密过程:

  • 编译期Hikari将字符串异或加密并存储为可执行模块的静态数据, 预分配解密后的存储区, 对同一字符串的多个引用, Hikari会创建多份加密副本防止冲突
  • 编译期Hikari将异或解密逻辑StringDecryptionBB插入到函数入口点
  • 运行时可执行模块解密静态数据到预分配存储区, 使用LoadEncryptionStatus变量记录是否已解密, 如果未解密则执行StringDecryptionBB否则执行PrecedingBlock

优缺点:

  • 异或加密简单可靠,兼容性较强
  • 异或加密算法较简单;执行一次函数即可在静态内存取获取解密的字符串

Pluto GlobalEncryption模块分析

PreservedAnalyses GlobalEncryption::run(Module &M, ModuleAnalysisManager &AM) {
    std::vector<GlobalVariable *> GVs;
    for (auto &GV : M.getGlobalList()) {
        if (!shouldSkip(GV)) {
            GVs.push_back(&GV);
        }
    }
    for (auto &GV : GVs) {
        if (ConstantDataArray *dataArray = dyn_cast<ConstantDataArray>(GV->getInitializer())) {
            uint64_t eleByteSize = dataArray->getElementByteSize();
            uint64_t eleNum = dataArray->getNumElements();
            const char *data = dataArray->getRawDataValues().data();
            uint64_t dataSize = eleByteSize * eleNum;
            if (data && eleByteSize <= 8) {
                char *dataCopy = new char[dataSize];
                memcpy(dataCopy, data, dataSize);
                uint64_t key = cryptoutils->get_uint64_t();
                // A simple xor encryption
                for (uint32_t i = 0; i < dataSize; i++) {
                    dataCopy[i] ^= ((char *)&key)[i % eleByteSize];
                }
                GV->setInitializer(
                    ConstantDataArray::getRaw(StringRef(dataCopy, dataSize), eleNum, dataArray->getElementType()));
                GV->setConstant(false);
                insertArrayDecryption(M, GV, key, eleNum);
            }
        } else if (ConstantInt *dataInt = dyn_cast<ConstantInt>(GV->getInitializer())) {
            uint64_t key = cryptoutils->get_uint64_t();
            ConstantInt *enc = ConstantInt::get(dataInt->getType(), key ^ dataInt->getZExtValue());
            GV->setInitializer(enc);
            GV->setConstant(false);
            insertIntDecryption(M, GV, key);
        }
    }
    return PreservedAnalyses::all();
}

void GlobalEncryption::insertArrayDecryption(Module &M, GlobalVariable *GV, uint64_t key, uint64_t eleNum) {
    static uint64_t cnt = 0;
    LLVMContext &context = M.getContext();
    FunctionType *funcType = FunctionType::get(Type::getVoidTy(context), false);
    std::string funcName = formatv("decrypt.arr.{0:d}", cnt++);
    FunctionCallee callee = M.getOrInsertFunction(funcName, funcType);
    Function *func = cast<Function>(callee.getCallee());
    func->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);
    BasicBlock *head = BasicBlock::Create(context, "head", func);
    BasicBlock *forCond = BasicBlock::Create(context, "for.cond", func);
    BasicBlock *forBody = BasicBlock::Create(context, "for.body", func);
    BasicBlock *forInc = BasicBlock::Create(context, "for.inc", func);
    BasicBlock *forEnd = BasicBlock::Create(context, "for.inc", func);

    IRBuilder<> builder(context);

    builder.SetInsertPoint(head);
    AllocaInst *indexPtr = builder.CreateAlloca(Type::getInt32Ty(context));
    builder.CreateStore(ConstantInt::get(Type::getInt32Ty(context), 0), indexPtr);
    builder.CreateBr(forCond);

    builder.SetInsertPoint(forCond);
    LoadInst *index = builder.CreateLoad(Type::getInt32Ty(context), indexPtr);
    Value *cond = builder.CreateICmpSLT(index, ConstantInt::get(Type::getInt32Ty(context), eleNum));
    builder.CreateCondBr(cond, forBody, forEnd);

    builder.SetInsertPoint(forBody);

    Value *elePtr = builder.CreateGEP(GV->getValueType(), GV, {ConstantInt::get(Type::getInt32Ty(context), 0), index});
    Type *eleType = cast<ArrayType>(GV->getValueType())->getElementType();
    builder.CreateStore(builder.CreateXor(builder.CreateLoad(eleType, elePtr), ConstantInt::get(eleType, key)), elePtr);
    builder.CreateBr(forInc);

    builder.SetInsertPoint(forInc);
    builder.CreateStore(builder.CreateAdd(index, ConstantInt::get(Type::getInt32Ty(context), 1)), indexPtr);
    builder.CreateBr(forCond);

    builder.SetInsertPoint(forEnd);
    builder.CreateRetVoid();

    appendToGlobalCtors(M, func, 0);
}

void GlobalEncryption::insertIntDecryption(Module &M, GlobalVariable *GV, uint64_t key) {
    static uint64_t cnt = 0;
    LLVMContext &context = M.getContext();
    FunctionType *funcType = FunctionType::get(Type::getVoidTy(context), false);
    std::string funcName = formatv("decrypt.int.{0:d}", cnt++);
    FunctionCallee callee = M.getOrInsertFunction(funcName, funcType);
    Function *func = cast<Function>(callee.getCallee());
    func->setLinkage(GlobalValue::LinkageTypes::PrivateLinkage);

    BasicBlock *BB = BasicBlock::Create(context, "BB", func);

    IRBuilder<> builder(context);
    builder.SetInsertPoint(BB);
    LoadInst *val = builder.CreateLoad(GV->getValueType(), GV);
    builder.CreateStore(builder.CreateXor(val, ConstantInt::get(GV->getValueType(), key)), GV);
    builder.CreateRetVoid();

    appendToGlobalCtors(M, func, 0);
}

未混淆IR

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 @puts(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}
declare i32 @puts(i8*) #1

已混淆IR(-mllvm -passes=gle)

@.str = private unnamed_addr global [6 x i8] c"\11\1C\15\15\16y", align 1
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @decrypt.arr.0, i8* null }]

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 noundef %argc, i8** noundef %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  ret i32 0
}

declare i32 @printf(i8* noundef, ...) #1

define private void @decrypt.arr.0() {
head:
  %0 = alloca i32, align 4
  store i32 0, i32* %0, align 4
  br label %for.cond

for.cond:                                         ; preds = %for.inc, %head
  %1 = load i32, i32* %0, align 4
  %2 = icmp slt i32 %1, 6
  br i1 %2, label %for.body, label %for.inc1

for.body:                                         ; preds = %for.cond
  %3 = getelementptr [6 x i8], [6 x i8]* @.str, i32 0, i32 %1
  %4 = load i8, i8* %3, align 1
  %5 = xor i8 %4, 121
  store i8 %5, i8* %3, align 1
  br label %for.inc

for.inc:                                          ; preds = %for.body
  %6 = add i32 %1, 1
  store i32 %6, i32* %0, align 4
  br label %for.cond

for.inc1:                                         ; preds = %for.cond
  ret void
}

总结:

  • Pluto和Hikari不同在于, 解密逻辑放到ctors段执行而不是每个函数中

goron IndirectGlobalVariable模块分析

bool runOnFunction(Function &Fn) override {
    if (!toObfuscate(flag, &Fn, "indgv")) {
      return false;
    }

    if (Options && Options->skipFunction(Fn.getName())) {
      return false;
    }

    LLVMContext &Ctx = Fn.getContext();

    GVNumbering.clear();
    GlobalVariables.clear();

    LowerConstantExpr(Fn);
    NumberGlobalVariable(Fn);

    if (GlobalVariables.empty()) {
      return false;
    }

    uint32_t V = RandomEngine.get_uint32_t() & ~3;
    ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false);

    const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
    if (IPO) {
      SecretInfo = IPO->getIPOInfo(&Fn);
    }

    Value *MySecret;
    if (SecretInfo) {
      MySecret = SecretInfo->SecretLI;
    } else {
      MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true);
    }

    ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);
    GlobalVariable *GVars = getIndirectGlobalVariables(Fn, EncKey);

    for (inst_iterator I = inst_begin(Fn), E = inst_end(Fn); I != E; ++I) {
      Instruction *Inst = &*I;
      if (PHINode *PHI = dyn_cast<PHINode>(Inst)) {
        for (unsigned int i = 0; i < PHI->getNumIncomingValues(); ++i) {
          Value *val = PHI->getIncomingValue(i);
          if (GlobalVariable *GV = dyn_cast<GlobalVariable>(val)) {
            if (GVNumbering.count(GV) == 0) {
              continue;
            }

            Instruction *IP = PHI->getIncomingBlock(i)->getTerminator();
            IRBuilder<> IRB(IP);

            Value *Idx = ConstantInt::get(Type::getInt32Ty(Ctx), GVNumbering[GV]);
            Value *GEP = IRB.CreateGEP(GVars, {Zero, Idx});
            LoadInst *EncGVAddr = IRB.CreateLoad(GEP, GV->getName());
            Constant *X;
            if (SecretInfo) {
              X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey);
            } else {
              X = ConstantExpr::getSub(Zero, EncKey);
            }

            Value *Secret = IRB.CreateSub(X, MySecret);
            Value *GVAddr = IRB.CreateGEP(EncGVAddr, Secret);
            GVAddr = IRB.CreateBitCast(GVAddr, GV->getType());
            GVAddr->setName("IndGV");
            Inst->replaceUsesOfWith(GV, GVAddr);
          }
        }
      } else {
        for (User::op_iterator op = Inst->op_begin(); op != Inst->op_end(); ++op) {
          if (GlobalVariable *GV = dyn_cast<GlobalVariable>(*op)) {
            if (GVNumbering.count(GV) == 0) {
              continue;
            }

            IRBuilder<> IRB(Inst);
            Value *Idx = ConstantInt::get(Type::getInt32Ty(Ctx), GVNumbering[GV]);
            Value *GEP = IRB.CreateGEP(GVars, {Zero, Idx});
            LoadInst *EncGVAddr = IRB.CreateLoad(GEP, GV->getName());
            Constant *X;
            if (SecretInfo) {
              X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey);
            } else {
              X = ConstantExpr::getSub(Zero, EncKey);
            }

            Value *Secret = IRB.CreateSub(X, MySecret);
            Value *GVAddr = IRB.CreateGEP(EncGVAddr, Secret);
            GVAddr = IRB.CreateBitCast(GVAddr, GV->getType());
            GVAddr->setName("IndGV");
            Inst->replaceUsesOfWith(GV, GVAddr);
          }
        }
      }
    }

      return true;
}

未混淆IR

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp ne i32 %0, 1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

已混淆IR(-mllvm -irobf-indgv)

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
@main_IndirectGVars = private global [1 x i8*] [i8* getelementptr (i8, i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i32 0, i32 0), i32 731375704)]
@llvm.compiler.used = appending global [1 x i8*] [i8* bitcast ([1 x i8*]* @main_IndirectGVars to i8*)], section "llvm.metadata"

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %CallerSlot = alloca i32, align 4
  %CalleeSlot = alloca i32, align 4
  store i32 1941440102, i32* %CallerSlot
  %MySecret = load i32, i32* %CallerSlot
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp ne i32 %0, 1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %.str = load i8*, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @main_IndirectGVars, i32 0, i32 0)
  %1 = sub i32 1210064398, %MySecret
  %2 = getelementptr i8, i8* %.str, i32 %1
  %IndGV = bitcast i8* %2 to [6 x i8]*
  %3 = getelementptr inbounds [6 x i8], [6 x i8]* %IndGV, i64 0, i64 0
  %call = call i32 (i8*, ...) @printf(i8* %3)
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

实验:将静态字符串转换为栈字符串

  以下代码在LLVM8-18下测试, 仅使用NewPass. 注意本节只是为了验证静态转栈的可行性, 不推荐实际使用.

#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/Pass.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#if LLVM_VERSION_MAJOR <= 15
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#endif

using namespace llvm;

#define PASSNAME          "MyPassDemo"

static void doModule(Module& M);

// ---------------- New Pass ---------------- //
#if LLVM_VERSION_MAJOR <= 13
#define OptimizationLevel PassBuilder::OptimizationLevel
#endif

class MyPassDemo : public PassInfoMixin<MyPassDemo> {
public:
    PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
        doModule(M);
        return PreservedAnalyses::all();
    };
    static bool isRequired() { return true; }
};

extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
    return {
        .APIVersion = LLVM_PLUGIN_API_VERSION,
        .PluginName = PASSNAME,
        .PluginVersion = "1.0",
        .RegisterPassBuilderCallbacks = [](PassBuilder &PB) {
            PB.registerPipelineStartEPCallback(
                [](ModulePassManager &MPM
#if LLVM_VERSION_MAJOR >= 12
                , OptimizationLevel Level
#endif
                ) {
                    MPM.addPass(MyPassDemo());
            });
            PB.registerPipelineParsingCallback(
                [](StringRef Name, ModulePassManager& MPM, ArrayRef<PassBuilder::PipelineElement>) {
                    MPM.addPass(MyPassDemo());
                    return true;
            });
        }
    };
}
// ---------------- New Pass ---------------- //
#include <vector>
class TodoItem {
public:
    Instruction*    inst;
    unsigned        idx;
    StringRef       data;     
};

void doModule(Module& M) {   
    std::vector<TodoItem> todo_list;
    auto handle_gv = [&todo_list](Instruction* I, unsigned i, GlobalVariable* GV) {
        if (GV->isConstant() && GV->hasInitializer()) {
            Constant* GVI = GV->getInitializer();
            ConstantDataArray* CDA = dyn_cast<ConstantDataArray>(GVI);
            if (CDA != 0) {
                StringRef data = CDA->getAsString(); // 如果是字符串则包括'\0'
                if (data.size() >= 2) {
                    errs() << "Add todo_list: " << data << "\n";
                    todo_list.push_back({I, i, data});
                }
            }
        }
    };
    for (Function& F : M) {
        for (BasicBlock& bb : F) {
            for (Instruction& I : bb) {
                for (unsigned i = 0; i < I.getNumOperands(); i++) {
                    Value* v = I.getOperand(i);
                    unsigned valueID = v->getValueID();
                    if (valueID == Value::GlobalVariableVal) { // LLVM>=15
                        GlobalVariable* GV = dyn_cast<GlobalVariable>(v);
                        handle_gv(&I, i, GV);
                        // @printf(ptr noundef @.str) -> @printf(ptr noundef %str)
                    } else if (valueID == Value::ConstantExprVal) { // LLVM<=14
                        ConstantExpr* CE = dyn_cast<ConstantExpr>(v);
                        unsigned op = CE->getOpcode();
                        if (op == Instruction::GetElementPtr) {
                            Value* v0 = CE->getOperand(0);
                            Value* v1 = CE->getOperand(1);
                            Value* v2 = CE->getOperand(2);
                            unsigned vID0 = v0->getValueID();
                            unsigned vID1 = v1->getValueID();
                            unsigned vID2 = v2->getValueID();
                            if (vID0 == Value::GlobalVariableVal && vID1 == Value::ConstantIntVal && vID2 == Value::ConstantIntVal ) {
                                if (dyn_cast<ConstantInt>(v1)->getSExtValue() == 0 && dyn_cast<ConstantInt>(v2)->getSExtValue() == 0) {
                                    GlobalVariable* GV = dyn_cast<GlobalVariable>(v0);
                                    handle_gv(&I, 0, GV);
                                    // @printf(i8* noundef getelementptr inbounds ([11 x i8], [11 x i8]* @.str, i64 0, i64 0)) -> @printf(i8* noundef %str)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    for (TodoItem& item : todo_list) {
        Instruction* inst = item.inst;
        unsigned idx = item.idx;
        StringRef data = item.data;     
        BasicBlock* bb = inst->getParent();
        IRBuilder<> IRB(&bb->front());
        AllocaInst* alloca = IRB.CreateAlloca(IRB.getInt8Ty(), IRB.getInt32(data.size()));
        for (unsigned i = 0; i < data.size(); i++) {
            Value* gep = IRB.CreateConstGEP1_64(IRB.getInt8Ty(), alloca, i);
            Constant* n = ConstantInt::get(IRB.getInt8Ty(), data[i]);
            IRB.CreateStore(n, gep);
        }
        inst->setOperand(idx, alloca);
    }
}


测试

// /tmp/1.cpp
#include <stdio.h>
int main(int argc, char** argv) {
	printf("helloworld");
	return 0;
}

编译为Debug

llvm15/build/bin/clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` -arch arm64 -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp 
__text:0000000100007E8C                 SUB             X0, X29, #-var_13 ; char *
__text:0000000100007E90                 MOV             W9, #0x68
__text:0000000100007E94                 STURB           W9, [X29,#var_13]
__text:0000000100007E98                 MOV             W9, #0x65
__text:0000000100007E9C                 STURB           W9, [X29,#var_12]
__text:0000000100007EA0                 MOV             W9, #0x6C
__text:0000000100007EA4                 STURB           W9, [X29,#var_11]
__text:0000000100007EA8                 STURB           W9, [X29,#var_10]
__text:0000000100007EAC                 MOV             W10, #0x6F
__text:0000000100007EB0                 STURB           W10, [X29,#var_F]
__text:0000000100007EB4                 MOV             W11, #0x77
__text:0000000100007EB8                 STURB           W11, [X29,#var_E]
__text:0000000100007EBC                 STURB           W10, [X29,#var_D]
__text:0000000100007EC0                 MOV             W10, #0x72
__text:0000000100007EC4                 STURB           W10, [X29,#var_C]
__text:0000000100007EC8                 STURB           W9, [X29,#var_B]
__text:0000000100007ECC                 MOV             W9, #0x64
__text:0000000100007ED0                 STURB           W9, [X29,#var_A]
__text:0000000100007ED4                 STURB           WZR, [X29,#var_9]
__text:0000000100007ED8                 STR             WZR, [SP,#0x30+var_18]
__text:0000000100007EDC                 STR             W8, [SP,#0x30+var_1C]
__text:0000000100007EE0                 STR             X1, [SP,#0x30+var_28]
__text:0000000100007EE4                 BL              _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v4 = 104;
  v5 = 101;
  v6 = 108;
  v7 = 108;
  v8 = 111;
  v9 = 119;
  v10 = 111;
  v11 = 114;
  v12 = 108;
  v13 = 100;
  printf(&v4);
  return 0;
}

注意:此结果为IDA7.0版生成,如果使用IDA7.7+则识别为strcpy

llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp 
__text:0000000100003F06                 mov     [rbp+var_13], 68h
__text:0000000100003F0A                 mov     [rbp+var_12], 65h
__text:0000000100003F0E                 mov     [rbp+var_11], 6Ch
__text:0000000100003F12                 mov     [rbp+var_10], 6Ch
__text:0000000100003F16                 mov     [rbp+var_F], 6Fh
__text:0000000100003F1A                 mov     [rbp+var_E], 77h
__text:0000000100003F1E                 mov     [rbp+var_D], 6Fh
__text:0000000100003F22                 mov     [rbp+var_C], 72h
__text:0000000100003F26                 mov     [rbp+var_B], 6Ch
__text:0000000100003F2A                 mov     [rbp+var_A], 64h
__text:0000000100003F2E                 mov     [rbp+var_9], 0
__text:0000000100003F32                 mov     [rbp+var_18], 0
__text:0000000100003F39                 mov     [rbp+var_1C], edi
__text:0000000100003F3C                 mov     [rbp+var_28], rsi
__text:0000000100003F40                 lea     rdi, [rbp+var_13] ; char *
__text:0000000100003F44                 mov     al, 0
__text:0000000100003F46                 call    _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v4 = 104;
  v5 = 101;
  v6 = 108;
  v7 = 108;
  v8 = 111;
  v9 = 119;
  v10 = 111;
  v11 = 114;
  v12 = 108;
  v13 = 100;
  v14 = 0;
  printf(&v4, argv, envp);
  result = __stack_chk_guard;
  if ( __stack_chk_guard == v15 )
    result = 0;
  return result;
}

编译为Release

llvm15/build/bin/clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` -arch arm64 -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp -O3
__text:0000000100007ED8                 LDR             D0, =0x726F776F6C6C6568
__text:0000000100007EDC                 STR             D0, [SP,#0x20+var_18]
__text:0000000100007EE0                 MOV             W8, #0x646C
__text:0000000100007EE4                 STRH            W8, [SP,#0x20+var_10]
__text:0000000100007EE8                 STRB            WZR, [SP,#0x20+var_E]
__text:0000000100007EEC                 ADD             X0, SP, #0x20+var_18 ; char *
__text:0000000100007EF0                 BL              _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  strcpy(v4, "helloworld");
  result = printf(v4, argv, envp);
  if ( __stack_chk_guard == v5 )
    result = 0;
  return result;
}

注意:此时IDA已经把上述指令集识别成内部函数的strcpy, 这里strcpy非动态库里的那个函数

llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp -O3
__text:0000000100003F46                 mov     rax, 726F776F6C6C6568h
__text:0000000100003F50                 mov     qword ptr [rbp+var_18], rax
__text:0000000100003F54                 mov     [rbp+var_10], 646Ch
__text:0000000100003F5A                 mov     [rbp+var_E], 0
__text:0000000100003F5E                 lea     rdi, [rbp+var_18] ; char *
__text:0000000100003F62                 xor     eax, eax
__text:0000000100003F64                 call    _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  strcpy(v4, "helloworld");
  printf(v4, argv, envp);
  if ( __stack_chk_guard != v5 )
    __stack_chk_fail();
  return 0;
}

介入时机指定为EP_OptimizerLast,编译为Release

llvm15/build/bin/clang -isysroot `xcrun --sdk iphoneos --show-sdk-path` -arch arm64 -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp -O3
__text:0000000100007ED4                 MOV             X8, #0x6568
__text:0000000100007ED8                 MOVK            X8, #0x6C6C,LSL#16
__text:0000000100007EDC                 MOVK            X8, #0x776F,LSL#32
__text:0000000100007EE0                 MOVK            X8, #0x726F,LSL#48
__text:0000000100007EE4                 STUR            X8, [SP,#0x20+var_13]
__text:0000000100007EE8                 MOV             W8, #0x646C
__text:0000000100007EEC                 STURH           W8, [SP,#0x20+var_B]
__text:0000000100007EF0                 STRB            WZR, [SP,#0x20+var_9]
__text:0000000100007EF4                 ADD             X0, SP, #0x20+var_13 ; char *
__text:0000000100007EF8                 BL              _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  strcpy(v4, "helloworld");
  result = printf(v4, argv, envp);
  if ( __stack_chk_guard == v5 )
    result = 0;
  return result;
}
llvm15/build/bin/clang -isysroot `xcrun --sdk macosx --show-sdk-path` -fpass-plugin=build/MyPassDemo15.dylib -o /tmp/1.bin /tmp/1.cpp -O3
__text:0000000100003F46                 mov     rax, 726F776F6C6C6568h
__text:0000000100003F50                 mov     qword ptr [rbp+var_13], rax
__text:0000000100003F54                 mov     [rbp+var_B], 646Ch
__text:0000000100003F5A                 mov     [rbp+var_9], 0
__text:0000000100003F5E                 lea     rdi, [rbp+var_13] ; char *
__text:0000000100003F62                 xor     eax, eax
__text:0000000100003F64                 call    _printf
// IDA伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  strcpy(v4, "helloworld");
  printf(v4, argv, envp);
  if ( __stack_chk_guard != v5 )
    __stack_chk_fail();
  return 0;
}

控制流级别的混淆

Hikari SplitBasicBlock模块分析

void SplitBasicBlock::split(Function *f) {
  std::vector<BasicBlock *> origBB;
  int splitN = SplitNum;

  // Save all basic blocks
  for (Function::iterator I = f->begin(), IE = f->end(); I != IE; ++I) {
    origBB.push_back(&*I);
  }

  for (std::vector<BasicBlock *>::iterator I = origBB.begin(),
                                           IE = origBB.end();
       I != IE; ++I) {
    BasicBlock *curr = *I;

    // No need to split a 1 inst bb
    // Or ones containing a PHI node
    if (curr->size() < 2 || containsPHI(curr)) {
      continue;
    }

    // Check splitN and current BB size
    if ((size_t)splitN > curr->size()) {
      splitN = curr->size() - 1;
    }

    // Generate splits point
    std::vector<int> test;
    for (unsigned i = 1; i < curr->size(); ++i) {
      test.push_back(i);
    }

    // Shuffle
    if (test.size() != 1) {
      shuffle(test);
      std::sort(test.begin(), test.begin() + splitN);
    }

    // Split
    BasicBlock::iterator it = curr->begin();
    BasicBlock *toSplit = curr;
    int last = 0;
    for (int i = 0; i < splitN; ++i) {
      for (int j = 0; j < test[i] - last; ++j) {
        ++it;
      }
      last = test[i];
      if (toSplit->size() < 2)
        continue;
      toSplit = toSplit->splitBasicBlock(it, toSplit->getName() + ".split");
    }

    ++Split;
  }
}

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 0
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}
flowchart LR
A([entry]) --> B([if.then])
A([entry]) --> C([if.end])
B([if.then]) --> C([if.end])
C([if.end]) --> ret([ret])

已混淆IR(-mllvm -enable-splitobf)

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  br label %entry.split

entry.split:                                      ; preds = %entry
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  br label %entry.split.split

entry.split.split:                                ; preds = %entry.split
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 0
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry.split.split
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  br label %if.then.split

if.then.split:                                    ; preds = %if.then
  br label %if.end

if.end:                                           ; preds = %if.then.split, %entry.split.split
  ret i32 0
}
flowchart LR
A([entry]) --> B([entry.split])
B([entry.split]) --> C([entry.split.split])
C([entry.split.split]) --> D([if.then])
C([entry.split.split]) --> E([if.end])
D([if.then]) --> F([if.then.split])
F([if.then.split]) --> E([if.end])
E([if.end]) --> ret([ret])

  split是BasicBlock层的混淆,可以将函数的基本块随机划分为若干个更小的基本块,单独使用并无作用,但可用于增强其他基于块的混淆方式(如fla)

Hikari Flattening模块分析

bool Flattening::flatten(Function *f) {
  vector<BasicBlock *> origBB;
  BasicBlock *loopEntry;
  BasicBlock *loopEnd;
  LoadInst *load;
  SwitchInst *switchI;
  AllocaInst *switchVar;

  // SCRAMBLER
  std::map<uint32_t,uint32_t> scrambling_key;
  // END OF SCRAMBLER

  // Lower switch
  FunctionPass *lower = createLowerSwitchPass();
  lower->runOnFunction(*f);

  // Save all original BB
  for (Function::iterator i = f->begin(); i != f->end(); ++i) {
    BasicBlock *tmp = &*i;
    if (tmp->isEHPad() || tmp->isLandingPad()) {
          errs()<<f->getName()<<" Contains Exception Handing Instructions and is unsupported for flattening in the open-source version of Hikari.\n";
          return false;
    }
    origBB.push_back(tmp);

    BasicBlock *bb = &*i;
    if (!isa<BranchInst>(bb->getTerminator()) && !isa<ReturnInst>(bb->getTerminator())) {
      return false;
    }
  }

  // Nothing to flatten
  if (origBB.size() <= 1) {
    return false;
  }

  // Remove first BB
  origBB.erase(origBB.begin());

  // Get a pointer on the first BB
  Function::iterator tmp = f->begin(); //++tmp;
  BasicBlock *insert = &*tmp;

  // If main begin with an if
  BranchInst *br = NULL;
  if (isa<BranchInst>(insert->getTerminator())) {
    br = cast<BranchInst>(insert->getTerminator());
  }

  if ((br != NULL && br->isConditional()) ||
      insert->getTerminator()->getNumSuccessors() > 1) {
    BasicBlock::iterator i = insert->end();
    --i;

    if (insert->size() > 1) {
      --i;
    }

    BasicBlock *tmpBB = insert->splitBasicBlock(i, "first");
    origBB.insert(origBB.begin(), tmpBB);
  }

  // Remove jump
  Instruction* oldTerm=insert->getTerminator();

  // Create switch variable and set as it
  switchVar =
      new AllocaInst(Type::getInt32Ty(f->getContext()), 0, "switchVar",oldTerm);
  oldTerm->eraseFromParent();
  new StoreInst(
      ConstantInt::get(Type::getInt32Ty(f->getContext()),
                       llvm::cryptoutils->scramble32(0, scrambling_key)),
      switchVar, insert);

  // Create main loop
  loopEntry = BasicBlock::Create(f->getContext(), "loopEntry", f, insert);
  loopEnd = BasicBlock::Create(f->getContext(), "loopEnd", f, insert);

  load = new LoadInst(switchVar, "switchVar", loopEntry);

  // Move first BB on top
  insert->moveBefore(loopEntry);
  BranchInst::Create(loopEntry, insert);

  // loopEnd jump to loopEntry
  BranchInst::Create(loopEntry, loopEnd);

  BasicBlock *swDefault =
      BasicBlock::Create(f->getContext(), "switchDefault", f, loopEnd);
  BranchInst::Create(loopEnd, swDefault);

  // Create switch instruction itself and set condition
  switchI = SwitchInst::Create(&*f->begin(), swDefault, 0, loopEntry);
  switchI->setCondition(load);

  // Remove branch jump from 1st BB and make a jump to the while
  f->begin()->getTerminator()->eraseFromParent();

  BranchInst::Create(loopEntry, &*f->begin());

  // Put all BB in the switch
  for (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end();
       ++b) {
    BasicBlock *i = *b;
    ConstantInt *numCase = NULL;

    // Move the BB inside the switch (only visual, no code logic)
    i->moveBefore(loopEnd);

    // Add case to switch
    numCase = cast<ConstantInt>(ConstantInt::get(
        switchI->getCondition()->getType(),
        llvm::cryptoutils->scramble32(switchI->getNumCases(), scrambling_key)));
    switchI->addCase(numCase, i);
  }

  // Recalculate switchVar
  for (vector<BasicBlock *>::iterator b = origBB.begin(); b != origBB.end();
       ++b) {
    BasicBlock *i = *b;
    ConstantInt *numCase = NULL;

    // Ret BB
    if (i->getTerminator()->getNumSuccessors() == 0) {
      continue;
    }

    // If it's a non-conditional jump
    if (i->getTerminator()->getNumSuccessors() == 1) {
      // Get successor and delete terminator
      BasicBlock *succ = i->getTerminator()->getSuccessor(0);
      i->getTerminator()->eraseFromParent();

      // Get next case
      numCase = switchI->findCaseDest(succ);

      // If next case == default case (switchDefault)
      if (numCase == NULL) {
        numCase = cast<ConstantInt>(
            ConstantInt::get(switchI->getCondition()->getType(),
                             llvm::cryptoutils->scramble32(
                                 switchI->getNumCases() - 1, scrambling_key)));
      }

      // Update switchVar and jump to the end of loop
      new StoreInst(numCase, load->getPointerOperand(), i);
      BranchInst::Create(loopEnd, i);
      continue;
    }

    // If it's a conditional jump
    if (i->getTerminator()->getNumSuccessors() == 2) {
      // Get next cases
      ConstantInt *numCaseTrue =
          switchI->findCaseDest(i->getTerminator()->getSuccessor(0));
      ConstantInt *numCaseFalse =
          switchI->findCaseDest(i->getTerminator()->getSuccessor(1));

      // Check if next case == default case (switchDefault)
      if (numCaseTrue == NULL) {
        numCaseTrue = cast<ConstantInt>(
            ConstantInt::get(switchI->getCondition()->getType(),
                             llvm::cryptoutils->scramble32(
                                 switchI->getNumCases() - 1, scrambling_key)));
      }

      if (numCaseFalse == NULL) {
        numCaseFalse = cast<ConstantInt>(
            ConstantInt::get(switchI->getCondition()->getType(),
                             llvm::cryptoutils->scramble32(
                                 switchI->getNumCases() - 1, scrambling_key)));
      }

      // Create a SelectInst
      BranchInst *br = cast<BranchInst>(i->getTerminator());
      SelectInst *sel =
          SelectInst::Create(br->getCondition(), numCaseTrue, numCaseFalse, "",
                             i->getTerminator());

      // Erase terminator
      i->getTerminator()->eraseFromParent();
      // Update switchVar and jump to the end of loop
      new StoreInst(sel, load->getPointerOperand(), i);
      BranchInst::Create(loopEnd, i);
      continue;
    }
  }
  errs()<<"Fixing Stack\n";
  fixStack(f);
  errs()<<"Fixed Stack\n";
  return true;
}

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call = call i32 @puts(i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i32 0, i32 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}
flowchart LR
A([entry]) --> B([if.then])
A([entry]) --> C([if.end])
B([if.then]) --> C([if.end])
C([if.end]) --> ret([ret])

已混淆IR(-mllvm -enable-cffobf)

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %.reg2mem = alloca i32
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  store i32 %0, i32* %.reg2mem
  %switchVar = alloca i32
  store i32 175153184, i32* %switchVar
  br label %loopEntry

loopEntry:                                        ; preds = %entry, %loopEnd
  %switchVar1 = load i32, i32* %switchVar
  switch i32 %switchVar1, label %switchDefault [
    i32 175153184, label %first
    i32 -210782820, label %if.then
    i32 165052502, label %if.end
  ]

switchDefault:                                    ; preds = %loopEntry
  br label %loopEnd

first:                                            ; preds = %loopEntry
  %.reload = load volatile i32, i32* %.reg2mem
  %cmp = icmp eq i32 %.reload, 1
  %1 = select i1 %cmp, i32 -210782820, i32 165052502
  store i32 %1, i32* %switchVar
  br label %loopEnd

if.then:                                          ; preds = %loopEntry
  %call = call i32 @puts(i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str, i32 0, i32 0))
  store i32 165052502, i32* %switchVar
  br label %loopEnd

if.end:                                           ; preds = %loopEntry
  ret i32 0

loopEnd:                                          ; preds = %if.then, %first, %switchDefault
  br label %loopEntry
}
flowchart LR
A([entry]) --> B([loopEntry])
B([loopEntry]) --> C([first])
B([loopEntry]) --> D([if.then])
B([loopEntry]) --> G([if.end])
B([loopEntry]) --> E([switchDefault])
C([first]) --> F([loopEnd])
D([if.then]) --> F([loopEnd])
E([switchDefault]) --> F([loopEnd])
F([loopEnd]) --> B([loopEntry])
G([if.end]) --> ret([ret])

  fla是基于BasicBlock的混淆,可以将顺序执行的基本块转换为循环结构,使之平坦化

Pluto Flattening模块分析

PreservedAnalyses Pluto::Flattening::run(Function &F, FunctionAnalysisManager &AM) {
    auto &context = F.getContext();
    IRBuilder<> builder(context);

    // No need to do flattening if only there is only one block
    if (F.size() <= 1) {
        return PreservedAnalyses::all();
    }

    vector<BasicBlock *> origBB;

    for (BasicBlock &BB : F) {
        origBB.push_back(&BB);
    }

    BasicBlock &entryBB = F.getEntryBlock();
    origBB.erase(origBB.begin());

    // If the entry block ends with a conditional branch, seperate it as a new block
    if (entryBB.getTerminator()->getNumSuccessors() > 1) {
        BasicBlock *newBB = entryBB.splitBasicBlock(entryBB.getTerminator(), "newBB");
        origBB.insert(origBB.begin(), newBB);
    }

    // This is for register demotion
    // The return value of the invoke instruction will be store into stack in the bridge block
    for (BasicBlock *BB : origBB) {
        if (InvokeInst *invoke = dyn_cast_or_null<InvokeInst>(BB->getTerminator())) {
            BasicBlock *bridgeBB = BasicBlock::Create(context, "bridgeBB", &F, invoke->getNormalDest());
            invoke->getNormalDest()->replacePhiUsesWith(BB, bridgeBB);
            builder.SetInsertPoint(bridgeBB);
            builder.CreateBr(invoke->getNormalDest());
            invoke->setNormalDest(bridgeBB);
        }
    }

    // Create the dispatch block and return block
    BasicBlock *dispatchBB = BasicBlock::Create(context, "dispatchBB", &F, &entryBB);
    BasicBlock *returnBB = BasicBlock::Create(context, "returnBB", &F, &entryBB);
    BranchInst::Create(dispatchBB, returnBB);
    entryBB.moveBefore(dispatchBB);

    // Make the entry block go to the dispatchBB directly
    entryBB.getTerminator()->eraseFromParent();
    BranchInst *brDispatchBB = BranchInst::Create(dispatchBB, &entryBB);

    builder.SetInsertPoint(brDispatchBB);

    // Now insert an alloca and a store instruction at the end of entry block, and initialize the switch variable with
    // a random value.
    uint32_t randNum = cryptoutils->get_uint32_t();
    AllocaInst *swVarPtr = builder.CreateAlloca(Type::getInt32Ty(context), 0, "swVar.ptr");
    builder.CreateStore(ConstantInt::get(Type::getInt32Ty(context), randNum), swVarPtr);

    // Insert a load instruction at the end of dispatch block
    builder.SetInsertPoint(dispatchBB);
    LoadInst *swVar = builder.CreateLoad(Type::getInt32Ty(context), swVarPtr, false, "swVar");

    // Insert a switch instruction to dispatch blocks
    BasicBlock *swDefault = BasicBlock::Create(context, "swDefault", &F, returnBB);
    builder.SetInsertPoint(swDefault);
    builder.CreateBr(returnBB);
    builder.SetInsertPoint(dispatchBB);
    SwitchInst *swInst = builder.CreateSwitch(swVar, swDefault, origBB.size());

    // Record used random numbers to avoid depulicate case values in switch
    std::set<uint32_t> usedNum;

    // Insert original basic blocks before return block and assign a random case value for each one.
    for (BasicBlock *BB : origBB) {
        // Do not add error handling blocks into switch
        if (BB->isEHPad()) {
            continue;
        }
        usedNum.insert(randNum);
        BB->moveBefore(returnBB);
        swInst->addCase(ConstantInt::get(Type::getInt32Ty(context), randNum), BB);
        do {
            randNum = cryptoutils->get_uint32_t();
        } while (find(usedNum.begin(), usedNum.end(), randNum) != usedNum.end());
    }

    // Insert a store instruction at the end of each block to modify swVar, and make them jump back to dispatch block.
    for (BasicBlock *BB : origBB) {
        // Skip blocks with no successor
        if (BB->getTerminator()->getNumSuccessors() == 0) {
            continue;
        }
        // Branch
        else if (BB->getTerminator()->getNumSuccessors() == 1) {
            BasicBlock *sucBB = BB->getTerminator()->getSuccessor(0);
            ConstantInt *numCase = swInst->findCaseDest(sucBB);
            if (numCase) {
                if (BranchInst *br = dyn_cast_or_null<BranchInst>(BB->getTerminator())) {
                    BB->getTerminator()->eraseFromParent();
                    builder.SetInsertPoint(BB);
                    builder.CreateStore(numCase, swVarPtr);
                    builder.CreateBr(returnBB);
                }
            }
        }
        // Conditional branch
        else if (BB->getTerminator()->getNumSuccessors() == 2) {
            ConstantInt *numIfTrue = swInst->findCaseDest(BB->getTerminator()->getSuccessor(0));
            ConstantInt *numIfFalse = swInst->findCaseDest(BB->getTerminator()->getSuccessor(1));
            if (numIfTrue && numIfFalse) {
                if (BranchInst *br = dyn_cast_or_null<BranchInst>(BB->getTerminator())) {
                    Value *cond = br->getCondition();
                    builder.SetInsertPoint(BB);
                    builder.CreateStore(builder.CreateSelect(cond, numIfTrue, numIfFalse), swVarPtr);
                    builder.CreateBr(returnBB);
                    br->eraseFromParent();
                }
            }
        }
    }
    fixVariables(F);
    return PreservedAnalyses::none();
}

已混淆IR(-mllvm -passes=fla)

define i32 @main(i32 noundef %argc, i8** noundef %argv) #0 {
entry:
  %cmp.reg2mem = alloca i1, align 1
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp ne i32 %0, 1
  store i1 %cmp, i1* %cmp.reg2mem, align 1
  %swVar.ptr = alloca i32, align 4
  store i32 99869029, i32* %swVar.ptr, align 4
  br label %dispatchBB

dispatchBB:                                       ; preds = %entry, %returnBB
  %swVar = load i32, i32* %swVar.ptr, align 4
  switch i32 %swVar, label %swDefault [
    i32 99869029, label %newBB
    i32 1698805515, label %if.then
    i32 -335309019, label %if.end
  ]

swDefault:                                        ; preds = %dispatchBB
  br label %returnBB

newBB:                                            ; preds = %dispatchBB
  %cmp.reload = load volatile i1, i1* %cmp.reg2mem, align 1
  %1 = select i1 %cmp.reload, i32 1698805515, i32 -335309019
  store i32 %1, i32* %swVar.ptr, align 4
  br label %returnBB

if.then:                                          ; preds = %dispatchBB
  %call = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  store i32 -335309019, i32* %swVar.ptr, align 4
  br label %returnBB

if.end:                                           ; preds = %dispatchBB
  ret i32 0

returnBB:                                         ; preds = %if.then, %newBB, %swDefault
  br label %dispatchBB
}
flowchart LR
A([entry]) --> B([dispatchBB])
B([dispatchBB]) --> C([swDefault])
B([dispatchBB]) --> D([newBB])
B([dispatchBB]) --> E([if.then])
B([dispatchBB]) --> F([if.end])
C([swDefault]) --> G([returnBB])
D([newBB]) --> G([returnBB])
E([if.then]) --> G([returnBB])
F([if.end]) --> H([ret])
G([returnBB]) --> B([dispatchBB])

Hikari BogusControlFlow模块分析

  void bogus(Function &F) {
    // For statistics and debug
    int NumBasicBlocks = 0;
    bool firstTime = true; // First time we do the loop in this function
    bool hasBeenModified = false;
    DEBUG_WITH_TYPE("opt", errs() << "bcf: Started on function " << F.getName()
                                  << "\n");
    DEBUG_WITH_TYPE("opt",
                    errs() << "bcf: Probability rate: " << ObfProbRate << "\n");
    if (ObfProbRate < 0 || ObfProbRate > 100) {
      DEBUG_WITH_TYPE("opt", errs()
                                 << "bcf: Incorrect value,"
                                 << " probability rate set to default value: "
                                 << defaultObfRate << " \n");
      ObfProbRate = defaultObfRate;
    }
    DEBUG_WITH_TYPE("opt", errs()
                               << "bcf: How many times: " << ObfTimes << "\n");
    if (ObfTimes <= 0) {
      DEBUG_WITH_TYPE("opt", errs()
                                 << "bcf: Incorrect value,"
                                 << " must be greater than 1. Set to default: "
                                 << defaultObfTime << " \n");
      ObfTimes = defaultObfTime;
    }
    int NumObfTimes = ObfTimes;

    // Real begining of the pass
    // Loop for the number of time we run the pass on the function
    do {
      DEBUG_WITH_TYPE("cfg", errs() << "bcf: Function " << F.getName()
                                    << ", before the pass:\n");
      DEBUG_WITH_TYPE("cfg", F.viewCFG());
      // Put all the function's block in a list
      std::list<BasicBlock *> basicBlocks;
      for (Function::iterator i = F.begin(); i != F.end(); ++i) {
        BasicBlock *BB = &*i;
        if (!BB->isEHPad() && !BB->isLandingPad()) {
          basicBlocks.push_back(BB);
        }
      }
      DEBUG_WITH_TYPE(
          "gen", errs() << "bcf: Iterating on the Function's Basic Blocks\n");

      while (!basicBlocks.empty()) {
        NumBasicBlocks++;
        // Basic Blocks' selection
        if ((int)llvm::cryptoutils->get_range(100) <= ObfProbRate) {
          DEBUG_WITH_TYPE("opt", errs() << "bcf: Block " << NumBasicBlocks
                                        << " selected. \n");
          hasBeenModified = true;
          // Add bogus flow to the given Basic Block (see description)
          BasicBlock *basicBlock = basicBlocks.front();
          addBogusFlow(basicBlock, F);
        } else {
          DEBUG_WITH_TYPE("opt", errs() << "bcf: Block " << NumBasicBlocks
                                        << " not selected.\n");
        }
        // remove the block from the list
        basicBlocks.pop_front();
      } // end of while(!basicBlocks.empty())
      DEBUG_WITH_TYPE("gen",
                      errs() << "bcf: End of function " << F.getName() << "\n");
      if (hasBeenModified) { // if the function has been modified
        DEBUG_WITH_TYPE("cfg", errs() << "bcf: Function " << F.getName()
                                      << ", after the pass: \n");
        DEBUG_WITH_TYPE("cfg", F.viewCFG());
      } else {
        DEBUG_WITH_TYPE("cfg", errs()
                                   << "bcf: Function's not been modified \n");
      }
      firstTime = false;
    } while (--NumObfTimes > 0);
  }

  /* addBogusFlow
   *
   * Add bogus flow to a given basic block, according to the header's
   * description
   */
  virtual void addBogusFlow(BasicBlock *basicBlock, Function &F) {

    // Split the block: first part with only the phi nodes and debug info and
    // terminator
    //                  created by splitBasicBlock. (-> No instruction)
    //                  Second part with every instructions from the original
    //                  block
    // We do this way, so we don't have to adjust all the phi nodes, metadatas
    // and so on for the first block. We have to let the phi nodes in the first
    // part, because they actually are updated in the second part according to
    // them.
    BasicBlock::iterator i1 = basicBlock->begin();
    if (basicBlock->getFirstNonPHIOrDbgOrLifetime())
      i1 = (BasicBlock::iterator)basicBlock->getFirstNonPHIOrDbgOrLifetime();
    Twine *var;
    var = new Twine("originalBB");
    BasicBlock *originalBB = basicBlock->splitBasicBlock(i1, *var);
    DEBUG_WITH_TYPE("gen", errs()
                               << "bcf: First and original basic blocks: ok\n");

    // Creating the altered basic block on which the first basicBlock will jump
    Twine *var3 = new Twine("alteredBB");
    BasicBlock *alteredBB = createAlteredBasicBlock(originalBB, *var3, &F);
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Altered basic block: ok\n");

    // Now that all the blocks are created,
    // we modify the terminators to adjust the control flow.

    alteredBB->getTerminator()->eraseFromParent();
    basicBlock->getTerminator()->eraseFromParent();
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Terminator removed from the altered"
                                  << " and first basic blocks\n");

    // Preparing a condition..
    // For now, the condition is an always true comparaison between 2 float
    // This will be complicated after the pass (in doFinalization())

    // We need to use ConstantInt instead of ConstantFP as ConstantFP results in strange dead-loop
    // when injected into Xcode
    Value *LHS = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1);
    Value *RHS = ConstantInt::get(Type::getInt32Ty(F.getContext()), 1);
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Value LHS and RHS created\n");

    // The always true condition. End of the first block
    ICmpInst *condition =
        new ICmpInst(*basicBlock, ICmpInst::ICMP_EQ, LHS, RHS,"BCFPlaceHolderPred");
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Always true condition created\n");

    // Jump to the original basic block if the condition is true or
    // to the altered block if false.
    BranchInst::Create(originalBB, alteredBB, (Value *)condition, basicBlock);
    DEBUG_WITH_TYPE(
        "gen",
        errs() << "bcf: Terminator instruction in first basic block: ok\n");

    // The altered block loop back on the original one.
    BranchInst::Create(originalBB, alteredBB);
    DEBUG_WITH_TYPE(
        "gen", errs() << "bcf: Terminator instruction in altered block: ok\n");

    // The end of the originalBB is modified to give the impression that
    // sometimes it continues in the loop, and sometimes it return the desired
    // value (of course it's always true, so it always use the original
    // terminator..
    //  but this will be obfuscated too;) )

    // iterate on instruction just before the terminator of the originalBB
    BasicBlock::iterator i = originalBB->end();

    // Split at this point (we only want the terminator in the second part)
    Twine *var5 = new Twine("originalBBpart2");
    BasicBlock *originalBBpart2 = originalBB->splitBasicBlock(--i, *var5);
    DEBUG_WITH_TYPE("gen",
                    errs() << "bcf: Terminator part of the original basic block"
                           << " is isolated\n");
    // the first part go either on the return statement or on the begining
    // of the altered block.. So we erase the terminator created when splitting.
    originalBB->getTerminator()->eraseFromParent();
    // We add at the end a new always true condition
    ICmpInst *condition2 =
        new ICmpInst(*originalBB, CmpInst::ICMP_EQ, LHS, RHS,"BCFPlaceHolderPred");
    // BranchInst::Create(originalBBpart2, alteredBB, (Value
    // *)condition2,originalBB);  Do random behavior to avoid pattern
    // recognition This is achieved by jumping to a random BB
    switch (llvm::cryptoutils->get_uint16_t() % 2) {
    case 0: {
      BranchInst::Create(originalBBpart2, originalBB, condition2, originalBB);
      break;
    }
    case 1: {
      BranchInst::Create(originalBBpart2, alteredBB, condition2, originalBB);
      break;
    }
    default: {
      BranchInst::Create(originalBBpart2, originalBB, condition2, originalBB);
      break;
    }
    }
    DEBUG_WITH_TYPE("gen", errs()
                               << "bcf: Terminator original basic block: ok\n");
    DEBUG_WITH_TYPE("gen", errs() << "bcf: End of addBogusFlow().\n");

  } // end of addBogusFlow()

  /* createAlteredBasicBlock
   *
   * This function return a basic block similar to a given one.
   * It's inserted just after the given basic block.
   * The instructions are similar but junk instructions are added between
   * the cloned one. The cloned instructions' phi nodes, metadatas, uses and
   * debug locations are adjusted to fit in the cloned basic block and
   * behave nicely.
   */
  virtual BasicBlock *createAlteredBasicBlock(BasicBlock *basicBlock,
                                              const Twine &Name = "gen",
                                              Function *F = 0) {
    // Useful to remap the informations concerning instructions.
    ValueToValueMapTy VMap;
    BasicBlock *alteredBB = llvm::CloneBasicBlock(basicBlock, VMap, Name, F);
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Original basic block cloned\n");
    // Remap operands.
    BasicBlock::iterator ji = basicBlock->begin();
    for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end();
         i != e; ++i) {
      // Loop over the operands of the instruction
      for (User::op_iterator opi = i->op_begin(), ope = i->op_end(); opi != ope;
           ++opi) {
        // get the value for the operand
        Value *v = MapValue(*opi, VMap, RF_None, 0);
        if (v != 0) {
          *opi = v;
          DEBUG_WITH_TYPE("gen",
                          errs() << "bcf: Value's operand has been setted\n");
        }
      }
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Operands remapped\n");
      // Remap phi nodes' incoming blocks.
      if (PHINode *pn = dyn_cast<PHINode>(i)) {
        for (unsigned j = 0, e = pn->getNumIncomingValues(); j != e; ++j) {
          Value *v = MapValue(pn->getIncomingBlock(j), VMap, RF_None, 0);
          if (v != 0) {
            pn->setIncomingBlock(j, cast<BasicBlock>(v));
          }
        }
      }
      DEBUG_WITH_TYPE("gen", errs() << "bcf: PHINodes remapped\n");
      // Remap attached metadata.
      SmallVector<std::pair<unsigned, MDNode *>, 4> MDs;
      i->getAllMetadata(MDs);
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Metadatas remapped\n");
      // important for compiling with DWARF, using option -g.
      i->setDebugLoc(ji->getDebugLoc());
      ji++;
      DEBUG_WITH_TYPE("gen", errs()
                                 << "bcf: Debug information location setted\n");

    } // The instructions' informations are now all correct

    DEBUG_WITH_TYPE("gen",
                    errs() << "bcf: The cloned basic block is now correct\n");
    DEBUG_WITH_TYPE(
        "gen",
        errs() << "bcf: Starting to add junk code in the cloned bloc...\n");

    // add random instruction in the middle of the bloc. This part can be
    // improve
    for (BasicBlock::iterator i = alteredBB->begin(), e = alteredBB->end();
         i != e; ++i) {
      // in the case we find binary operator, we modify slightly this part by
      // randomly insert some instructions
      if (i->isBinaryOp()) { // binary instructions
        unsigned opcode = i->getOpcode();
        BinaryOperator *op, *op1 = NULL;
        Twine *var = new Twine("_");
        // treat differently float or int
        // Binary int
        if (opcode == Instruction::Add || opcode == Instruction::Sub ||
            opcode == Instruction::Mul || opcode == Instruction::UDiv ||
            opcode == Instruction::SDiv || opcode == Instruction::URem ||
            opcode == Instruction::SRem || opcode == Instruction::Shl ||
            opcode == Instruction::LShr || opcode == Instruction::AShr ||
            opcode == Instruction::And || opcode == Instruction::Or ||
            opcode == Instruction::Xor) {
          for (int random = (int)llvm::cryptoutils->get_range(10); random < 10;
               ++random) {
            switch (llvm::cryptoutils->get_range(4)) { // to improve
            case 0:                                    // do nothing
              break;
            case 1:
              op = BinaryOperator::CreateNeg(i->getOperand(0), *var, &*i);
              op1 = BinaryOperator::Create(Instruction::Add, op,
                                           i->getOperand(1), "gen", &*i);
              break;
            case 2:
              op1 = BinaryOperator::Create(Instruction::Sub, i->getOperand(0),
                                           i->getOperand(1), *var, &*i);
              op = BinaryOperator::Create(Instruction::Mul, op1,
                                          i->getOperand(1), "gen", &*i);
              break;
            case 3:
              op = BinaryOperator::Create(Instruction::Shl, i->getOperand(0),
                                          i->getOperand(1), *var, &*i);
              break;
            }
          }
        }
        // Binary float
        if (opcode == Instruction::FAdd || opcode == Instruction::FSub ||
            opcode == Instruction::FMul || opcode == Instruction::FDiv ||
            opcode == Instruction::FRem) {
          for (int random = (int)llvm::cryptoutils->get_range(10); random < 10;
               ++random) {
            switch (llvm::cryptoutils->get_range(3)) { // can be improved
            case 0:                                    // do nothing
              break;
            case 1:
              op = BinaryOperator::CreateFNeg(i->getOperand(0), *var, &*i);
              op1 = BinaryOperator::Create(Instruction::FAdd, op,
                                           i->getOperand(1), "gen", &*i);
              break;
            case 2:
              op = BinaryOperator::Create(Instruction::FSub, i->getOperand(0),
                                          i->getOperand(1), *var, &*i);
              op1 = BinaryOperator::Create(Instruction::FMul, op,
                                           i->getOperand(1), "gen", &*i);
              break;
            }
          }
        }
        if (opcode == Instruction::ICmp) { // Condition (with int)
          ICmpInst *currentI = (ICmpInst *)(&i);
          switch (llvm::cryptoutils->get_range(3)) { // must be improved
          case 0:                                    // do nothing
            break;
          case 1:
            currentI->swapOperands();
            break;
          case 2: // randomly change the predicate
            switch (llvm::cryptoutils->get_range(10)) {
            case 0:
              currentI->setPredicate(ICmpInst::ICMP_EQ);
              break; // equal
            case 1:
              currentI->setPredicate(ICmpInst::ICMP_NE);
              break; // not equal
            case 2:
              currentI->setPredicate(ICmpInst::ICMP_UGT);
              break; // unsigned greater than
            case 3:
              currentI->setPredicate(ICmpInst::ICMP_UGE);
              break; // unsigned greater or equal
            case 4:
              currentI->setPredicate(ICmpInst::ICMP_ULT);
              break; // unsigned less than
            case 5:
              currentI->setPredicate(ICmpInst::ICMP_ULE);
              break; // unsigned less or equal
            case 6:
              currentI->setPredicate(ICmpInst::ICMP_SGT);
              break; // signed greater than
            case 7:
              currentI->setPredicate(ICmpInst::ICMP_SGE);
              break; // signed greater or equal
            case 8:
              currentI->setPredicate(ICmpInst::ICMP_SLT);
              break; // signed less than
            case 9:
              currentI->setPredicate(ICmpInst::ICMP_SLE);
              break; // signed less or equal
            }
            break;
          }
        }
        if (opcode == Instruction::FCmp) { // Conditions (with float)
          FCmpInst *currentI = (FCmpInst *)(&i);
          switch (llvm::cryptoutils->get_range(3)) { // must be improved
          case 0:                                    // do nothing
            break;
          case 1:
            currentI->swapOperands();
            break;
          case 2: // randomly change the predicate
            switch (llvm::cryptoutils->get_range(10)) {
            case 0:
              currentI->setPredicate(FCmpInst::FCMP_OEQ);
              break; // ordered and equal
            case 1:
              currentI->setPredicate(FCmpInst::FCMP_ONE);
              break; // ordered and operands are unequal
            case 2:
              currentI->setPredicate(FCmpInst::FCMP_UGT);
              break; // unordered or greater than
            case 3:
              currentI->setPredicate(FCmpInst::FCMP_UGE);
              break; // unordered, or greater than, or equal
            case 4:
              currentI->setPredicate(FCmpInst::FCMP_ULT);
              break; // unordered or less than
            case 5:
              currentI->setPredicate(FCmpInst::FCMP_ULE);
              break; // unordered, or less than, or equal
            case 6:
              currentI->setPredicate(FCmpInst::FCMP_OGT);
              break; // ordered and greater than
            case 7:
              currentI->setPredicate(FCmpInst::FCMP_OGE);
              break; // ordered and greater than or equal
            case 8:
              currentI->setPredicate(FCmpInst::FCMP_OLT);
              break; // ordered and less than
            case 9:
              currentI->setPredicate(FCmpInst::FCMP_OLE);
              break; // ordered or less than, or equal
            }
            break;
          }
        }
      }
    }
    // Remove DIs from AlterBB
    vector<CallInst *> toRemove;
    vector<Constant*> DeadConstants;
    for (Instruction &I : *alteredBB) {
      if (CallInst *CI = dyn_cast<CallInst>(&I)) {
        if (CI->getCalledFunction() != nullptr &&
            CI->getCalledFunction()->getName().startswith("llvm.dbg")) {
          toRemove.push_back(CI);
        }
      }
    }
    // Shamefully stolen from IPO/StripSymbols.cpp
    for (CallInst *CI : toRemove) {
      Value *Arg1 = CI->getArgOperand(0);
      Value *Arg2 = CI->getArgOperand(1);
      assert(CI->use_empty() && "llvm.dbg intrinsic should have void result");
      CI->eraseFromParent();
      if (Arg1->use_empty()) {
        if (Constant *C = dyn_cast<Constant>(Arg1)) {
          DeadConstants.push_back(C);
        } else {
          RecursivelyDeleteTriviallyDeadInstructions(Arg1);
        }
      }
      if (Arg2->use_empty()) {
        if (Constant *C = dyn_cast<Constant>(Arg2)) {
          DeadConstants.push_back(C);
        }
      }
    }
    while (!DeadConstants.empty()) {
      Constant *C = DeadConstants.back();
      DeadConstants.pop_back();
      if (GlobalVariable *GV = dyn_cast<GlobalVariable>(C)) {
        if (GV->hasLocalLinkage())
          RemoveDeadConstant(GV);
      } else
        RemoveDeadConstant(C);
    }
    return alteredBB;
  } // end of createAlteredBasicBlock()

  /* doFinalization
   *
   * Overwrite FunctionPass method to apply the transformations to the whole
   * module. This part obfuscate all the always true predicates of the module.
   * More precisely, the condition which predicate is FCMP_TRUE.
   * It also remove all the functions' basic blocks' and instructions' names.
   */
  bool doF(Module &M) {
    // In this part we extract all always-true predicate and replace them with
    // opaque predicate: For this, we declare two global values: x and y, and
    // replace the FCMP_TRUE predicate with (y < 10 || x * (x + 1) % 2 == 0) A
    // better way to obfuscate the predicates would be welcome. In the meantime
    // we will erase the name of the basic blocks, the instructions and the
    // functions.
    DEBUG_WITH_TYPE("gen", errs() << "bcf: Starting doFinalization...\n");
    std::vector<Instruction *> toEdit, toDelete;
    // BinaryOperator *op, *op1 = NULL;
    // ICmpInst *condition, *condition2;
    // Looking for the conditions and branches to transform
    for (Module::iterator mi = M.begin(), me = M.end(); mi != me; ++mi) {
      for (Function::iterator fi = mi->begin(), fe = mi->end(); fi != fe;
           ++fi) {
        // fi->setName("");
        Instruction *tbb = fi->getTerminator();
        if (tbb->getOpcode() == Instruction::Br) {
          BranchInst *br = (BranchInst *)(tbb);
          if (br->isConditional()) {
            ICmpInst *cond = (ICmpInst *)br->getCondition();
            unsigned opcode = cond->getOpcode();
            if (opcode == Instruction::ICmp) {
              if (cond->getPredicate() == ICmpInst::ICMP_EQ && cond->getName().startswith("BCFPlaceHolderPred")) {
                DEBUG_WITH_TYPE("gen",
                                errs() << "bcf: an always true predicate !\n");
                toDelete.push_back(cond); // The condition
                toEdit.push_back(tbb);    // The branch using the condition
              }
            }
          }
        }
        /*
        for (BasicBlock::iterator bi = fi->begin(), be = fi->end() ; bi != be;
        ++bi){ bi->setName(""); // setting the basic blocks' names
        }
        */
      }
    }
    // Replacing all the branches we found
    for (std::vector<Instruction *>::iterator i = toEdit.begin();
         i != toEdit.end(); ++i) {
      // Previously We Use LLVM EE To Calculate LHS and RHS
      // Since IRBuilder<> uses ConstantFolding to fold constants.
      // The return instruction is already returning constants
      // The variable names below are the artifact from the Emulation Era
      Type *I32Ty = Type::getInt32Ty(M.getContext());
      Module emuModule("HikariBCFEmulator", M.getContext());
      emuModule.setDataLayout(M.getDataLayout());
      emuModule.setTargetTriple(M.getTargetTriple());
      Function *emuFunction =
          Function::Create(FunctionType::get(I32Ty, false),
                           GlobalValue::LinkageTypes::PrivateLinkage,
                           "BeginExecution", &emuModule);
      BasicBlock *EntryBlock =
          BasicBlock::Create(M.getContext(), "", emuFunction);

      Instruction *tmp = &*((*i)->getParent()->getFirstInsertionPt());
      IRBuilder<> IRBReal(tmp);
      IRBuilder<> IRBEmu(EntryBlock);
      // First,Construct a real RHS that will be used in the actual condition
      Constant *RealRHS = ConstantInt::get(I32Ty, cryptoutils->get_uint32_t());
      // Prepare Initial LHS and RHS to bootstrap the emulator
      Constant *LHSC = ConstantInt::get(I32Ty, cryptoutils->get_uint32_t());
      Constant *RHSC = ConstantInt::get(I32Ty, cryptoutils->get_uint32_t());
      GlobalVariable *LHSGV =
          new GlobalVariable(M, Type::getInt32Ty(M.getContext()), false,
                             GlobalValue::PrivateLinkage, LHSC, "LHSGV");
      GlobalVariable *RHSGV =
          new GlobalVariable(M, Type::getInt32Ty(M.getContext()), false,
                             GlobalValue::PrivateLinkage, RHSC, "RHSGV");
      LoadInst *LHS = IRBReal.CreateLoad(LHSGV, "Initial LHS");
      LoadInst *RHS = IRBReal.CreateLoad(RHSGV, "Initial LHS");
      // To Speed-Up Evaluation
      Value *emuLHS = LHSC;
      Value *emuRHS = RHSC;
      Instruction::BinaryOps initialOp = ops[llvm::cryptoutils->get_uint32_t() %
                                             (sizeof(ops) / sizeof(ops[0]))];
      Value *emuLast =
          IRBEmu.CreateBinOp(initialOp, emuLHS, emuRHS, "EmuInitialCondition");
      Value *Last =
          IRBReal.CreateBinOp(initialOp, LHS, RHS, "InitialCondition");
      for (int i = 0; i < ConditionExpressionComplexity; i++) {
        Constant *newTmp = ConstantInt::get(I32Ty, cryptoutils->get_uint32_t());
        Instruction::BinaryOps initialOp =
            ops[llvm::cryptoutils->get_uint32_t() %
                (sizeof(ops) / sizeof(ops[0]))];
        emuLast = IRBEmu.CreateBinOp(initialOp, emuLast, newTmp,
                                     "EmuInitialCondition");
        Last = IRBReal.CreateBinOp(initialOp, Last, newTmp, "InitialCondition");
      }
      // Randomly Generate Predicate
      CmpInst::Predicate pred = preds[llvm::cryptoutils->get_uint32_t() %
                                      (sizeof(preds) / sizeof(preds[0]))];
      Last = IRBReal.CreateICmp(pred, Last, RealRHS);
      emuLast = IRBEmu.CreateICmp(pred, emuLast, RealRHS);
      ReturnInst *RI = IRBEmu.CreateRet(emuLast);
      ConstantInt *emuCI = cast<ConstantInt>(RI->getReturnValue());
      uint64_t emulateResult = emuCI->getZExtValue();
      vector<BasicBlock *> BBs; // Start To Prepare IndirectBranching
      if (emulateResult == 1) {
        // Our ConstantExpr evaluates to true;

        BranchInst::Create(((BranchInst *)*i)->getSuccessor(0),
                           ((BranchInst *)*i)->getSuccessor(1), (Value *)Last,
                           ((BranchInst *)*i)->getParent());
      } else {
        // False, swap operands

        BranchInst::Create(((BranchInst *)*i)->getSuccessor(1),
                           ((BranchInst *)*i)->getSuccessor(0), (Value *)Last,
                           ((BranchInst *)*i)->getParent());
      }
      EntryBlock->eraseFromParent();
      emuFunction->eraseFromParent();
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Erase branch instruction:"
                                    << *((BranchInst *)*i) << "\n");
      (*i)->eraseFromParent(); // erase the branch
    }
    // Erase all the associated conditions we found
    for (std::vector<Instruction *>::iterator i = toDelete.begin();
         i != toDelete.end(); ++i) {
      DEBUG_WITH_TYPE("gen", errs() << "bcf: Erase condition instruction:"
                                    << *((Instruction *)*i) << "\n");
      (*i)->eraseFromParent();
    }

    // Only for debug
    DEBUG_WITH_TYPE("cfg", errs() << "bcf: End of the pass, here are the "
                                     "graphs after doFinalization\n");
    for (Module::iterator mi = M.begin(), me = M.end(); mi != me; ++mi) {
      DEBUG_WITH_TYPE("cfg", errs()
                                 << "bcf: Function " << mi->getName() << "\n");
      DEBUG_WITH_TYPE("cfg", mi->viewCFG());
    }

    return true;
  } // end of doFinalization

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}

已混淆IR(-mllvm -enable-bcfobf)

@LHSGV = private global i32 -337248983
@RHSGV = private global i32 1241409517
@LHSGV.1 = private global i32 1605983733
@RHSGV.2 = private global i32 -1151943679

; Function Attrs: noinline norecurse optnone ssp uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %"Initial LHS" = load i32, i32* @LHSGV
  %"Initial LHS2" = load i32, i32* @RHSGV
  %InitialCondition = add i32 %"Initial LHS", %"Initial LHS2"
  %InitialCondition3 = xor i32 %InitialCondition, 1256167145
  %InitialCondition4 = and i32 %InitialCondition3, 1503516098
  %InitialCondition5 = add i32 %InitialCondition4, -148188098
  %0 = icmp ult i32 %InitialCondition5, 695259654
  br i1 %0, label %originalBBalteredBB, label %originalBB

originalBB:                                       ; preds = %entry, %originalBBalteredBB
  %"Initial LHS6" = load i32, i32* @LHSGV.1
  %"Initial LHS7" = load i32, i32* @RHSGV.2
  %InitialCondition8 = add i32 %"Initial LHS6", %"Initial LHS7"
  %InitialCondition9 = sub i32 %InitialCondition8, -1018173326
  %InitialCondition10 = or i32 %InitialCondition9, -63656773
  %InitialCondition11 = and i32 %InitialCondition10, 1604909974
  %1 = icmp ne i32 %InitialCondition11, 1137503584
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  br i1 %1, label %originalBBpart2, label %originalBBalteredBB

originalBBpart2:                                  ; preds = %originalBB
  ret i32 0

originalBBalteredBB:                              ; preds = %originalBB, %entry
  %retvalalteredBB = alloca i32, align 4
  %argc.addralteredBB = alloca i32, align 4
  %argv.addralteredBB = alloca i8**, align 8
  store i32 0, i32* %retvalalteredBB, align 4
  store i32 %argc, i32* %argc.addralteredBB, align 4
  store i8** %argv, i8*** %argv.addralteredBB, align 8
  %callalteredBB = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  br label %originalBB
}
flowchart LR
A([entry]) --> B([originalBBalteredBB])
A([entry]) --> C([originalBB])
C([originalBB]) --> D([originalBBpart2])
C([originalBB]) --> B([originalBBalteredBB])
B([originalBBalteredBB]) --> C([originalBB])
D([originalBBpart2]) --> ret([ret])

  bcf是BasicBlock层的混淆,通过构造条件和分支使CF复杂化

Pluto MbaObfuscation模块分析

PreservedAnalyses Pluto::MbaObfuscation::run(Function &F, FunctionAnalysisManager &AM) {
    for (BasicBlock &BB : F) {
        std::vector<Instruction *> origInst;
        for (Instruction &I : BB) {
            origInst.push_back(&I);
        }
        for (Instruction *I : origInst) {
            if (isa<BinaryOperator>(I)) {
                BinaryOperator *BI = cast<BinaryOperator>(I);
                if (BI->getOperand(0)->getType()->isIntegerTy()) {
                    // Do not support 128-bit integers now
                    if (BI->getOperand(0)->getType()->getIntegerBitWidth() > 64) {
                        continue;
                    }
                    substitute(BI);
                }
            } else {
                for (int i = 0; i < I->getNumOperands(); i++) {
                    if (I->getOperand(0)->getType()->isIntegerTy()) {
                        // error occurs for unknown reasons
                        // if(isa<StoreInst>(I) || isa<CmpInst>(I) || isa<CallInst>(I)){
                        if (isa<StoreInst>(I) || isa<CmpInst>(I)) {
                            substituteConstant(I, i);
                        }
                    }
                }
            }
        }
    }
    PreservedAnalyses PA;
    PA.preserveSet<CFGAnalyses>();
    return PA;
}

void Pluto::MbaObfuscation::substituteConstant(Instruction *I, int i) {
    ConstantInt *val = dyn_cast<ConstantInt>(I->getOperand(i));
    if (val && val->getBitWidth() <= 64) {
        int64_t *coeffs = generateLinearMBA(NUM_COEFFS);
        coeffs[14] -= val->getValue().getZExtValue();
        Value *mbaExpr = insertLinearMBA(coeffs, I);
        delete[] coeffs;
        if (val->getBitWidth() <= 32) {
            mbaExpr = insertPolynomialMBA(mbaExpr, I);
        }
        I->setOperand(i, mbaExpr);
    }
}

void Pluto::MbaObfuscation::substitute(BinaryOperator *BI) {
    Value *mbaExpr = nullptr;
    switch (BI->getOpcode()) {
    case BinaryOperator::Add:
        mbaExpr = substituteAdd(BI);
        break;
    case BinaryOperator::Sub:
        mbaExpr = substituteSub(BI);
        break;
    case BinaryOperator::And:
        mbaExpr = substituteAnd(BI);
        break;
    case BinaryOperator::Or:
        mbaExpr = substituteOr(BI);
        break;
    case BinaryOperator::Xor:
        mbaExpr = substituteXor(BI);
        break;
    default:
        break;
    }
    if (mbaExpr) {
        if (BI->getOperand(0)->getType()->getIntegerBitWidth() <= 32) {
            mbaExpr = insertPolynomialMBA(mbaExpr, BI);
        }
        BI->replaceAllUsesWith(mbaExpr);
    }
}

未混淆IR

define i32 @main(i32 noundef %0, i8** noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %6 = load i32, i32* %4, align 4
  %7 = icmp eq i32 %6, 1
  br i1 %7, label %8, label %10

8:                                                ; preds = %2
  %9 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  br label %10

10:                                               ; preds = %8, %2
  ret i32 0
}

已混淆IR(-mllvm -passes=mba)

@.str = private unnamed_addr constant [6 x i8] c"hello\00", align 1
@x = private global i32 633921516
@y = private global i32 -929520819
@x.1 = private global i32 -269327150
@y.2 = private global i32 -1823721797

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 noundef %0, i8** noundef %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = load i32, i32* @x, align 4
  %7 = load i32, i32* @y, align 4
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %8 = load i32, i32* %4, align 4
  %9 = load i32, i32* @x.1, align 4
  %10 = load i32, i32* @y.2, align 4
  %11 = xor i32 %10, -1
  %12 = and i32 %9, %11
  %13 = mul i32 -1, %12
  %14 = add i32 0, %13
  %15 = xor i32 %9, %10
  %16 = xor i32 %15, -1
  %17 = mul i32 -1, %16
  %18 = add i32 %14, %17
  %19 = xor i32 %10, -1
  %20 = or i32 %9, %19
  %21 = mul i32 1, %20
  %22 = add i32 %18, %21
  %23 = add i32 %22, 1
  %24 = mul i32 -2064751721, %23
  %25 = add i32 %24, -1348253517
  %26 = mul i32 43142183, %25
  %27 = add i32 %26, -394999621
  %28 = icmp eq i32 %8, %27
  br i1 %28, label %29, label %31

29:                                               ; preds = %2
  %30 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  br label %31

31:                                               ; preds = %29, %2
  ret i32 0
}

函数级别的混淆

Hikari FunctionCallObfuscate模块分析

  virtual bool doInitialization(Module &M) override {
    // Basic Defs
    if (SymbolConfigPath == "+-x/") {
      SmallString<32> Path;
      if (sys::path::home_directory(Path)) { // Stolen from LineEditor.cpp
        sys::path::append(Path, "Hikari", "SymbolConfig.json");
        SymbolConfigPath = Path.str();
      }
    }
    ifstream infile(SymbolConfigPath);
    if (infile.good()) {
      errs() << "Loading Symbol Configuration From:" << SymbolConfigPath
             << "\n";
      infile >> this->Configuration;
    } else {
      errs() << "Failed To Loading Symbol Configuration From:"
             << SymbolConfigPath << "\n";
    }
    Triple tri(M.getTargetTriple());
    if (tri.getVendor() != Triple::VendorType::Apple) {
      return false;
    }
    Type *Int64Ty = Type::getInt64Ty(M.getContext());
    Type *Int32Ty = Type::getInt32Ty(M.getContext());
    Type *Int8PtrTy = Type::getInt8PtrTy(M.getContext());
    Type *Int8Ty = Type::getInt8Ty(M.getContext());
    // Generic ObjC Runtime Declarations
    FunctionType *IMPType =
        FunctionType::get(Int8PtrTy, {Int8PtrTy, Int8PtrTy}, true);
    PointerType *IMPPointerType = PointerType::get(IMPType, 0);
    vector<Type *> classReplaceMethodTypeArgs;
    classReplaceMethodTypeArgs.push_back(Int8PtrTy);
    classReplaceMethodTypeArgs.push_back(Int8PtrTy);
    classReplaceMethodTypeArgs.push_back(IMPPointerType);
    classReplaceMethodTypeArgs.push_back(Int8PtrTy);
    FunctionType *class_replaceMethod_type =
        FunctionType::get(IMPPointerType, classReplaceMethodTypeArgs, false);
    M.getOrInsertFunction("class_replaceMethod", class_replaceMethod_type);
    FunctionType *sel_registerName_type =
        FunctionType::get(Int8PtrTy, {Int8PtrTy}, false);
    M.getOrInsertFunction("sel_registerName", sel_registerName_type);
    FunctionType *objc_getClass_type =
        FunctionType::get(Int8PtrTy, {Int8PtrTy}, false);
    M.getOrInsertFunction("objc_getClass", objc_getClass_type);
    M.getOrInsertFunction("objc_getMetaClass", objc_getClass_type);
    StructType *objc_property_attribute_t_type = reinterpret_cast<StructType *>(
        M.getTypeByName("struct.objc_property_attribute_t"));
    if (objc_property_attribute_t_type == NULL) {
      vector<Type *> types;
      types.push_back(Int8PtrTy);
      types.push_back(Int8PtrTy);
      objc_property_attribute_t_type = StructType::create(
          ArrayRef<Type *>(types), "struct.objc_property_attribute_t");
      M.getOrInsertGlobal("struct.objc_property_attribute_t",
                          objc_property_attribute_t_type);
    }
    vector<Type *> allocaClsTypeVector;
    vector<Type *> addIvarTypeVector;
    vector<Type *> addPropTypeVector;
    allocaClsTypeVector.push_back(Int8PtrTy);
    allocaClsTypeVector.push_back(Int8PtrTy);
    addIvarTypeVector.push_back(Int8PtrTy);
    addIvarTypeVector.push_back(Int8PtrTy);
    addPropTypeVector.push_back(Int8PtrTy);
    addPropTypeVector.push_back(Int8PtrTy);
    addPropTypeVector.push_back(objc_property_attribute_t_type->getPointerTo());
    if (tri.isArch64Bit()) {
      // We are 64Bit Device
      allocaClsTypeVector.push_back(Int64Ty);
      addIvarTypeVector.push_back(Int64Ty);
      addPropTypeVector.push_back(Int64Ty);
    } else {
      // Not 64Bit.However we are still on apple platform.So We are
      // ARMV7/ARMV7S/i386
      // PowerPC is ignored, feel free to open a PR if you want to
      allocaClsTypeVector.push_back(Int32Ty);
      addIvarTypeVector.push_back(Int32Ty);
      addPropTypeVector.push_back(Int32Ty);
    }
    addIvarTypeVector.push_back(Int8Ty);
    addIvarTypeVector.push_back(Int8PtrTy);
    // Types Collected. Now Inject Functions
    FunctionType *addIvarType =
        FunctionType::get(Int8Ty, ArrayRef<Type *>(addIvarTypeVector), false);
    M.getOrInsertFunction("class_addIvar", addIvarType);
    FunctionType *addPropType =
        FunctionType::get(Int8Ty, ArrayRef<Type *>(addPropTypeVector), false);
    M.getOrInsertFunction("class_addProperty", addPropType);
    FunctionType *class_getName_Type =
        FunctionType::get(Int8PtrTy, {Int8PtrTy}, false);
    M.getOrInsertFunction("class_getName", class_getName_Type);
    FunctionType *objc_getMetaClass_Type =
        FunctionType::get(Int8PtrTy, {Int8PtrTy}, false);
    M.getOrInsertFunction("objc_getMetaClass", objc_getMetaClass_Type);
    return true;
  }
  void HandleObjC(Module &M) {
    // Iterate all CLASSREF uses and replace with objc_getClass() call
    // Strings are encrypted in other passes
    for (auto G = M.global_begin(); G != M.global_end(); G++) {
      GlobalVariable &GV = *G;
      if (GV.getName().str().find("OBJC_CLASSLIST_REFERENCES") == 0) {
        if (GV.hasInitializer()) {
          string className = GV.getInitializer()->getName();
          className.replace(className.find("OBJC_CLASS_$_"),
                            strlen("OBJC_CLASS_$_"), "");
          for (auto U = GV.user_begin(); U != GV.user_end(); U++) {
            if (Instruction *I = dyn_cast<Instruction>(*U)) {
              IRBuilder<> builder(I);
              Function *objc_getClass_Func =
                  cast<Function>(M.getFunction("objc_getClass"));
              Value *newClassName =
                  builder.CreateGlobalStringPtr(StringRef(className));
              CallInst *CI =
                  builder.CreateCall(objc_getClass_Func, {newClassName});
              // We need to bitcast it back to avoid IRVerifier
              Value *BCI = builder.CreateBitCast(CI, I->getType());
              I->replaceAllUsesWith(BCI);
              I->eraseFromParent();
            }
          }
          GV.removeDeadConstantUsers();
          if (GV.getNumUses() == 0) {
            GV.dropAllReferences();
            GV.eraseFromParent();
          }
        }
      }
      // Selector Convert
      else if (GV.getName().str().find("OBJC_SELECTOR_REFERENCES") == 0) {
        if (GV.hasInitializer()) {
          ConstantExpr *CE = dyn_cast<ConstantExpr>(GV.getInitializer());
          Constant *C = CE->getOperand(0);
          GlobalVariable *SELNameGV = dyn_cast<GlobalVariable>(C);
          ConstantDataArray *CDA =
              dyn_cast<ConstantDataArray>(SELNameGV->getInitializer());
          StringRef SELName = CDA->getAsString(); // This is REAL Selector Name
          for (auto U = GV.user_begin(); U != GV.user_end(); U++) {
            if (Instruction *I = dyn_cast<Instruction>(*U)) {
              IRBuilder<> builder(I);
              Function *sel_registerName_Func =
                  cast<Function>(M.getFunction("sel_registerName"));
              Value *newGlobalSELName = builder.CreateGlobalStringPtr(SELName);
              CallInst *CI =
                  builder.CreateCall(sel_registerName_Func, {newGlobalSELName});
              // We need to bitcast it back to avoid IRVerifier
              Value *BCI = builder.CreateBitCast(CI, I->getType());
              I->replaceAllUsesWith(BCI);
              I->eraseFromParent();
            }
          }
          GV.removeDeadConstantUsers();
          if (GV.getNumUses() == 0) {
            GV.dropAllReferences();
            GV.eraseFromParent();
          }
        }
      }
    }
  }
  virtual bool runOnFunction(Function &F) override {
    // Construct Function Prototypes
    if (toObfuscate(flag, &F, "fco") == false) {
      return false;
    }
    Triple Tri(F.getParent()->getTargetTriple());
    if (!Tri.isAndroid() && !Tri.isOSDarwin()) {
      errs() << "Unsupported Target Triple:"<< F.getParent()->getTargetTriple() << "\n";
      return false;
    }
    errs() << "Running FunctionCallObfuscate On " << F.getName() << "\n";
    Module *M = F.getParent();
    FixFunctionConstantExpr(&F);
    HandleObjC(*M);
    Type *Int32Ty = Type::getInt32Ty(M->getContext());
    Type *Int8PtrTy = Type::getInt8PtrTy(M->getContext());
    // ObjC Runtime Declarations
    FunctionType *dlopen_type = FunctionType::get(
        Int8PtrTy, {Int8PtrTy, Int32Ty},
        false); // int has a length of 32 on both 32/64bit platform
    FunctionType *dlsym_type =
        FunctionType::get(Int8PtrTy, {Int8PtrTy, Int8PtrTy}, false);
    Function *dlopen_decl =
        cast<Function>(M->getOrInsertFunction("dlopen", dlopen_type));
    Function *dlsym_decl =
        cast<Function>(M->getOrInsertFunction("dlsym", dlsym_type));
    // Begin Iteration
    for (BasicBlock &BB : F) {
      for (auto I = BB.getFirstInsertionPt(), end = BB.end(); I != end; ++I) {
        Instruction &Inst = *I;
        if (isa<CallInst>(&Inst) || isa<InvokeInst>(&Inst)) {
          CallSite CS(&Inst);
          Function *calledFunction = CS.getCalledFunction();
          if (calledFunction == NULL) {
            /*
              Note:
              For Indirect Calls:
                CalledFunction is NULL and calledValue is usually a bitcasted
              function pointer. We'll need to strip out the hiccups and obtain
              the called Function* from there
            */
            calledFunction =
                dyn_cast<Function>(CS.getCalledValue()->stripPointerCasts());
          }
          // Simple Extracting Failed
          // Use our own implementation
          if (calledFunction == NULL) {
            DEBUG_WITH_TYPE(
                "opt", errs()
                           << "Failed To Extract Function From Indirect Call: "
                           << *CS.getCalledValue() << "\n");
            continue;
          }
          // It's only safe to restrict our modification to external symbols
          // Otherwise stripped binary will crash
          if (!calledFunction->empty() ||
              calledFunction->getName().equals("dlsym") ||
              calledFunction->getName().equals("dlopen") ||
              calledFunction->isIntrinsic()) {
            continue;
          }
          // errs()<<"Searching For:"<<calledFunction->getName()<<" In
          // Configuration\n";
          if (this->Configuration.find(calledFunction->getName().str()) !=
              this->Configuration.end()) {
            string sname = this->Configuration[calledFunction->getName().str()]
                               .get<string>();
            StringRef calledFunctionName = StringRef(sname);
            BasicBlock *EntryBlock = CS->getParent();
            IRBuilder<> IRB(EntryBlock, EntryBlock->getFirstInsertionPt());
            vector<Value *> dlopenargs;
            dlopenargs.push_back(Constant::getNullValue(Int8PtrTy));
             if (Tri.isOSDarwin()) {
              dlopen_flag=DARWIN_FLAG;
            } else if (Tri.isAndroid()) {
              if (Tri.isArch64Bit()) {
                dlopen_flag=ANDROID64_FLAG;
              } else {
                dlopen_flag=ANDROID32_FLAG;
              }

            } else {
              errs() << "[FunctionCallObfuscate]Unsupported Target Triple:"
                         << F.getParent()->getTargetTriple() << "\n";
              errs()<<"[FunctionCallObfuscate]Applying Default Signature:"<<dlopen_flag<<"\n";
            }
            dlopenargs.push_back(ConstantInt::get(Int32Ty, dlopen_flag));
            Value *Handle =
                IRB.CreateCall(dlopen_decl, ArrayRef<Value *>(dlopenargs));
            // Create dlsym call
            vector<Value *> args;
            args.push_back(Handle);
            args.push_back(IRB.CreateGlobalStringPtr(calledFunctionName));
            Value *fp = IRB.CreateCall(dlsym_decl, ArrayRef<Value *>(args));
            Value *bitCastedFunction =
                IRB.CreateBitCast(fp, CS.getCalledValue()->getType());
            CS.setCalledFunction(bitCastedFunction);
          }
        }
      }
    }
    return true;
  }

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  ret i32 0
}

已混淆IR(-mllvm -enable-fco)

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %0 = call i8* @dlopen(i8* null, i32 10)
  %1 = call i8* @dlsym(i8* %0, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @0, i32 0, i32 0))
  %2 = bitcast i8* %1 to i32 (i8*, ...)*
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %3 = getelementptr inbounds [3 x i8], [3 x i8]* @.str, i32 0, i32 0
  %call = call i32 (i8*, ...) %2(i8* %3)
  ret i32 0
}

   fco是Instruction层的混淆,可以将API调用构造为动态调用。

Hikari FunctionWrapper模块分析

  CallSite *HandleCallSite(CallSite *CS) {
    Value *calledFunction = CS->getCalledFunction();
    if (calledFunction == nullptr) {
      calledFunction = CS->getCalledValue()->stripPointerCasts();
    }
    // Filter out IndirectCalls that depends on the context
    // Otherwise It'll be blantantly troublesome since you can't reference an
    // Instruction outside its BB  Too much trouble for a hobby project
    // To be precise, we only keep CS that refers to a non-intrinsic function
    // either directly or through casting
    if (calledFunction == nullptr ||
        (!isa<ConstantExpr>(calledFunction) &&
         !isa<Function>(calledFunction)) ||
        CS->getIntrinsicID() != Intrinsic::ID::not_intrinsic) {
      return nullptr;
    }
    if (Function *tmp = dyn_cast<Function>(calledFunction)) {
      if (tmp->getName().startswith("clang.")) {
        // Clang Intrinsic
        return nullptr;
      }
      for(auto argiter = tmp->arg_begin(); argiter!= tmp->arg_end(); ++argiter) {
       Argument& arg=*(argiter);
       if(arg.hasByValAttr()){
        // Arguments with byval attribute yields issues without proper handling.
        // The "proper" method to handle this is to revisit and patch attribute stealing code.
        // Technically readonly attr probably should also get filtered out here.

        // Nah too much work. This would do for open-source version since private already
        // this pass with more advanced solutions 
	return nullptr;       
       }   
      }
    }
    // Create a new function which in turn calls the actual function
    vector<Type *> types;
    for (unsigned i = 0; i < CS->getNumArgOperands(); i++) {
      types.push_back(CS->getArgOperand(i)->getType());
    }
    FunctionType *ft =
        FunctionType::get(CS->getType(), ArrayRef<Type *>(types), false);
    Function *func =
        Function::Create(ft, GlobalValue::LinkageTypes::InternalLinkage,
                         "HikariFunctionWrapper", CS->getParent()->getModule());
      //Trolling was all fun and shit so old implementation forced this symbol to exist in all objects
    appendToCompilerUsed(*func->getParent(), {func});
    BasicBlock *BB = BasicBlock::Create(func->getContext(), "", func);
    IRBuilder<> IRB(BB);
    vector<Value *> params;
    for (auto arg = func->arg_begin(); arg != func->arg_end(); arg++) {
      params.push_back(arg);
    }
    Value *retval = IRB.CreateCall(ConstantExpr::getBitCast(cast<Function>(calledFunction),CS->getCalledValue()->getType()), ArrayRef<Value *>(params));
    if (ft->getReturnType()->isVoidTy()) {
      IRB.CreateRetVoid();
    } else {
      IRB.CreateRet(retval);
    }
    CS->setCalledFunction(func);
    CS->mutateFunctionType(ft);
    Instruction *Inst = CS->getInstruction();
    delete CS;
    return new CallSite(Inst);
  }

未混淆IR

define void @_Z4funcv() #0 {
entry:
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  ret void
}

define i32 @main(i32 %argc, i8** %argv) #2 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  call void @_Z4funcv()
  ret i32 0
}

已混淆IR(-mllvm -enable-funcwra)

define void @_Z4funcv() #0 {
entry:
  %call = call i32 @HikariFunctionWrapper.1(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  ret void
}

define i32 @main(i32 %argc, i8** %argv) #2 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  call void @HikariFunctionWrapper.3()
  ret i32 0
}

define internal i32 @HikariFunctionWrapper(i8*) {
  %2 = call i32 (i8*, ...) @printf(i8* %0)
  ret i32 %2
}

define internal i32 @HikariFunctionWrapper.1(i8*) {
  %2 = call i32 @HikariFunctionWrapper(i8* %0)
  ret i32 %2
}

define internal void @HikariFunctionWrapper.2() {
  call void @_Z4funcv()
  ret void
}

define internal void @HikariFunctionWrapper.3() {
  call void @HikariFunctionWrapper.2()
  ret void
}
flowchart LR
A([main]) --> B([HikariFunctionWrapper.3])
B([HikariFunctionWrapper.3]) --> C([HikariFunctionWrapper.2])
C([HikariFunctionWrapper.2]) --> D([_Z4funcv])
D([_Z4funcv]) --> E([HikariFunctionWrapper.1])
E([HikariFunctionWrapper.1]) --> F([HikariFunctionWrapper])
F([HikariFunctionWrapper]) --> G([printf])

  fw是Function层的混淆,可以将函数调用构造为调用多个嵌套空函数。

指令级别的混淆

Hikari IndirectBranch模块分析

  bool initialize(Module &M) {
    vector<Constant *> BBs;
    unsigned long long i = 0;
    for (auto F = M.begin(); F != M.end(); F++) {
      for (auto BB = F->begin(); BB != F->end(); BB++) {
        BasicBlock *BBPtr = &*BB;
        if (BBPtr != &(BBPtr->getParent()->getEntryBlock())) {
          indexmap[BBPtr] = i++;
          BBs.push_back(BlockAddress::get(BBPtr));
        }
      }
    }
    ArrayType *AT =
        ArrayType::get(Type::getInt8PtrTy(M.getContext()), BBs.size());
    Constant *BlockAddressArray =
        ConstantArray::get(AT, ArrayRef<Constant *>(BBs));
    GlobalVariable *Table = new GlobalVariable(
        M, AT, false, GlobalValue::LinkageTypes::InternalLinkage,
        BlockAddressArray, "IndirectBranchingGlobalTable");
    appendToCompilerUsed(M, {Table});
    return true;
  }
  bool runOnFunction(Function &Func) override {
    if (!toObfuscate(flag, &Func, "indibr")) {
      return false;
    }
    if (this->initialized == false) {
      initialize(*Func.getParent());
      this->initialized = true;
    }
    errs() << "Running IndirectBranch On " << Func.getName() << "\n";
    vector<BranchInst *> BIs;
    for (inst_iterator I = inst_begin(Func); I != inst_end(Func); I++) {
      Instruction *Inst = &(*I);
      if (BranchInst *BI = dyn_cast<BranchInst>(Inst)) {
        BIs.push_back(BI);
      }
    } // Finish collecting branching conditions
    Value *zero =
        ConstantInt::get(Type::getInt32Ty(Func.getParent()->getContext()), 0);
    for (BranchInst *BI : BIs) {
      IRBuilder<> IRB(BI);
      vector<BasicBlock *> BBs;
      // We use the condition's evaluation result to generate the GEP
      // instruction  False evaluates to 0 while true evaluates to 1.  So here
      // we insert the false block first
      if (BI->isConditional()) {
        BBs.push_back(BI->getSuccessor(1));
      }
      BBs.push_back(BI->getSuccessor(0));
      ArrayType *AT = ArrayType::get(
          Type::getInt8PtrTy(Func.getParent()->getContext()), BBs.size());
      vector<Constant *> BlockAddresses;
      for (unsigned i = 0; i < BBs.size(); i++) {
        BlockAddresses.push_back(BlockAddress::get(BBs[i]));
      }
      GlobalVariable *LoadFrom = NULL;

      if (BI->isConditional() ||
          indexmap.find(BI->getSuccessor(0)) == indexmap.end()) {
        // Create a new GV
        Constant *BlockAddressArray =
            ConstantArray::get(AT, ArrayRef<Constant *>(BlockAddresses));
        LoadFrom = new GlobalVariable(
            *Func.getParent(), AT, false,
            GlobalValue::LinkageTypes::PrivateLinkage, BlockAddressArray,
            "HikariConditionalLocalIndirectBranchingTable");
        appendToCompilerUsed(*Func.getParent(), {LoadFrom});
      } else {
        LoadFrom = Func.getParent()->getGlobalVariable(
            "IndirectBranchingGlobalTable", true);
      }
      Value *index = NULL;
      if (BI->isConditional()) {
        Value *condition = BI->getCondition();
        index = IRB.CreateZExt(
            condition, Type::getInt32Ty(Func.getParent()->getContext()));
      } else {
        index =
            ConstantInt::get(Type::getInt32Ty(Func.getParent()->getContext()),
                             indexmap[BI->getSuccessor(0)]);
      }
      Value *GEP = IRB.CreateGEP(LoadFrom, {zero, index});
      LoadInst *LI = IRB.CreateLoad(GEP, "IndirectBranchingTargetAddress");
      IndirectBrInst *indirBr = IndirectBrInst::Create(LI, BBs.size());
      for (BasicBlock *BB : BBs) {
        indirBr->addDestination(BB);
      }
      ReplaceInstWithInst(BI, indirBr);
    }
    return true;
  }
  virtual bool doFinalization(Module &M) override {
    indexmap.clear();
    initialized = false;
    return false;
  }

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 0
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

已混淆IR(-mllvm -enable-indibran)

@IndirectBranchingGlobalTable = internal global [2 x i8*] [i8* blockaddress(@main, %if.then), i8* blockaddress(@main, %if.end)]
@HikariConditionalLocalIndirectBranchingTable = private global [2 x i8*] [i8* blockaddress(@main, %if.end), i8* blockaddress(@main, %if.then)]
@llvm.compiler.used = appending global [2 x i8*] [i8* bitcast ([2 x i8*]* @IndirectBranchingGlobalTable to i8*), i8* bitcast ([2 x i8*]* @HikariConditionalLocalIndirectBranchingTable to i8*)], section "llvm.metadata"

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp eq i32 %0, 0
  %1 = zext i1 %cmp to i32
  %2 = getelementptr [2 x i8*], [2 x i8*]* @HikariConditionalLocalIndirectBranchingTable, i32 0, i32 %1
  %IndirectBranchingTargetAddress = load i8*, i8** %2
  indirectbr i8* %IndirectBranchingTargetAddress, [label %if.end, label %if.then]

if.then:                                          ; preds = %entry
  %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([3 x i8], [3 x i8]* @.str, i32 0, i32 0))
  %IndirectBranchingTargetAddress1 = load i8*, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @IndirectBranchingGlobalTable, i32 0, i32 1)
  indirectbr i8* %IndirectBranchingTargetAddress1, [label %if.end]

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

  indibr是Instruction层的混淆,可以将分支指令降级(lower)为间接分支指令.

Hikari Substitution模块分析

bool Substitution::substitute(Function *f) {
  Function *tmp = f;

  // Loop for the number of time we run the pass on the function
  int times = ObfTimes;
  do {
    for (Function::iterator bb = tmp->begin(); bb != tmp->end(); ++bb) {
      for (BasicBlock::iterator inst = bb->begin(); inst != bb->end(); ++inst) {
        if (inst->isBinaryOp() && cryptoutils->get_range(100) <= ObfProbRate) {
          switch (inst->getOpcode()) {
          case BinaryOperator::Add:
            // case BinaryOperator::FAdd:
            // Substitute with random add operation
            (this->*funcAdd[llvm::cryptoutils->get_range(NUMBER_ADD_SUBST)])(
                cast<BinaryOperator>(inst));
            ++Add;
            break;
          case BinaryOperator::Sub:
            // case BinaryOperator::FSub:
            // Substitute with random sub operation
            (this->*funcSub[llvm::cryptoutils->get_range(NUMBER_SUB_SUBST)])(
                cast<BinaryOperator>(inst));
            ++Sub;
            break;
          case BinaryOperator::Mul:
          case BinaryOperator::FMul:
            //++Mul;
            break;
          case BinaryOperator::UDiv:
          case BinaryOperator::SDiv:
          case BinaryOperator::FDiv:
            //++Div;
            break;
          case BinaryOperator::URem:
          case BinaryOperator::SRem:
          case BinaryOperator::FRem:
            //++Rem;
            break;
          case Instruction::Shl:
            //++Shi;
            break;
          case Instruction::LShr:
            //++Shi;
            break;
          case Instruction::AShr:
            //++Shi;
            break;
          case Instruction::And:
            (this->*funcAnd[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++And;
            break;
          case Instruction::Or:
            (this->*funcOr[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++Or;
            break;
          case Instruction::Xor:
            (this->*funcXor[llvm::cryptoutils->get_range(2)])(
                cast<BinaryOperator>(inst));
            ++Xor;
            break;
          default:
            break;
          }              // End switch
        }                // End isBinaryOp
      }                  // End for basickblock
    }                    // End for Function
  } while (--times > 0); // for times
  return false;
}

未混淆IR

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %add = add nsw i32 %0, 2
  %sub = sub nsw i32 %add, 1
  %add1 = add nsw i32 %sub, 3
  %sub2 = sub nsw i32 %add1, 2
  ret i32 %sub2
}

已混淆IR(-mllvm -enable-subobf)

define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %1 = sub i32 0, 2
  %2 = sub i32 %0, %1
  %add = add nsw i32 %0, 2
  %3 = add i32 %2, 1155499931
  %4 = sub i32 %3, 1
  %5 = sub i32 %4, 1155499931
  %sub = sub nsw i32 %2, 1
  %add1 = add nsw i32 %5, 3
  %6 = sub i32 0, 2
  %7 = add i32 %add1, %6
  %sub2 = sub nsw i32 %add1, 2
  ret i32 %7
}

  sub是Instruction层混淆,可以将add/sub/and/or/xor等简单二元操作替换为等价的一系列简单指令。此功能是原始ollvm就存在的,对IDA这样成熟的逆向工具来说没什么实际效果,都被自动优化了。

goron IndirectCall模块分析

bool runOnFunction(Function &Fn) override {
    if (!toObfuscate(flag, &Fn, "icall")) {
      return false;
    }

    if (Options && Options->skipFunction(Fn.getName())) {
      return false;
    }

    LLVMContext &Ctx = Fn.getContext();

    CalleeNumbering.clear();
    Callees.clear();
    CallSites.clear();

    NumberCallees(Fn);

    if (Callees.empty()) {
      return false;
    }

    uint32_t V = RandomEngine.get_uint32_t() & ~3;
    ConstantInt *EncKey = ConstantInt::get(Type::getInt32Ty(Ctx), V, false);

    const IPObfuscationContext::IPOInfo *SecretInfo = nullptr;
    if (IPO) {
      SecretInfo = IPO->getIPOInfo(&Fn);
    }

    Value *MySecret;
    if (SecretInfo) {
      MySecret = SecretInfo->SecretLI;
    } else {
      MySecret = ConstantInt::get(Type::getInt32Ty(Ctx), 0, true);
    }

    ConstantInt *Zero = ConstantInt::get(Type::getInt32Ty(Ctx), 0);
    GlobalVariable *Targets = getIndirectCallees(Fn, EncKey);

    for (auto CI : CallSites) {
      SmallVector<Value *, 8> Args;
      SmallVector<AttributeSet, 8> ArgAttrVec;

      CallSite CS(CI);

      Instruction *Call = CS.getInstruction();
      Function *Callee = CS.getCalledFunction();
      FunctionType *FTy = CS.getFunctionType();
      IRBuilder<> IRB(Call);

      Args.clear();
      ArgAttrVec.clear();

      Value *Idx = ConstantInt::get(Type::getInt32Ty(Ctx), CalleeNumbering[CS.getCalledFunction()]);
      Value *GEP = IRB.CreateGEP(Targets, {Zero, Idx});
      LoadInst *EncDestAddr = IRB.CreateLoad(GEP, CI->getName());
      Constant *X;
      if (SecretInfo) {
        X = ConstantExpr::getSub(SecretInfo->SecretCI, EncKey);
      } else {
        X = ConstantExpr::getSub(Zero, EncKey);
      }

      const AttributeList &CallPAL = CS.getAttributes();
      CallSite::arg_iterator I = CS.arg_begin();
      unsigned i = 0;

      for (unsigned e = FTy->getNumParams(); i != e; ++I, ++i) {
        Args.push_back(*I);
        AttributeSet Attrs = CallPAL.getParamAttributes(i);
        ArgAttrVec.push_back(Attrs);
      }

      for (CallSite::arg_iterator E = CS.arg_end(); I != E; ++I, ++i) {
        Args.push_back(*I);
        ArgAttrVec.push_back(CallPAL.getParamAttributes(i));
      }

      AttributeList NewCallPAL = AttributeList::get(
          IRB.getContext(), CallPAL.getFnAttributes(), CallPAL.getRetAttributes(), ArgAttrVec);

      Value *Secret = IRB.CreateSub(X, MySecret);
      Value *DestAddr = IRB.CreateGEP(EncDestAddr, Secret);

      Value *FnPtr = IRB.CreateBitCast(DestAddr, FTy->getPointerTo());
      FnPtr->setName("Call_" + Callee->getName());
      CallInst *NewCall = IRB.CreateCall(FTy, FnPtr, Args, Call->getName());
      NewCall->setAttributes(NewCallPAL);
      Call->replaceAllUsesWith(NewCall);
      Call->eraseFromParent();
    }

    return true;
  }

未混淆IR

define i32 @main(i32 noundef %argc, i8** noundef %argv) #0 {
entry:
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp ne i32 %0, 1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

已混淆IR(-mllvm -irobf-indbr)

@main_IndirectCallees = private global [1 x i8*] [i8* getelementptr (i8, i8* bitcast (i32 (i8*, ...)* @printf to i8*), i32 -912149244)]
@llvm.compiler.used = appending global [1 x i8*] [i8* bitcast ([1 x i8*]* @main_IndirectCallees to i8*)], section "llvm.metadata"

; Function Attrs: noinline nounwind optnone ssp uwtable
define i32 @main(i32 %argc, i8** %argv) #0 {
entry:
  %CallerSlot = alloca i32, align 4
  %CalleeSlot = alloca i32, align 4
  store i32 -407925950, i32* %CallerSlot
  %MySecret = load i32, i32* %CallerSlot
  %retval = alloca i32, align 4
  %argc.addr = alloca i32, align 4
  %argv.addr = alloca i8**, align 8
  store i32 0, i32* %retval, align 4
  store i32 %argc, i32* %argc.addr, align 4
  store i8** %argv, i8*** %argv.addr, align 8
  %0 = load i32, i32* %argc.addr, align 4
  %cmp = icmp ne i32 %0, 1
  br i1 %cmp, label %if.then, label %if.end

if.then:                                          ; preds = %entry
  %call1 = load i8*, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @main_IndirectCallees, i32 0, i32 0)
  %1 = sub i32 504223294, %MySecret
  %2 = getelementptr i8, i8* %call1, i32 %1
  %Call_printf = bitcast i8* %2 to i32 (i8*, ...)*
  %call2 = call i32 (i8*, ...) %Call_printf(i8* getelementptr inbounds ([6 x i8], [6 x i8]* @.str, i64 0, i64 0))
  br label %if.end

if.end:                                           ; preds = %if.then, %entry
  ret i32 0
}

OLLVM对抗

D810

angr