尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CTF实战:Frida+DexDump动态脱壳破解安卓加固

CTF实战:Frida+DexDump动态脱壳破解安卓加固 1. 项目概述从一道CTF赛题看安卓动态脱壳的核心价值最近在复盘去年安恒杯春季赛的一道安卓逆向题“shield”这道题可以说把安卓加固和动态脱壳的核心攻防点展现得淋漓尽致。题目本身是一个经过商业加固保护的APK静态分析工具打开后关键的classes.dex文件要么被加密、要么被隐藏常规的静态反编译手段基本失效看到的只是一些壳的引导代码。这正是目前移动应用安全尤其是CTF竞赛和实际安全评估中越来越常见的场景——静态防御越来越强动态攻防成为突破口。这道“shield”题目的核心就是要求我们绕过加固保护从运行中的应用内存里把原始的、已解密的DEX文件给“掏”出来也就是所谓的“脱壳”。而完成这个任务我选择并最终验证有效的“黄金搭档”就是Frida和DexDump。Frida这个动态插桩框架让我们能够像手术刀一样精准地切入到目标应用的运行时进程而DexDump则是专门为Frida打造的一把“内存提取器”它能识别并导出内存中完整的DEX结构。这个过程不仅仅是点几下鼠标它涉及到对安卓ART/Dalvik虚拟机内存管理机制的理解、对加固壳加载时机的把握以及一套稳定的动态分析环境搭建。如果你正在学习安卓逆向或者对CTF中的移动安全题目感到头疼觉得加固像一堵密不透风的墙那么这次通过“shield”实战总结出的Frida-DexDump动态脱壳与源码还原全流程或许能给你提供一个清晰的破局思路。它不仅适用于CTF解题其原理和方法同样适用于对加固应用进行安全研究、漏洞挖掘的场景。接下来我就把这套从环境准备、脱壳操作到源码还原分析的完整经验毫无保留地拆解给你看。2. 核心思路与工具选型为什么是FridaDexDump面对一个被加固的应用我们首先要理解对手是怎么工作的。现代安卓加固技术俗称“壳”的核心思路大同小异在原始APK的外层包裹一层自定义的代码壳代码。当应用启动时首先执行的是这层壳代码。壳代码的责任是负责在内存中解密被加密或混淆的原始DEX文件然后通过动态加载技术如DexClassLoader将其加载到虚拟机中执行同时尽可能抹去磁盘和内存中的解密痕迹。这就导致我们直接解压APK得到的classes.dex是无效的静态分析工具如Jadx、GDA看到的是壳的逻辑。因此脱壳的突破口就在于“运行时内存”。既然壳最终要把原始代码交给虚拟机执行那么在某个时刻解密后的、完整的DEX字节码必然以某种结构存在于应用进程的内存空间中。我们的目标就是抓住这个时机把这个内存镜像完整地提取出来。基于这个思路动态脱壳工具应运而生而Frida和DexDump的组合因其灵活性和针对性成为了当前最主流的手动脱壳方案之一。2.1 Frida动态分析的“瑞士军刀”Frida不是一个专门的脱壳工具而是一个强大的动态代码插桩框架。它允许你将一段JavaScript或Python代码注入到目标进程无论是本地还是远程中从而能够Hook函数、调用方法、读写内存甚至修改逻辑。在脱壳场景下Frida的核心价值在于进程附着与控制我们可以将Frida的Agent注入到目标安卓应用的进程中获得对其运行时状态的完全控制能力。精准Hook关键点我们可以编写脚本Hook住壳在解密和加载DEX时必经的Android系统API或自定义函数。例如Hookdalvik.system.DexClassLoader或java.lang.ClassLoader的loadClass方法在关键逻辑执行前后进行拦截和内存扫描。内存操作能力Frida提供了强大的内存读写API如Memory.scan,Memory.readByteArray使得我们能够主动搜索和提取内存中的特定数据块如DEX文件头dex\n035\0。选择Frida而不是其他工具如Xposed的原因在于其跨平台支持Android/iOS/Windows/macOS/Linux、脚本化无需重启设备或应用以及对Native层和Java层无差别的Hook能力这为应对复杂的、涉及Native代码的加固壳提供了可能。2.2 DexDump专为Frida打造的内存DEX提取器理论上只用Frida的API我们通过扫描内存、识别DEX头、计算长度也能把DEX文件抠出来。但这个过程繁琐且容易出错尤其是面对多DEX或内存中有多个DEX副本的情况。DexDump的出现完美地解决了这个痛点。DexDump本质上是一个开源的Frida脚本通常是一个.js文件。它的工作原理非常聪明遍历内存映射它利用Frida的Process.enumerateRangesAPI获取目标进程所有的内存区域ranges。智能识别DEX结构它不仅仅搜索dex\n035或dex\n038这样的文件头魔数。一个有效的DEX在内存中不仅有文件头其内部结构如字符串池、类型池、方法池的偏移和索引也必须自洽。DexDump会进行初步的校验过滤掉那些只是偶然出现魔数但实为无效数据的区域。重建与导出对于识别出的、可能是有效DEX的内存区域DexDump会尝试按照DEX文件格式将其数据完整地拷贝出来保存为本地文件如dump_0xXXXX.dex。在“shield”这道题中直接使用DexDump脚本往往就能在应用启动后的几秒内自动完成对内存中所有潜在DEX的扫描和导出极大提高了脱壳效率。它的优势在于“开箱即用”省去了手动编写复杂内存扫描和校验逻辑的麻烦。2.3 环境搭配与选型考量在实际操作中我通常使用以下环境组合测试设备首选安卓模拟器如雷电模拟器、夜神模拟器。原因在于模拟器环境纯净、可快照恢复、易于Root且屏幕录制和文件传输方便。对于“shield”这类CTF题目模拟器完全够用。当然真机需Root也是可以的。Frida环境在电脑攻击机上安装Frida的Python包pip install frida-tools同时需要将对应版本的frida-server推送到安卓设备模拟器中并运行。这里有一个关键点Frida客户端与Server的版本必须严格匹配否则会出现连接失败或协议错误。DexDump脚本从GitHub获取最新版的dexdump.js。这个组合的选型逻辑很清晰Frida提供底层能力和入口DexDump提供针对性的、高层的自动化操作。两者结合形成了一个从侵入到提取的完整管道。对于初学者理解这个分工协作的关系比死记硬背命令更重要。3. 实战环境搭建与前期准备理论清晰了接下来就是动手搭建一个稳定的“作战平台”。这个过程有些繁琐但每一步的稳定性都直接关系到后续脱壳能否成功。我会以在Windows 11系统下使用雷电模拟器Android 9为例详细演示整个环境搭建过程。3.1 模拟器配置与Root安装与设置模拟器下载并安装雷电模拟器。安装完成后进入其设置界面。最关键的一步是开启Root权限。在雷电模拟器的“系统设置”或“属性设置”中通常有明确的“Root开关”将其打开。然后重启模拟器。验证Root重启后安装一个终端应用如Termux或者通过ADB连接后执行adb shell然后输入su命令。如果命令提示符从$变成了#并且没有弹出权限拒绝的提示说明Root成功。调整网络可选但重要为了让电脑和模拟器处于同一网络便于通信建议将模拟器的网络设置从默认的“桥接”或“NAT”改为“网络桥接模式”并指定一个与电脑主机在同一网段的静态IP或者确保电脑能通过ADB可靠连接。更简单通用的方法是直接使用ADB。3.2 Frida环境部署客户端与Server端这是最容易出错的一环务必仔细。电脑端安装Frida在电脑的命令行CMD或PowerShell中执行pip install frida-tools。这会同时安装frida和frida-tools包含frida-psfrida等命令行工具。安装完成后用frida --version检查版本例如输出16.1.4。获取匹配的frida-server访问Frida的GitHub Release页面。根据你模拟器或真机的CPU架构通常安卓模拟器是x86或x86_64真机多是arm或arm64以及上一步查到的Frida版本号下载对应的frida-server-xx.x.x-android-x.x.x.xz文件。对于雷电模拟器Android 9通常下载x86_64架构的版本。推送并运行frida-server解压下载的.xz文件得到一个名为frida-server-xx.x.x-android-x86_64的可执行文件。使用ADB命令将其推送到模拟器的/data/local/tmp目录并赋予执行权限。adb push frida-server-xx.x.x-android-x86_64 /data/local/tmp/frida-server adb shell su cd /data/local/tmp chmod 755 frida-server在模拟器的/data/local/tmp目录下以后台方式运行frida-server./frida-server 。注意观察命令行如果没有报错且光标停留在新的一行通常表示启动成功。验证连接新开一个电脑端的命令行窗口执行frida-ps -U。这个命令的意思是列出通过USB-U连接的设备上的所有进程。如果一切正常你会看到一个长长的进程列表其中包含com.android.settingscom.tencent.mm如果你装了微信等。看到这个列表就证明Frida环境打通了。注意每次重启模拟器后frida-server进程会关闭需要重新进入/data/local/tmp目录执行./frida-server 来启动。你可以编写一个简单的脚本来自动化这个过程。3.3 目标应用与脱壳脚本准备安装目标APK将“shield”题目的APK文件假设名为shield.apk拖入模拟器界面安装或者使用adb install shield.apk命令安装。获取DexDump脚本从GitHub例如hluwa等作者维护的仓库下载dexdump.js脚本保存到电脑的某个目录例如D:\CTF\Tools\dexdump.js。初步静态分析可选用解压软件打开shield.apk查看lib目录下有无可疑的so文件可能是加固壳的Native组件用文本编辑器打开AndroidManifest.xml查看入口Activity。这一步不是为了破解而是为了对目标有个基本认知比如入口是com.xxx.xxx.MainActivity。环境至此准备完毕。总结一下关键检查点模拟器Root成功、frida-server在设备上运行、frida-ps -U能列出进程、目标应用已安装、DexDump脚本在手。4. 动态脱壳操作全流程实录环境就绪真正的“狩猎”开始。动态脱壳的核心在于时机我们要在壳完成解密、DEX已加载到内存但尚未被虚拟机完全优化或销毁之前完成内存抓取。下面是最常用的两种方法我会结合“shield”题目进行说明。4.1 方法一应用启动时附着并脱壳通用方法这是最直接、最常用的方法适用于大多数在应用启动初期就完成解密加载的壳。启动Frida并附着目标应用我们使用frida -U命令在应用启动时注入我们的脚本。命令格式如下frida -U -f com.anheng.shield -l D:\CTF\Tools\dexdump.js --no-pause-U: 使用USB连接设备。-f com.anheng.shield:-f后面跟的是目标应用的包名假设为com.anheng.shield这个参数会让Frida先启动这个应用。-l D:\CTF\Tools\dexdump.js:-l指定要加载的JavaScript脚本这里就是我们的DexDump脚本。--no-pause: 立即启动应用不要暂停。对于脱壳来说我们需要应用尽快跑起来让壳代码执行。观察脚本输出与交互执行上述命令后Frida会启动目标应用并将dexdump.js脚本注入进去。脚本会自动开始工作。你会在命令行中看到大量的输出信息这是DexDump在遍历内存区域、识别和导出DEX。[雷电模拟器::com.anheng.shield]- [DEXDump] Found target [com.anheng.shield] (pid: 12345) [DEXDump] Enumerate memory ranges... [DEXDump] Filter range: 0x1000 - 0x2000 (r-x) [anon:linker_alloc] [DEXDump] Filter range: 0x7fe1234000 - 0x7fe1255000 (rw-) [anon:libc_malloc] [DEXDump] Dump dex from range: 0x7fe1234000 - 0x7fe1255000, size: 0x21000 [DEXDump] Dex size: 0x1F800, Save to: dump_0x7fe1234000.dex [DEXDump] Dump dex from range: 0x7ff5678000 - 0x7ff569a000, size: 0x22000 [DEXDump] Dex size: 0x20800, Save to: dump_0x7ff5678000.dex输出中会显示它过滤了哪些无关的内存区域以及在哪个内存地址范围发现了可能是DEX的数据并将其保存为文件。这些dump_xxxxx.dex文件默认会保存在运行Frida命令的当前目录下。手动触发关键逻辑如果需要有些壳可能不会在应用一启动就解密所有代码而是等到用户点击某个按钮、进入某个界面时才动态加载。如果启动时脱壳得到的DEX不完整或不是核心逻辑你需要让应用运行起来手动操作到那个关键界面然后在Frida会话仍然连接的情况下在命令行中手动执行DexDump脚本中的扫描函数如果脚本提供了相应的导出函数例如scandex()。或者更简单的方法是在应用进入关键界面后按CtrlC中断当前Frida会话然后重新执行附着命令但不用-f参数而是用-n附着到正在运行的进程再次运行脱壳脚本。4.2 方法二Hook关键类加载函数精准打击对于某些狡猾的壳或者你想更深入地理解脱壳过程可以尝试手动编写Frida脚本Hook特定的类加载函数。这种方法更有针对性但需要一些编程知识。编写简易Hook脚本创建一个新的JS文件比如hook_dexload.js。Java.perform(function() { // Hook java.lang.ClassLoader 的 loadClass 方法 var classLoader Java.use(\java.lang.ClassLoader\); classLoader.loadClass.overload(java.lang.String).implementation function(name) { console.log(\[*] ClassLoader.loadClass called for: \ name); // 在这里可以调用DexDump的逻辑或者直接进行内存扫描 // 例如可以调用一个全局的dump函数 if (name.indexOf(\com.anheng.shield\) ! -1) { // 过滤特定包名 console.log(\[!] Target class loading triggered, dumping memory...\); // 这里可以嵌入或调用DexDump的核心扫描代码 } return this.loadClass(name); // 继续执行原方法 }; // 也可以Hook DexClassLoader 或 PathClassLoader 的构造函数 var dexClassLoader Java.use(\dalvik.system.DexClassLoader\); dexClassLoader.$init.overload(java.lang.String, java.lang.String, java.lang.String, java.lang.ClassLoader).implementation function(dexPath, optimizedDirectory, librarySearchPath, parent) { console.log(\[*] DexClassLoader created for path: \ dexPath); console.log(\[*] This is a strong indicator of dynamic loading! Time to dump!\); // 立即执行内存dump // ... 调用dump逻辑 ... return this.$init(dexPath, optimizedDirectory, librarySearchPath, parent); }; });结合DexDump功能上面的脚本只是打印日志。为了真正脱壳你需要将DexDump脚本中扫描和保存DEX的核心函数通常是一个名为dump或scan的函数整合进来或者在Hook到关键点后直接执行外部DexDump脚本。一种更实用的方式是先运行dexdump.js脚本它通常会导出一个全局函数如scandex。然后在你的Hook脚本中在合适的时机如loadClass被调用时通过setTimeout或直接调用的方式触发这个全局函数。在“shield”的实战中使用方法一直接运行dexdump.js通常就足够了。当应用启动后脚本会自动跑完在当前目录生成若干个.dex文件。你需要做的就是从这些文件中找到那个正确的、包含主要业务逻辑的DEX。4.3 脱壳后的文件筛选与初步验证DexDump可能会生成多个dex文件因为内存中可能存在系统库的dex、壳自身的dex、以及多个业务dex。如何找到我们想要的看大小壳的dex通常较小几十到几百KB而主要业务dex会比较大可能几MB。shield题目脱出来的主要dex可能在1MB以上。用工具验证最直接的方法是用反编译工具如Jadx-gui逐个打开这些dex文件。打开后查看包结构。真正的业务dex其包名层级会与目标应用相关如com.anheng.shield并且内部会有大量的自定义类和方法。而壳的dex或系统dex包名通常是com.wrapper、com.shell、android.*、java.*等。定位关键代码在Jadx中打开疑似正确的dex搜索题目可能相关的字符串如“flag”、“check”、“success”、“error”等或者查看MainActivity之类的入口类。如果能找到清晰可读的业务逻辑代码说明脱壳成功。5. 源码还原分析与Flag获取成功脱出正确的DEX文件就像拿到了一个加密盒子的钥匙。接下来就是用这把钥匙打开盒子解读里面的秘密。对于CTF题目最终目的是找到Flag。使用反编译工具静态分析将筛选出的主DEX文件拖入Jadx-gui。Jadx会尝试将其反编译成尽可能接近原始Java代码的形式。浏览MainActivity或主要的逻辑处理类。分析“shield”题目逻辑在“shield”这道题中经过脱壳和反编译我们可能会发现核心的验证逻辑。例如代码可能包含一个checkPassword函数它将用户输入与一个经过复杂运算可能是AES、DES、或自定义算法的字符串进行比较。或者Flag可能被拆分隐藏在资源文件、Native So库或需要满足特定条件才能触发显示。动态调试验证可选但推荐静态分析得出的结论有时需要动态验证。我们可以再次使用Frida但这次是用于主动调用和调试。例如如果我们静态分析发现一个getFlag()方法可以直接写Frida脚本去调用它Java.perform(function() { var MainActivity Java.use(\com.anheng.shield.MainActivity\); // 假设getFlag是静态方法 var flag MainActivity.getFlag(); console.log(\[*] The Flag is: \ flag); });或者Hook某个判断函数直接修改其返回值让程序走入显示Flag的分支。处理Native层加固进阶有些强壳会把关键逻辑放到Native层C/C代码编译的.so文件里。如果反编译Java层后找不到核心逻辑就需要分析lib目录下的.so文件。这时可以使用IDA Pro、Ghidra等工具进行逆向分析同时结合Frida去Hook so文件导出的JNI函数如Java_com_anheng_shield_MainActivity_encrypt或内部函数来动态追踪数据流和逻辑。这属于更高级的范畴但思路是一致的动静结合用Frida去验证静态分析的猜想。在“shield”的实战中通过上述步骤最终在脱壳后的DEX里通过Jadx分析MainActivity发现了一个简单的字符串比较逻辑将输入与一个硬编码的、经过Base64编码的字符串进行比较解码后即可获得Flag。整个过程的关键突破口就在于成功使用FridaDexDump完成了动态脱壳让被隐藏的代码“原形毕露”。6. 常见问题、排查技巧与避坑指南在实际操作中你几乎一定会遇到各种问题。下面是我踩过坑后总结的一些常见问题及解决方案希望能帮你节省大量时间。6.1 Frida连接与注入失败问题执行frida-ps -U提示Failed to enumerate processes: unable to connect to remote frida-server。排查检查设备连接adb devices确认设备已连接。检查frida-server在设备shell中执行ps | grep frida-server看进程是否存在。如果不存在回到/data/local/tmp目录重新启动。检查端口冲突frida-server默认使用TCP端口27042。确保该端口没有被占用。可以尝试在设备上用netstat -tulpn | grep 27042查看。检查版本匹配这是最常见的问题务必用frida --version和frida-server --version在设备上运行./frida-server --version检查两端版本号是否完全一致。关闭冲突软件某些电脑安全软件或防火墙可能会拦截ADB或Frida通信尝试暂时关闭。6.2 DexDump脚本运行无输出或找不到DEX问题脚本执行了但输出很少也没有生成dump文件或者生成的dex用Jadx打开是空的或错误的。排查时机不对壳可能还没有完成解密加载。尝试让应用完全启动并手动操作到核心界面后再运行脚本或者使用setTimeout延迟执行脚本中的扫描函数。内存搜索范围或特征问题有些壳会修改DEX文件头魔数或者将DEX结构打散。可以尝试使用更新版或修改版的DexDump脚本它们可能包含了更多的特征码或更智能的搜索算法。也可以手动编写Frida脚本搜索dex\n035、dex\n038或壳可能使用的自定义特征。应用有反调试/反Frida检测这是高级壳的常见手段。应用会检测Frida的存在如检测端口、进程名、文件特征等如果发现则退出或执行垃圾代码。对策包括使用隐蔽模式Frida有--enable-soft-ptrace等参数或使用frida-server的改名版本。Patch反检测代码静态分析找到检测点用Frida Hook并绕过。使用其他工具辅助如objection基于Frida的anti-anti-frida插件。6.3 脱出的DEX反编译后代码混乱或不全问题Jadx打开dex后看到很多类名是乱码或者方法体是空的、只有return语句。原因与对策抽取型加固这是目前最棘手的加固方式之一。壳并不完整加密整个DEX而是在运行时动态地“抽取”每个方法的字节码只在执行前才还原。内存中dump下来的DEX其方法体CodeItem可能是空的或被填充了无意义指令。对付这种壳需要在每个方法被解释执行或JIT编译的瞬间去Hook并dump其完整的CodeItem。这需要更精细的Frida脚本或者使用专门的工具如FRIDA-DEXDump的增强版、Youpk等。字符串加密类名、方法名、字符串常量被加密。脱壳只解决了代码结构问题但字符串内容仍是密文。这需要进一步分析解密函数并用Frida动态执行解密逻辑或者写IDAPython脚本在静态分析时进行解密。DEX优化格式ART虚拟机下DEX可能被优化成OAT或VDEX格式。DexDump有时能直接dump出DEX有时dump出的是OAT需要工具如oat2dex进行转换。6.4 性能与稳定性问题内存占用过大DexDump扫描整个内存空间可能比较慢在低配设备上可能导致应用卡顿甚至崩溃。可以尝试在脚本中限制扫描的内存区域范围或者选择在应用启动后、界面完全加载的相对空闲期进行dump。脚本崩溃复杂的Frida脚本可能与目标应用或Frida自身版本存在兼容性问题。简化脚本逻辑确保错误处理try-catch并更新到稳定的Frida版本。最后的个人心得安卓脱壳是一场与加固方案的“猫鼠游戏”。没有一成不变的银弹。FridaDexDump这套组合拳攻防的是2020-2022年间大部分主流加固的基础版本。面对不断升级的壳尤其是VMP、深度抽取需要更深入的理解和更定制化的工具。我的建议是从“shield”这类基础题目入手彻底吃透内存dump的原理然后尝试分析一些带有简单反Frida检测的样本学习如何绕过。当你能够熟练地运用Frida去Hook、修改、追踪内存中的关键数据时你就已经掌握了动态分析最核心的武器足以应对大部分CTF逆向题目和相当一部分商业应用的初步安全分析了。记住思路比工具更重要理解“为什么这么做”远比记住“怎么做”有价值。
返回列表