本文仅对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();
BBNumbering.clear();
BBTargets.clear();
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");
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();
BBNumbering.clear();
BBTargets.clear();
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");
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();
for (unsigned I = 0; I < N; I++) {
BasicBlock *Succ = BI->getSuccessor(I);
if (BBNumbering.count(Succ) == 0) {
BBTargets.push_back(Succ);
BBNumbering[Succ] = 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();
for (unsigned I = 0; I < N; I++) {
BasicBlock *Succ = BI->getSuccessor(I);
if (BBNumbering.count(Succ) == 0) {
BBTargets.push_back(Succ);
BBNumbering[Succ] = 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++;
}
}
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);
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 使用) |
[[clang::annotate("+icall ^icall=3")]]
int main() {
std::cout << "HelloWorld" << std::endl;
return 0;
}
[[clang::annotate("+icall ^icall=3")]]
int main() {
std::cout << "HelloWorld" << std::endl;
return 0;
}
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;
}
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;
}
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);
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!