首页
社区
课程
招聘
6
[原创]从源码视角分析Arkari间接跳转混淆
发表于: 2025-3-11 19:52 7944

[原创]从源码视角分析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.cpprunOnFunction函数,在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 3level 2的基础上增加了对密钥的混淆完全随机化的动态密钥 + 双数组隔离 + 混淆的设计,使得原来硬编码的密钥更加安全,在后续解密基本块地址的时候再对混淆的密钥进行还原,这也是Arkari对间接跳转混淆的创新点所在

大概的过程就是如下所示(AI生成):
mermaid-diagram-1741693164097

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);
  // 创建全局变量

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 6
支持
分享
赞赏记录
参与人
雪币
留言
时间
Liv_T
+10
这个讨论对我很有帮助,谢谢!
7小时前
mb_zpyvrtaq
为你点赞!
4天前
mb_okvqvrhy
非常支持你的观点!
5天前
PPKun
谢谢你的细致分析,受益匪浅!
2025-3-16 14:13
令狐双
为你点赞!
2025-3-12 10:20
sinker_
你的帖子非常有用,感谢分享!
2025-3-12 08:51
最新回复 (1)
雪    币: 168
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
请问在arm64架构上你是用什么编译的
5天前
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册