尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Android APK 加固原理(二):从 DEX 解密到 ART 加载,如何缩短代码明文暴露窗口

Android APK 加固原理(二):从 DEX 解密到 ART 加载,如何缩短代码明文暴露窗口 Android APK 加固原理二从 DEX 解密到 ART 加载如何缩短代码明文暴露窗口基于 XopProtector 源码架构的技术分析项目地址https://github.com/xopJack/XopProtector在上一篇《Android APK 加固原理一Native Shell 如何隐藏和恢复 DEX》中我们分析了 Android APK 加固的第一道核心防线通过 ProxyApplication 接管应用启动再由 Native Shell 控制受保护 DEX 的恢复过程。但问题并没有结束。因为无论 APK Shell 做得多复杂只要应用最终需要执行代码就必须经历一个过程密文 ↓ 解密 ↓ 恢复 ↓ ART 加载 ↓ 执行这里存在一个 Android APK 加固中非常关键的问题从 DEX 被解密到真正进入 ART Runtime 执行中间会不会出现完整的明文代码如果答案是会那么攻击者就有机会Hook Dump Copy Monitor最终重新获得原始 DEX。因此真正的 APK 加固并不仅仅研究如何把 DEX 加密。更重要的是研究DEX 解密之后如何尽可能减少明文代码暴露的时间和空间。这就是本文要讨论的核心Plaintext Window —— 代码明文暴露窗口结合 XopProtector 的 DEX Runtime 设计本文将重点分析什么是 DEX 明文暴露窗口为什么传统 DEX 解密方案容易被 DumpXopProtector 为什么采用 Payload Restore 的设计什么是 Hollow Restore什么是 Class-Batch Restore为什么要在 ART Map 之前进行预处理Cold Start 与 Warm Start 为什么不能采用完全相同的策略如何在安全性和启动性能之间寻找平衡。一、什么是代码明文暴露窗口先来看一个最简单的 DEX 加密方案。构建阶段classes.dex │ ▼ Encrypt │ ▼ encrypted.dexAPK 中保存encrypted.dex应用启动后encrypted.dex │ ▼ Decrypt │ ▼ classes.dex │ ▼ Write To Disk │ ▼ DexClassLoader │ ▼ ART从功能角度来说这完全可以运行。但安全问题也非常明显。因为在Decrypt ↓ Plain classes.dex之后完整 DEX 已经重新出现。我们可以把这个阶段称为Plaintext Window也就是从代码恢复为明文到代码重新进入更受控的 Runtime 状态之间攻击者能够观察、读取或复制明文代码的时间窗口。可以简单表示为┌─────────────────────────────────────────────┐ │ │ │ Plaintext Window │ │ │ │ DEX 已经恢复为明文但仍可能被外部截获 │ │ │ └─────────────────────────────────────────────┘传统方案Encrypted DEX │ ▼ Decrypt │ ▼ ┌───────────────────┐ │ │ │ Plain DEX File │ ← 风险区域 │ │ └───────────────────┘ │ ▼ ART Load如果这个 DEX写入磁盘长时间存在存在完整副本可以被 Hook可以被内存 Dump那么前面的 DEX 加密价值就会大幅下降。二、为什么“解密后写入文件”并不安全假设一个加固方案采用assets/code.dat │ ▼ Decrypt │ ▼ /data/data/package/cache/classes.dex │ ▼ DexClassLoader对于攻击者来说只需要关注/data/data/package/或者cache/ code_cache/ files/在应用启动之后寻找classes.dex classes2.dex classes3.dex如果存在完整明文文件那么攻击过程可能重新变成启动 APK │ ▼ 等待 Shell 解密 │ ▼ 提取 classes.dex │ ▼ JADX这样Shell 最终只是增加了一步操作。攻击成本并没有真正提高多少。因此一个更合理的保护方案需要避免把完整的原始 DEX 长时间以普通文件形式重新暴露出来。XopProtector 的源码设计中明确围绕这一问题进行了优化其中包括no plaintext zipplaintext-window shrinkClass-batch hollow restoreParallel file prepatch before ART map这些设计的核心目标都是减少传统“完整 DEX 解密 → 明文长期存在”的攻击面。三、真正需要保护的不是 DEX 文件而是 DEX 生命周期这是理解 APK 加固的一个重要转变。很多人认为DEX 加密 保护 DEX 文件实际上更准确的理解应该是DEX Protection 保护代码生命周期一个 DEX 从构建到执行大致会经历Build │ ▼ Original DEX │ ▼ Protect │ ▼ Protected Payload │ ▼ APK │ ▼ Application Launch │ ▼ Decrypt │ ▼ Restore │ ▼ ART Mapping │ ▼ Execute真正需要控制的是Original DEX ↓ Protected Payload ↓ Runtime Restore ↓ ART中的每一个关键节点。因此XopProtector 的思路并不是单纯Encrypt ↓ Decrypt而是逐步控制Payload 如何保存什么时候解密解密哪些数据是否需要完整恢复是否需要生成完整 DEX如何在 ART Mapping 前完成处理如何减少明文数据的停留时间。四、XopProtector 的 DEX Payload 设计从 XopProtector 的保护资产结构可以看到受保护 APK 中包含code.bin dexes.zip config.json其中dexes.zip采用项目定义的PDX1格式承载 DEX 相关数据。而code.bin则用于保存运行时恢复所需要的代码数据。整体可以理解为Original APK │ ▼ XopProtector Packer │ ├── DEX Processing │ ├── Code Extraction │ ├── Payload Generation │ └── Shell Injection │ ▼ Protected APK │ ├── Shell DEX ├── libprotector.so │ └── Protected Assets │ ├── code.bin ├── dexes.zip └── config.json这种设计带来的第一个变化就是APK 中的业务代码不再只依赖普通 classes.dex 作为唯一存储形式。攻击者面对的不再是classes.dex ↓ JADX而是Shell ↓ Payload ↓ Native Runtime ↓ Restore因此攻击链首先被向后推进了一层。五、为什么不能简单地“全部解密”假设一个 APK 有classes.dex classes2.dex classes3.dex classes4.dex最直接的方式是Application Start │ ▼ Decrypt All │ ▼ Restore All │ ▼ ART Load这存在两个问题。问题一完整代码同时暴露如果一次性恢复classes.dex classes2.dex classes3.dex classes4.dex攻击者一旦找到正确的 Dump 时机就可能一次获得完整业务代码例如┌──────────────────────┐ │ classes.dex │ │ classes2.dex │ │ classes3.dex │ │ classes4.dex │ └──────────────────────┘全部处于可访问状态。问题二启动性能下降如果应用启动时Decrypt All Restore All Patch All那么CPU IO Memory都会产生额外开销。最终表现可能就是点击 APP ↓ 黑屏 ↓ 等待 ↓ 进入首页对于商业应用来说安全不能以严重破坏用户体验为代价。因此加固系统必须考虑Security Performance Compatibility三者之间的平衡。六、什么是 Hollow RestoreXopProtector 的 DEX Runtime 中包含Hollow Restore从技术思路上理解Hollow 可以看成保留 DEX 的整体结构或运行时定位能力将需要保护的实际代码数据抽离到独立 Payload 中。原始代码Class A ├── method1 ├── method2 └── method3 Class B ├── method1 └── method2保护之后可以抽象理解为DEX Structure │ ├── Class A │ ├── Method Slot │ ├── Method Slot │ └── Method Slot │ └── Class B ├── Method Slot └── Method Slot真正的代码则进入Protected Payload │ ├── Class A.method1 Code ├── Class A.method2 Code ├── Class A.method3 Code ├── Class B.method1 Code └── Class B.method2 Code运行时Hollow Structure │ ▼ Locate Protected Code │ ▼ Restore │ ▼ ART这样做的关键价值在于APK 中不再简单保存一份完整、直接可被反编译的业务 DEX。攻击者即使打开classes.dex看到的也可能只是部分结构信息而真正的 Code Item 或受保护代码数据需要经过 Runtime Restore 才能恢复。七、从 Hollow Restore 到 Class-Batch Restore如果恢复粒度仍然是全部恢复那么问题依然存在。因此 XopProtector 进一步引入Class-Batch Hollow Restore也就是以 Class 或 Class Batch 为单位进行恢复。传统方式Protected Payload │ ▼ Restore Everything │ ▼ Complete DEXBatch RestoreProtected Payload │ ├── Batch 1 │ ├── Batch 2 │ ├── Batch 3 │ └── Batch N恢复流程Application Startup │ ▼ Load Required Batch │ ▼ Restore │ ▼ ART Mapping │ ▼ Continue Startup从安全角度来看一次性完整恢复意味着攻击者只需要找到一个正确时机。而分批恢复则意味着攻击者需要理解 Restore Strategy ↓ 寻找多个恢复时机 ↓ 定位不同 Code Data ↓ 重新组织数据攻击复杂度明显提高。八、缩短明文窗口的核心思想所谓Plaintext-window shrink并不是说DEX 永远不会出现明文。这是不现实的。因为最终 ART 需要执行代码。真正的目标是减少暴露时间 减少完整副本 减少落地文件 减少无意义缓存可以理解为传统Decrypt │ ▼ Plain DEX │ │ ← 长时间存在 │ │ ▼ ART优化后的思路Decrypt │ ▼ Restore Required Data │ ▼ ART Mapping │ ▼ Execute目标就是让代码从受保护状态进入 Runtime 的路径尽可能短。也就是Protected ↓ Restore ↓ ART中间尽量避免Plain File ↓ Long-term Storage ↓ Copy ↓ Extra Cache九、为什么要避免 Plaintext ZIP传统打包方式中一个简单的思路可能是DEX Files │ ▼ ZIP │ ▼ Encrypt运行时Encrypted ZIP │ ▼ Decrypt │ ▼ Plain ZIP │ ▼ Extract DEX这样会产生两个暴露对象Plain ZIP以及Plain DEX因此Encrypted Payload ↓ Plain ZIP ↓ Plain DEX实际上增加了一层中间明文。XopProtector 在 DEX Runtime 优化中明确提出no plaintext zip其核心思路就是避免在恢复过程中产生不必要的完整明文压缩包。更合理的流程是Protected Payload │ ▼ Decrypt Required Data │ ▼ Direct Extract / Restore │ ▼ ART这样可以减少中间文件 完整副本 磁盘残留十、为什么 ART Mapping 是一个关键节点Android 应用最终需要通过 ART 来执行代码。因此ART Map可以看成 DEX 保护流程中的一个重要边界。完整流程Protected Code │ ▼ Native Restore │ ▼ DEX Ready │ ▼ ART Mapping │ ▼ ART Runtime │ ▼ Execution在 ART Mapping 之前Native Shell 仍然可以控制代码数据的恢复和预处理。进入 ART Runtime 之后代码已经开始进入 Android Runtime 的管理体系。因此XopProtector 在优化中加入Parallel file prepatch before ART map可以理解为在 ART 真正映射之前提前完成必要的恢复和 Patch 工作。整体Protected Payload │ ├──────────────┐ │ │ ▼ ▼ Decrypt Prepatch │ │ └──────┬───────┘ │ ▼ ART Map │ ▼ Execute这样可以减少ART 已经开始加载 ↓ Native Runtime 仍在大量处理带来的性能问题。十一、Parallel Prepatch 为什么重要假设所有工作都是串行的Start │ ▼ Decrypt │ ▼ Restore │ ▼ Patch │ ▼ ART Map │ ▼ Application整个启动时间可以表示为T Decrypt Restore Patch ART Map如果其中一部分工作可以提前或者并行┌── Decrypt ──┐ Start ────┤ ├── ART Map └── Prepatch ─┘那么关键启动路径就可能缩短。这就是Parallel file prepatch before ART map的意义。它不仅是性能优化。从安全角度来说也意味着减少恢复后的代码处于“已经明文但尚未进入 ART”的等待时间。因此它同时服务于Performance Plaintext Window Shrink十二、Cold Start 与 Warm Start 为什么需要不同策略这是商业 APK 加固中非常现实的问题。第一次启动应用Cold Start通常需要完成更多工作Read Payload ↓ Decrypt ↓ Extract ↓ Restore ↓ Prepare Runtime因此 XopProtector 中存在cold-start decrypt → extract pipeline的优化思路。而应用后续再次启动Warm Start如果仍然重复Read ↓ Decrypt ↓ Copy ↓ Write那么会产生大量不必要的IO Memory Copy CPU因此 XopProtector 的优化中还提出warm skip code.bin re-copy也就是说冷启动和后续启动不必采用完全相同的恢复路径。可以抽象为Cold StartProtected Payload │ ▼ Decrypt │ ▼ Extract │ ▼ Restore │ ▼ ARTWarm StartPrepared Runtime Data │ ▼ Skip Unnecessary Copy │ ▼ Restore Required State │ ▼ ART这种设计体现了一个重要原则安全系统不能只考虑第一次保护成功还必须考虑长期运行成本。十三、启动性能与安全性之间的平衡一个 APK 加固方案可能非常安全每次启动 ↓ 全部 DEX ↓ 强加密 ↓ 完整解密 ↓ 完整校验 ↓ 重新加载但如果最终启动时间 3 秒那么用户体验会明显下降。反过来如果为了性能第一次解密 ↓ 保存完整 DEX ↓ 以后直接加载那么安全性又会下降。因此真正的商业 APK 加固系统需要寻找Security ▲ │ │ │ Performance ─────┼───── Compatibility三个方向之间的平衡。XopProtector 的Class-Batch RestoreParallel PrepatchCold Start PipelineWarm Start SkipNo Plaintext ZIP本质上都是围绕这个目标进行优化。不是简单追求“最强加密”。而是在应用能够正常运行的前提下提高攻击成本同时控制启动性能。十四、从攻击者角度看 Plaintext Window如果没有控制明文窗口攻击者的流程可能非常简单APK │ ▼ Run │ ▼ Hook Decrypt │ ▼ Dump Memory │ ▼ Get classes.dex而加入 Shell Payload Restore 后APK │ ▼ Analyze Shell │ ▼ Locate Native Runtime │ ▼ Analyze Payload │ ▼ Understand Restore Flow │ ▼ Locate ART Mapping │ ▼ Find Correct Dump Point │ ▼ Reconstruct Code如果进一步采用Class-Batch Restore那么攻击者可能还需要处理Batch A Batch B Batch C Batch N重新组织恢复结果。攻击从拿到文件逐渐变成理解整个 Runtime这就是明文窗口缩短带来的真正价值。十五、完整的 XopProtector DEX Restore 流程结合 XopProtector 的整体设计可以将整个过程抽象为Original APK │ ▼ XopProtector Packer │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ Shell DEX Native Runtime Payload │ │ │ └────────────┼────────────┘ │ ▼ Protected APK │ ▼ App Launch │ ▼ ProxyApplication │ ▼ Native Bootstrap │ ▼ Read Protected Data │ ▼ Decrypt │ ▼ ┌──────────────────────┐ │ Class-Batch Restore │ └──────────────────────┘ │ ▼ Prepatch / Prepare │ ▼ ART Map │ ▼ Execution整个过程中核心目标始终没有变化让代码以尽可能短的路径从受保护状态进入可执行状态。而不是Decrypt ↓ 生成一份完整明文 ↓ 放在那里 ↓ 等 ART 加载十六、总结DEX 加密真正的难点不在加密而在恢复很多人第一次做 APK 加固时会把重点放在AES RC4 XOR ChaCha20到底使用哪一种算法。但实际上对于 APK Runtime Protection 来说更重要的问题是解密之后怎么办如果Encrypted DEX ↓ Decrypt ↓ Plain classes.dex ↓ 攻击者复制那么无论前面使用多强的加密算法最终仍然可能被绕过。因此真正的 DEX 加固应该关注何时恢复 恢复多少 在哪里恢复 是否产生完整副本 如何进入 ART 明文存在多久XopProtector 在 DEX Runtime 层中的设计方向正是围绕这些问题展开。通过Protected PayloadPDX1code.binHollow RestoreClass-Batch RestorePlaintext Window ShrinkNo Plaintext ZIPART Map 前 PrepatchCold Start PipelineWarm Start Optimization尝试重新控制 DEX 从Protected State到ART Execution之间的整个生命周期。最终APK 加固不再只是“给 DEX 加密。”而是“控制代码什么时候恢复、恢复到什么粒度以及明文代码在系统中存在多久。”这也是 Native Shell 技术进一步发展的重要方向。在 XopProtector 的整体保护体系中DEX Shell 和 Runtime Restore 解决的是如何隐藏代码。而接下来进一步进入的技术则是即使攻击者成功定位到代码也让其难以直接理解代码逻辑。下一篇我们将继续分析《Android APK 加固原理三方法级代码抽取——PVM1 虚拟化打包到底是什么》将从 XopProtector 的 PVM1 实现思路出发分析什么是方法级代码抽取什么是 Virtualized Packing为什么 PVM1 不等于真正的 Native InterpreterDalvik Code 如何被抽取Runtime 如何恢复方法代码PVM1 与传统 DEX Shell 的区别PVM1 与 PVM2 真虚拟化的区别。从 DEX 文件级保护进一步进入Method-Level Code Protection —— 方法级代码保护。
返回列表