-
-
[原创]从源码视角分析Arkari间接跳转混淆
-
发表于: 2025-3-11 19:52 7944
-
本文仅对Arkari用于混淆的pass的源码分析和讲解,没有提供去除混淆的方案,请需要去混淆的师傅自己想出解决方案,希望分析能够帮到各位师傅
文章大部分对源码的解释都直接以注释的形式写在代码中
PS: 本文所有分析均基于开源项目Arkari:Arkari LLVM19.x 架构为ARM64
在ARM64架构中,间接跳转混淆是一种通过破坏静态控制流可读性的代码保护技术,其核心机制是将程序中的直接跳转(如B
/BL
指令)替换为通过通用寄存器(如X17)间接跳转的模式,常见形式为BR X8
等。由于IDA等静态反编译工具无法确定寄存器中的值,导致程序原始控制流在静态分析下被截断,使得静态分析失效。
间接跳转混淆的源码在编译好之后的Arkari/llvm/lib/Transforms/Obfuscation
路径下的IndirectBranch.cpp
文件
这是IndirectBranch.cpp
的runOnFunction
函数,在OLLVM中runOnFunction函数是pass执行的入口点
在函数中函数首先初始化了两个数组:
这里需要提一下,OLLVM中一个函数对象(Function)是由基本块(BasicBlock)组成,而基本块由每一条指令(Instruction)组成
接着调用SplitAllCriticalEdges
函数,分割关键边,原理是在有多个前驱和后继的边之间插入新基本块
再调用NumberBasicBlock
函数,这个函数将所有条件分支目标基本块分配随机化编号,打乱程序原有的顺序执行流程
代码的注释中已经写的很详细,总结一下就是:寻找条件跳转的后继基本块->随机数打乱基本块->基本块重编号
这里引用作者的话来解释:
可以使用下列几种方法之一单独控制某个混淆Pass的强度
(Win64-19.1.0-rc3-obf1.5.1-rc5 or later)
如果不指定强度则默认强度为0,annotate的优先级永远高于命令行参数
可用的Pass:
1.通过annotate对特定函数指定混淆强度:
^flag=1
表示当前函数设置某功能强度等级(此处为1)
2.通过命令行参数指定特定混淆Pass的强度
Eg.间接函数调用,并加密目标函数地址,强度设置为3(-mllvm -irobf-icall -mllvm -level-icall=3
)
总结下来level 0
的处理就是生成一个加密地址的全局变量数组(GV),可以用一个公式来概括:加密地址 = 原始地址 + EncKey
需要注意的是,EncKey是负数,所以代码上看起来是加密地址 = 原始地址 - EncKey
level 1
主要的修改点就是ConstantExpr::getXor(AddKey, XorKey)
这里,换成公式就是加密地址 = 原始地址 + (AddKey ^ XorKey)
同level 1
,修改点在代码中已标出,公式:加密地址 = 原始地址 + [AddKey ^ (XorKey * Idx)]
稍微解释一下,level 0 和 level 1的key都是固定的,而level 2的key和基本块的编号挂钩,所以每个基本块对应的解密key都不一样
level 3
的加密公式可以这么表示: 加密地址 = 原始地址 + [EncKey1 ^ ( (XorKeys[Idx] ^ EncKey1) * Idx )]
level 3
在level 2
的基础上增加了对密钥的混淆。完全随机化的动态密钥 + 双数组隔离 + 混淆的设计,使得原来硬编码的密钥更加安全,在后续解密基本块地址的时候再对混淆的密钥进行还原,这也是Arkari对间接跳转混淆的创新点所在
大概的过程就是如下所示(AI生成):
Hakari的间接跳转混淆增加了不同的混淆强度,尤其是Level 3的混淆,对密钥做了进一步混淆,使得通过原始计算密钥来恢复函数的控制流变得更加困难,如果想更加完美的解决混淆,用Angr强制程序走不同分支也许会更加合适
git clone https:
//github
.com
/KomiMoe/Arkari
.git
cd
Arkari
mkdir
build
cd
build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang -G
"Unix Makefiles"
..
/llvm
make
-j12
git clone https:
//github
.com
/KomiMoe/Arkari
.git
cd
Arkari
mkdir
build
cd
build
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang -G
"Unix Makefiles"
..
/llvm
make
-j12
bool
runOnFunction(Function &Fn) override {
const
auto
opt = ArgsOptions->toObfuscate(ArgsOptions->indBrOpt(), &Fn);
if
(!opt.isEnabled()) {
return
false
;
}
if
(Fn.empty() || Fn.hasLinkOnceLinkage() || Fn.getSection() ==
".text.startup"
) {
return
false
;
}
LLVMContext &Ctx = Fn.getContext();
// Init member fields
BBNumbering.clear();
BBTargets.clear();
// llvm cannot split critical edge from IndirectBrInst
SplitAllCriticalEdges(Fn, CriticalEdgeSplittingOptions(nullptr, nullptr));
NumberBasicBlock(Fn);
if
(BBNumbering.empty()) {
return
false
;
}
uint64_t V = RandomEngine.get_uint64_t();
uint64_t XV = RandomEngine.get_uint64_t();
IntegerType* intType = Type::getInt32Ty(Ctx);
if
(pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}
ConstantInt *EncKey = ConstantInt::get(intType, V,
false
);
ConstantInt *EncKey1 = ConstantInt::get(intType, -V,
false
);
ConstantInt *Zero = ConstantInt::get(intType, 0);
GlobalVariable *GXorKey = nullptr;
GlobalVariable *DestBBs = nullptr;
GlobalVariable *XorKeys = nullptr;
if
(opt.level() == 0) {
DestBBs = getIndirectTargets0(Fn, EncKey1);
}
else
if
(opt.level() == 1 || opt.level() == 2) {
ConstantInt *CXK = ConstantInt::get(intType, XV,
false
);
GXorKey =
new
GlobalVariable(*Fn.getParent(), CXK->getType(),
false
, GlobalValue::LinkageTypes::PrivateLinkage,
CXK, Fn.getName() +
"_IBrXorKey"
);
appendToCompilerUsed(*Fn.getParent(), {GXorKey});
if
(opt.level() == 1) {
DestBBs = getIndirectTargets1(Fn, EncKey1, CXK);
}
else
{
DestBBs = getIndirectTargets2(Fn, EncKey1, CXK);
}
}
else
{
auto
[fst, snd] = getIndirectTargets3(Fn, EncKey1);
DestBBs = fst;
XorKeys = snd;
}
for
(
auto
&BB : Fn) {
auto
*BI = dyn_cast<BranchInst>(BB.getTerminator());
if
(BI && BI->isConditional()) {
IRBuilder<> IRB(BI);
Value *Cond = BI->getCondition();
Value *Idx;
Value *TIdx, *FIdx;
TIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(0)]);
FIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(1)]);
Idx = IRB.CreateSelect(Cond, TIdx, FIdx);
Value *GEP = IRB.CreateGEP(
DestBBs->getValueType(), DestBBs,
{Zero, Idx});
Value *EncDestAddr = IRB.CreateLoad(
GEP->getType(),
GEP,
"EncDestAddr"
);
// -EncKey = X - FuncSecret
Value *DecKey = EncKey;
if
(GXorKey) {
LoadInst *XorKey = IRB.CreateLoad(GXorKey->getValueType(), GXorKey);
if
(opt.level() == 1) {
DecKey = IRB.CreateXor(EncKey1, XorKey);
DecKey = IRB.CreateNeg(DecKey);
}
else
if
(opt.level() == 2) {
DecKey = IRB.CreateXor(EncKey1, IRB.CreateMul(XorKey, Idx));
DecKey = IRB.CreateNeg(DecKey);
}
}
if
(XorKeys) {
Value *XorKeysGEP = IRB.CreateGEP(XorKeys->getValueType(), XorKeys, {Zero, Idx});
Value *XorKey = IRB.CreateLoad(intType, XorKeysGEP);
XorKey = IRB.CreateNeg(XorKey);
XorKey = IRB.CreateXor(XorKey, EncKey1);
XorKey = IRB.CreateNeg(XorKey);
DecKey = IRB.CreateXor(EncKey1, IRB.CreateMul(XorKey, Idx));
DecKey = IRB.CreateNeg(DecKey);
}
Value *DestAddr = IRB.CreateGEP(
Type::getInt8Ty(Ctx),
EncDestAddr, DecKey);
IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
IBI->addDestination(BI->getSuccessor(0));
IBI->addDestination(BI->getSuccessor(1));
ReplaceInstWithInst(BI, IBI);
}
}
return
true
;
}
bool
runOnFunction(Function &Fn) override {
const
auto
opt = ArgsOptions->toObfuscate(ArgsOptions->indBrOpt(), &Fn);
if
(!opt.isEnabled()) {
return
false
;
}
if
(Fn.empty() || Fn.hasLinkOnceLinkage() || Fn.getSection() ==
".text.startup"
) {
return
false
;
}
LLVMContext &Ctx = Fn.getContext();
// Init member fields
BBNumbering.clear();
BBTargets.clear();
// llvm cannot split critical edge from IndirectBrInst
SplitAllCriticalEdges(Fn, CriticalEdgeSplittingOptions(nullptr, nullptr));
NumberBasicBlock(Fn);
if
(BBNumbering.empty()) {
return
false
;
}
uint64_t V = RandomEngine.get_uint64_t();
uint64_t XV = RandomEngine.get_uint64_t();
IntegerType* intType = Type::getInt32Ty(Ctx);
if
(pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}
ConstantInt *EncKey = ConstantInt::get(intType, V,
false
);
ConstantInt *EncKey1 = ConstantInt::get(intType, -V,
false
);
ConstantInt *Zero = ConstantInt::get(intType, 0);
GlobalVariable *GXorKey = nullptr;
GlobalVariable *DestBBs = nullptr;
GlobalVariable *XorKeys = nullptr;
if
(opt.level() == 0) {
DestBBs = getIndirectTargets0(Fn, EncKey1);
}
else
if
(opt.level() == 1 || opt.level() == 2) {
ConstantInt *CXK = ConstantInt::get(intType, XV,
false
);
GXorKey =
new
GlobalVariable(*Fn.getParent(), CXK->getType(),
false
, GlobalValue::LinkageTypes::PrivateLinkage,
CXK, Fn.getName() +
"_IBrXorKey"
);
appendToCompilerUsed(*Fn.getParent(), {GXorKey});
if
(opt.level() == 1) {
DestBBs = getIndirectTargets1(Fn, EncKey1, CXK);
}
else
{
DestBBs = getIndirectTargets2(Fn, EncKey1, CXK);
}
}
else
{
auto
[fst, snd] = getIndirectTargets3(Fn, EncKey1);
DestBBs = fst;
XorKeys = snd;
}
for
(
auto
&BB : Fn) {
auto
*BI = dyn_cast<BranchInst>(BB.getTerminator());
if
(BI && BI->isConditional()) {
IRBuilder<> IRB(BI);
Value *Cond = BI->getCondition();
Value *Idx;
Value *TIdx, *FIdx;
TIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(0)]);
FIdx = ConstantInt::get(intType, BBNumbering[BI->getSuccessor(1)]);
Idx = IRB.CreateSelect(Cond, TIdx, FIdx);
Value *GEP = IRB.CreateGEP(
DestBBs->getValueType(), DestBBs,
{Zero, Idx});
Value *EncDestAddr = IRB.CreateLoad(
GEP->getType(),
GEP,
"EncDestAddr"
);
// -EncKey = X - FuncSecret
Value *DecKey = EncKey;
if
(GXorKey) {
LoadInst *XorKey = IRB.CreateLoad(GXorKey->getValueType(), GXorKey);
if
(opt.level() == 1) {
DecKey = IRB.CreateXor(EncKey1, XorKey);
DecKey = IRB.CreateNeg(DecKey);
}
else
if
(opt.level() == 2) {
DecKey = IRB.CreateXor(EncKey1, IRB.CreateMul(XorKey, Idx));
DecKey = IRB.CreateNeg(DecKey);
}
}
if
(XorKeys) {
Value *XorKeysGEP = IRB.CreateGEP(XorKeys->getValueType(), XorKeys, {Zero, Idx});
Value *XorKey = IRB.CreateLoad(intType, XorKeysGEP);
XorKey = IRB.CreateNeg(XorKey);
XorKey = IRB.CreateXor(XorKey, EncKey1);
XorKey = IRB.CreateNeg(XorKey);
DecKey = IRB.CreateXor(EncKey1, IRB.CreateMul(XorKey, Idx));
DecKey = IRB.CreateNeg(DecKey);
}
Value *DestAddr = IRB.CreateGEP(
Type::getInt8Ty(Ctx),
EncDestAddr, DecKey);
IndirectBrInst *IBI = IndirectBrInst::Create(DestAddr, 2);
IBI->addDestination(BI->getSuccessor(0));
IBI->addDestination(BI->getSuccessor(1));
ReplaceInstWithInst(BI, IBI);
}
}
return
true
;
}
void
NumberBasicBlock(Function &F) {
for
(
auto
&BB : F) {
// 遍历所有基本块
if
(
auto
*BI = dyn_cast<BranchInst>(BB.getTerminator())) {
// 获取基本块的最后一条跳转指令
if
(BI->isConditional()) {
// 判断是否是条件跳转
unsigned N = BI->getNumSuccessors();
// N为分支数
for
(unsigned I = 0; I < N; I++) {
BasicBlock *Succ = BI->getSuccessor(I);
// 获取后继基本块
if
(BBNumbering.count(Succ) == 0) {
// 检查Succ是否已存在于map中
BBTargets.push_back(Succ);
BBNumbering[Succ] = 0;
// 添加到BBTargets并初始化编号为0
}
}
}
}
}
long
seed = RandomEngine.get_uint32_t();
// 随机数种子
std::default_random_engine e(seed);
std::shuffle(BBTargets.begin(), BBTargets.end(), e);
// 用随机数打乱
unsigned N = 0;
for
(
auto
BB:BBTargets) {
BBNumbering[BB] = N++;
// 打乱的基本块重新编号
}
}
void
NumberBasicBlock(Function &F) {
for
(
auto
&BB : F) {
// 遍历所有基本块
if
(
auto
*BI = dyn_cast<BranchInst>(BB.getTerminator())) {
// 获取基本块的最后一条跳转指令
if
(BI->isConditional()) {
// 判断是否是条件跳转
unsigned N = BI->getNumSuccessors();
// N为分支数
for
(unsigned I = 0; I < N; I++) {
BasicBlock *Succ = BI->getSuccessor(I);
// 获取后继基本块
if
(BBNumbering.count(Succ) == 0) {
// 检查Succ是否已存在于map中
BBTargets.push_back(Succ);
BBNumbering[Succ] = 0;
// 添加到BBTargets并初始化编号为0
}
}
}
}
}
long
seed = RandomEngine.get_uint32_t();
// 随机数种子
std::default_random_engine e(seed);
std::shuffle(BBTargets.begin(), BBTargets.end(), e);
// 用随机数打乱
unsigned N = 0;
for
(
auto
BB:BBTargets) {
BBNumbering[BB] = N++;
// 打乱的基本块重新编号
}
}
// 生成 64 位随机密钥
uint64_t V = RandomEngine.get_uint64_t();
uint64_t XV = RandomEngine.get_uint64_t();
// 根据指针大小选择整数类型
IntegerType* intType = Type::getInt32Ty(Ctx);
if
(pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}
// 构造加密常数(核心密钥)
ConstantInt *EncKey = ConstantInt::get(intType, V,
false
);
// 加密密钥
ConstantInt *EncKey1 = ConstantInt::get(intType, -V,
false
);
// 解密密钥
ConstantInt *Zero = ConstantInt::get(intType, 0);
// 零值常量
// 生成 64 位随机密钥
uint64_t V = RandomEngine.get_uint64_t();
uint64_t XV = RandomEngine.get_uint64_t();
// 根据指针大小选择整数类型
IntegerType* intType = Type::getInt32Ty(Ctx);
if
(pointerSize == 8) {
intType = Type::getInt64Ty(Ctx);
}
// 构造加密常数(核心密钥)
ConstantInt *EncKey = ConstantInt::get(intType, V,
false
);
// 加密密钥
ConstantInt *EncKey1 = ConstantInt::get(intType, -V,
false
);
// 解密密钥
ConstantInt *Zero = ConstantInt::get(intType, 0);
// 零值常量
GlobalVariable *GXorKey = nullptr;
// 一级异或密钥存储
GlobalVariable *DestBBs = nullptr;
// 加密跳转表
GlobalVariable *XorKeys = nullptr;
// 动态异或密钥池
GlobalVariable *GXorKey = nullptr;
// 一级异或密钥存储
GlobalVariable *DestBBs = nullptr;
// 加密跳转表
GlobalVariable *XorKeys = nullptr;
// 动态异或密钥池
变量名 | 类型 | 作用 |
---|---|---|
GXorKey |
GlobalVariable* |
存储全局异或密钥(用于后续 Level 1 or 2 混淆) |
DestBBs |
GlobalVariable* |
存储加密后的跳转地址数组(核心跳转表) |
XorKeys |
GlobalVariable* |
存储动态生成的异或密钥数组(后续 Level 3 使用) |
//^icall=表示指定icall的强度
//+icall表示当前函数启用icall混淆, 如果你在命令行中启用了icall则无需添加+icall
[[clang::annotate(
"+icall ^icall=3"
)]]
int
main() {
std::cout <<
"HelloWorld"
<< std::endl;
return
0;
}
//^icall=表示指定icall的强度
//+icall表示当前函数启用icall混淆, 如果你在命令行中启用了icall则无需添加+icall
[[clang::annotate(
"+icall ^icall=3"
)]]
int
main() {
std::cout <<
"HelloWorld"
<< std::endl;
return
0;
}
if
(opt.level() == 0) {
DestBBs = getIndirectTargets0(Fn, EncKey1);
//level 0 的处理逻辑
}
else
if
(opt.level() == 1 || opt.level() == 2) {
//level 1 和 2 共同的逻辑
ConstantInt *CXK = ConstantInt::get(intType, XV,
false
);
GXorKey =
new
GlobalVariable(
//生成用于XOR的key的全局变量
*Fn.getParent(),
CXK->getType(),
false
,
GlobalValue::LinkageTypes::PrivateLinkage,
CXK,
Fn.getName() +
"_IBrXorKey"
);
appendToCompilerUsed(*Fn.getParent(), {GXorKey});
if
(opt.level() == 1) {
DestBBs = getIndirectTargets1(Fn, EncKey1, CXK);
//level 1 方案
}
else
{
DestBBs = getIndirectTargets2(Fn, EncKey1, CXK);
//level 2 方案
}
}
else
{
auto
[fst, snd] = getIndirectTargets3(Fn, EncKey1);
//level 3 方案
DestBBs = fst;
XorKeys = snd;
}
if
(opt.level() == 0) {
DestBBs = getIndirectTargets0(Fn, EncKey1);
//level 0 的处理逻辑
}
else
if
(opt.level() == 1 || opt.level() == 2) {
//level 1 和 2 共同的逻辑
ConstantInt *CXK = ConstantInt::get(intType, XV,
false
);
GXorKey =
new
GlobalVariable(
//生成用于XOR的key的全局变量
*Fn.getParent(),
CXK->getType(),
false
,
GlobalValue::LinkageTypes::PrivateLinkage,
CXK,
Fn.getName() +
"_IBrXorKey"
);
appendToCompilerUsed(*Fn.getParent(), {GXorKey});
if
(opt.level() == 1) {
DestBBs = getIndirectTargets1(Fn, EncKey1, CXK);
//level 1 方案
}
else
{
DestBBs = getIndirectTargets2(Fn, EncKey1, CXK);
//level 2 方案
}
}
else
{
auto
[fst, snd] = getIndirectTargets3(Fn, EncKey1);
//level 3 方案
DestBBs = fst;
XorKeys = snd;
}
GlobalVariable *getIndirectTargets0(Function &F, ConstantInt *EncKey)
const
{
std::string GVName(F.getName().str() +
"_IndirectBrTargets"
);
GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName);
//获取对应函数的加密地址的全局变量
if
(GV)
return
GV;
//如果已经生成过全局变量,则直接返回,防止重复生成
std::vector<Constant *> Elements;
//存储加密后的基本块地址
//遍历需要加密的基本块
for
(
const
auto
BB:BBTargets) {
// 获取基本块地址并转换为通用指针类型
Constant *CE = ConstantExpr::getBitCast(
BlockAddress::get(BB),
PointerType::getUnqual(F.getContext())
);
// 加密基本块地址
CE = ConstantExpr::getGetElementPtr(
Type::getInt8Ty(F.getContext()),
CE,
//原始地址
EncKey
//加密偏移
);
Elements.push_back(CE);
}
// 定义数组类型
ArrayType *ATy = ArrayType::get(
PointerType::getUnqual(F.getContext()),
Elements.size()
// 数组长度
);
// 将加密地址封装为常量数组
Constant *CA = ConstantArray::get(ATy, Elements);
// 创建全局变量
GV =
new
GlobalVariable(
*F.getParent(),
// 所属模块
ATy,
// 数组类型
false
,
// 是否常量
GlobalValue::LinkageTypes::PrivateLinkage,
// 链接属性
CA,
// 初始值
GVName
// 变量名
);
// 防止优化器删除该全局变量
appendToCompilerUsed(*F.getParent(), {GV});
return
GV;
}
GlobalVariable *getIndirectTargets0(Function &F, ConstantInt *EncKey)
const
{
std::string GVName(F.getName().str() +
"_IndirectBrTargets"
);
GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName);
//获取对应函数的加密地址的全局变量
if
(GV)
return
GV;
//如果已经生成过全局变量,则直接返回,防止重复生成
std::vector<Constant *> Elements;
//存储加密后的基本块地址
//遍历需要加密的基本块
for
(
const
auto
BB:BBTargets) {
// 获取基本块地址并转换为通用指针类型
Constant *CE = ConstantExpr::getBitCast(
BlockAddress::get(BB),
PointerType::getUnqual(F.getContext())
);
// 加密基本块地址
CE = ConstantExpr::getGetElementPtr(
Type::getInt8Ty(F.getContext()),
CE,
//原始地址
EncKey
//加密偏移
);
Elements.push_back(CE);
}
// 定义数组类型
ArrayType *ATy = ArrayType::get(
PointerType::getUnqual(F.getContext()),
Elements.size()
// 数组长度
);
// 将加密地址封装为常量数组
Constant *CA = ConstantArray::get(ATy, Elements);
// 创建全局变量