首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
11
7
[原创]从源码视角分析Arkari间接跳转混淆
发表于: 2025-3-11 19:52
40561
[原创]从源码视角分析Arkari间接跳转混淆
Je2em1ah
2025-3-11 19:52
40561
## 前言 本文仅对Arkari用于混淆的pass的源码分析和讲解,没有提供去除混淆的方案,请需要去混淆的师傅自己想出解决方案,希望分析能够帮到各位师傅 文章大部分对源码的解释都直接以注释的形式写在代码中 > PS: 本文所有分析均基于开源项目Arkari:<a href="elink@222K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6w2L8$3#2A6e0h3!0W2i4K6u0r3b7i4u0C8j5i4u0A6">Arkari</a> LLVM19.x 架构为ARM64 ## Arkari支持的混淆特性 - 混淆过程间相关 - 间接跳转,并加密跳转目标(`-mllvm -irobf-indbr`) - 间接函数调用,并加密目标函数地址(`-mllvm -irobf-icall`) - 间接全局变量引用,并加密变量地址(`-mllvm -irobf-indgv`) - 字符串(c string)加密功能(`-mllvm -irobf-cse`) - 过程相关控制流平坦混淆(`-mllvm -irobf-cff`) - 整数常量加密(`-mllvm -irobf-cie`) - 浮点常量加密(`-mllvm -irobf-cfe`) ## 编译Arkari源码 ```bash 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 ``` ## 间接跳转介绍 在ARM64架构中,间接跳转混淆是一种通过**破坏静态控制流可读性**的代码保护技术,其核心机制是将程序中的**直接跳转(如`B`/`BL`指令)替换为通过通用寄存器(如X17)间接跳转**的模式,常见形式为`BR X8`等。由于IDA等静态反编译工具无法确定寄存器中的值,导致程序原始控制流在静态分析下被截断,使得静态分析失效。 ## 源码分析 间接跳转混淆的源码在编译好之后的`Arkari/llvm/lib/Transforms/Obfuscation`路径下的`IndirectBranch.cpp`文件 ```C 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; } ``` 这是`IndirectBranch.cpp`的`runOnFunction`函数,在OLLVM中runOnFunction函数是**pass**执行的**入口点** ### Step 1:随机打乱基本块 在函数中函数首先初始化了两个数组: - `BBNumbering` : 存储基本块到随机化编号的映射,用于后续计算跳转索引 - `BBTargets` : 临时存储所有唯一条件分支目标块 > 这里需要提一下,OLLVM中一个函数对象(Function)是由基本块(BasicBlock)组成,而基本块由每一条指令(Instruction)组成 接着调用`SplitAllCriticalEdges`函数,分割关键边,原理是在有多个前驱和后继的边之间**插入新基本块** 再调用`NumberBasicBlock`函数,这个函数将所有**条件分支目标基本块**分配随机化编号,打乱程序原有的顺序执行流程 ```C 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++; // 打乱的基本块重新编号 } } ``` 代码的注释中已经写的很详细,总结一下就是:**寻找条件跳转的后继基本块->随机数打乱基本块->基本块重编号** ### Step 2:初始化加密密钥 #### **1.密钥生成** ```C // 生成 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); // 零值常量 ``` #### **2. 全局变量声明** ```C GlobalVariable *GXorKey = nullptr; // 一级异或密钥存储 GlobalVariable *DestBBs = nullptr; // 加密跳转表 GlobalVariable *XorKeys = nullptr; // 动态异或密钥池 ``` | 变量名 | 类型 | 作用 | | --------- | ----------------- | ------------------------------ | | `GXorKey` | `GlobalVariable*` | 存储全局异或密钥(用于后续 Level 1 or 2 混淆) | | `DestBBs` | `GlobalVariable*` | 存储加密后的跳转地址数组(核心跳转表) | | `XorKeys` | `GlobalVariable*` | 存储动态生成的异或密钥数组(后续 Level 3 使用) | ### step 3:根据opt选择混淆强度 这里引用作者的话来解释: > 可以使用下列几种方法之一单独控制某个混淆Pass的强度 > (Win64-19.1.0-rc3-obf1.5.1-rc5 or later) > > 如果不指定强度则默认强度为0,annotate的优先级永远高于命令行参数 > > 可用的Pass: > - `icall` (强度范围: 0-3) > - `indbr` (强度范围: 0-3) > - `indgv` (强度范围: 0-3) > - `cie` (强度范围: 0-3) > - `cfe` (强度范围: 0-3) > > 1.通过**annotate**对特定函数指定混淆强度: > > `^flag=1` 表示当前函数设置某功能强度等级(此处为1) > > ```c > //^icall=表示指定icall的强度 > //+icall表示当前函数启用icall混淆, 如果你在命令行中启用了icall则无需添加+icall > > [[clang::annotate("+icall ^icall=3")]] > int main() { > std::cout << "HelloWorld" << std::endl; > return 0; > } > ``` > > 2.通过命令行参数指定特定混淆Pass的强度 > > Eg.间接函数调用,并加密目标函数地址,强度设置为3(`-mllvm -irobf-icall -mllvm -level-icall=3`) #### **控制逻辑** ```C 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; } ``` #### **Level 0** ```C 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; } ``` 总结下来`level 0`的处理就是生成一个**加密地址的全局变量数组(GV)**,可以用一个公式来概括:`加密地址 = 原始地址 + EncKey` 需要注意的是,EncKey是负数,所以代码上看起来是`加密地址 = 原始地址 - EncKey` #### **Level 1** ```C GlobalVariable *getIndirectTargets1(Function &F, ConstantInt *AddKey, ConstantInt *XorKey) const { std::string GVName(F.getName().str() + "_IndirectBrTargets1"); GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName); if (GV) return GV; // encrypt branch targets 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, ConstantExpr::getXor(AddKey, XorKey) //不同点 ); Elements.push_back(CE); } ArrayType *ATy =ArrayType::get( PointerType::getUnqual(F.getContext()), Elements.size() ); Constant *CA = ConstantArray::get(ATy, ArrayRef<Constant *>(Elements)); GV = new GlobalVariable( *F.getParent(), ATy, false, GlobalValue::LinkageTypes::PrivateLinkage, CA, GVName ); appendToCompilerUsed(*F.getParent(), {GV}); return GV; } ``` `level 1`主要的修改点就是`ConstantExpr::getXor(AddKey, XorKey)`这里,换成公式就是`加密地址 = 原始地址 + (AddKey ^ XorKey)` #### **Level 2** ```C GlobalVariable *getIndirectTargets2(Function &F, ConstantInt *AddKey, ConstantInt *XorKey) { std::string GVName(F.getName().str() + "_IndirectBrTargets2"); GlobalVariable *GV = F.getParent()->getNamedGlobal(GVName); if (GV) return GV; auto& Ctx = F.getContext(); IntegerType *intType = Type::getInt32Ty(Ctx); if (pointerSize == 8) { intType = Type::getInt64Ty(Ctx); } // encrypt branch targets std::vector<Constant *> Elements; for (auto BB:BBTargets) { Constant *CE = ConstantExpr::getBitCast( BlockAddress::get(BB), PointerType::getUnqual(F.getContext()) ); CE = ConstantExpr::getGetElementPtr( Type::getInt8Ty(F.getContext()), CE, ConstantExpr::getXor( //主要修改点 AddKey, ConstantExpr::getMul( XorKey, ConstantInt::get(intType, BBNumbering[BB], false) ) ) ); Elements.push_back(CE); } ArrayType *ATy =ArrayType::get(PointerType::getUnqual(F.getContext()), Elements.size()); Constant *CA = ConstantArray::get(ATy, ArrayRef<Constant *>(Elements)); GV = new GlobalVariable(*F.getParent(), ATy, false, GlobalValue::LinkageTypes::PrivateLinkage, CA, GVName); appendToCompilerUsed(*F.getParent(), {GV}); return GV; } ``` 同`level 1`,修改点在代码中已标出,公式:`加密地址 = 原始地址 + [AddKey ^ (XorKey * Idx)]` 稍微解释一下,level 0 和 level 1的key都是**固定**的,而level 2的key和**基本块的编号挂钩**,所以每个基本块对应的解密key都不一样 #### **Level 3** ```C std::pair<GlobalVariable *, GlobalVariable *> getIndirectTargets3(Function &F, ConstantInt *AddKey) { std::string GVNameAdd(F.getName().str() + "_IndirectBrTargets3"); std::string GVNameXor(F.getName().str() + "_IndirectBr3_Xor"); GlobalVariable *GVAdd = F.getParent()->getNamedGlobal(GVNameAdd); GlobalVariable *GVXor = F.getParent()->getNamedGlobal(GVNameXor); //新的XorKey数组全局变量 if (GVAdd && GVXor) return std::make_pair(GVAdd, GVXor); auto& Ctx = F.getContext(); IntegerType *intType = Type::getInt32Ty(Ctx); if (pointerSize == 8) { intType = Type::getInt64Ty(Ctx); } // encrypt branch targets std::vector<Constant *> Elements; std::vector<Constant *> XorKeys; for (auto BB:BBTargets) { uint64_t V = RandomEngine.get_uint64_t(); Constant *XorKey = ConstantInt::get(intType, V, false); Constant *CE = ConstantExpr::getBitCast( BlockAddress::get(BB), PointerType::getUnqual(F.getContext()) ); // 加密地址计算 // 与level 2相似,但是采用动态密钥 CE = ConstantExpr::getGetElementPtr( Type::getInt8Ty(F.getContext()), CE, ConstantExpr::getXor( AddKey, ConstantExpr::getMul( XorKey, ConstantInt::get(intType, BBNumbering[BB], false) ) ) ); //特别之处:使用密钥对原基本块地址加密之后,通过取反和异或等运算混淆密钥,使得原密钥更难被计算 XorKey = ConstantExpr::getNeg(XorKey); //XorKey = ~Xorkey XorKey = ConstantExpr::getXor(XorKey, AddKey); //XorKey ^= Addkey XorKey = ConstantExpr::getNeg(XorKey); //Xorkey = ~XorKey XorKeys.push_back(XorKey); Elements.push_back(CE); } ArrayType *ATy =ArrayType::get( PointerType::getUnqual(F.getContext()), Elements.size() ); Constant *CA = ConstantArray::get( ATy, ArrayRef<Constant *>(Elements) ); GVAdd = new GlobalVariable( *F.getParent(), ATy, false, GlobalValue::LinkageTypes::PrivateLinkage, CA, GVNameAdd ); appendToCompilerUsed(*F.getParent(), {GVAdd}); ArrayType *XTy = ArrayType::get(intType, XorKeys.size()); Constant *CX = ConstantArray::get(XTy, XorKeys); GVXor = new GlobalVariable( //把混淆后的密钥放到新的全局变量中 *F.getParent(), XTy, false, GlobalValue::LinkageTypes::PrivateLinkage, CX, GVNameXor ); appendToCompilerUsed(*F.getParent(), {GVXor}); return std::make_pair(GVAdd, GVXor); } ``` `level 3` 的加密公式可以这么表示: `加密地址 = 原始地址 + [EncKey1 ^ ( (XorKeys[Idx] ^ EncKey1) * Idx )]` `level 3`在`level 2`的基础上增加了对**密钥的混淆**。**完全随机化的动态密钥** + **双数组隔离** + **混淆**的设计,使得原来硬编码的密钥更加安全,在后续解密基本块地址的时候再对混淆的密钥进行还原,**这也是Arkari对间接跳转混淆的创新点所在** ### step 4:解密真实块地址并插入间接跳转指令 ```C for (auto &BB : Fn) { auto *BI = dyn_cast<BranchInst>(BB.getTerminator()); if (BI && BI->isConditional()) { //判断基本块结尾是否是条件跳转指令 IRBuilder<> IRB(BI); //获取IR代码的构造器 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); //构造CSEL指令 Value *GEP = IRB.CreateGEP(DestBBs->getValueType(), DestBBs,{Zero, Idx});//计算数组元素地址(DestBBs(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) { //生成level 1对应的解密密钥 DecKey = IRB.CreateXor(EncKey1, XorKey); DecKey = IRB.CreateNeg(DecKey); } else if (opt.level() == 2) { //生成level 2对应的解密密钥 DecKey = IRB.CreateXor(EncKey1, IRB.CreateMul(XorKey, Idx)); DecKey = IRB.CreateNeg(DecKey); } } if (XorKeys) { //生成level 3对应的解密密钥 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); //生成BR指令 IBI->addDestination(BI->getSuccessor(0)); //预先添加可能跳转的所有目标 IBI->addDestination(BI->getSuccessor(1)); //这里即当前块的两个后继(因为是条件跳转) ReplaceInstWithInst(BI, IBI); } } ``` 大概的过程就是如下所示(AI生成):  ## 总结 Hakari的间接跳转混淆增加了不同的混淆强度,尤其是Level 3的混淆,对密钥做了进一步混淆,使得通过原始计算密钥来恢复函数的控制流变得更加困难,如果想更加完美的解决混淆,用Angr强制程序走不同分支也许会更加合适
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
#逆向分析
#混淆加固
收藏
・
11
点赞
・
7
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
PLEBFE
为你点赞!
2025-12-1 04:40
Liv_T
+10
这个讨论对我很有帮助,谢谢!
2025-3-25 23:16
mb_zpyvrtaq
为你点赞!
2025-3-21 15:46
倾心Deep
非常支持你的观点!
2025-3-20 22:44
PPKun
谢谢你的细致分析,受益匪浅!
2025-3-16 14:13
令狐双
为你点赞!
2025-3-12 10:20
sinker_
你的帖子非常有用,感谢分享!
2025-3-12 08:51
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
倾心Deep
雪 币:
211
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
34
粉丝
0
关注
私信
倾心Deep
2
楼
请问在arm64架构上你是用什么编译的
2025-3-20 22:45
0
lichaolich
雪 币:
30
活跃值:
(3289)
能力值:
( LV2,RANK:10 )
在线值:
发帖
13
回帖
63
粉丝
77
关注
私信
lichaolich
3
楼
数据段设置只读就自动还原了,和没混淆一样
2026-3-13 13:42
0
Squirre17
雪 币:
0
活跃值:
(1019)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
9
粉丝
1
关注
私信
Squirre17
4
楼
间接跳转混淆有好的去除路子吗
2026-5-17 23:46
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Je2em1ah
2
发帖
11
回帖
20
RANK
关注
私信
他的文章
[原创]2025游戏安全技术竞赛-Android-初赛WriteUp
24640
[原创]从源码视角分析Arkari间接跳转混淆
40561
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部