
拿到目标样本之后我盯着AndroidManifest.xml看了很久。入口Activity被换成了com.stub.StubActivityApplication换成了com.stub.StubAppassets目录里躺着一堆看不出用途的加密文件classes.dex本体却只有寥寥几百行代码关键逻辑全部被抽空。不用猜这是360企业加固的典型特征而且这个加固版本还走了Native抽取还原的路线普通的内存dump根本不够用。这次实战要解决的核心问题有三个第一怎么突破libjiagu对DexLoader和ArtMethod的保护把真正运行的代码完整捞出来第二如何处理被抽空的方法体也就是Native抽取还原让还原后的dex可以放进jadx里正常看逻辑第三加固把入口类和Activity生命周期全部改掉了在还原之后还要把Application和Activity的生命周期重新接起来不然就算dump出代码也没法动态调试。这篇文章我会从样本分析到Frida脚本编写再到dex修复和生命周期重建把整条路径完整过一遍适合已经掌握基础Android逆向、但对加固对抗还比较陌生的朋友。1. 这次实战要解决的问题1.1 样本的基本画像先说样本本身。这是一个带360企业级加固的APK包名不去说它重要的是它的特征和市面上大多数商业加固一致原始dex被整体加密藏进了assets目录应用真正的逻辑其实存在于运行时才释放的dex或者so里。如果你只拿jadx打开classes.dex会看到StubApp、StubActivity这类傀儡类真正的业务代码一个都看不到。这里要区分一个概念360加固特别是企业版并不只是简单地把dex加密。它有两层手段叠加一层是壳负责在so层完成解密和动态加载另一层是抽取会在解密后的dex基础上把某些类的方法实现从dex里抹掉等运行时再通过Native层把方法体送进内存。这两层手段叠加以后传统脱壳工具拉出来的dex往往是残缺的方法体是空的根本没法静态分析。所以我这次没有选择现成一键脱壳工具而是手动走了一遍流程从定位so、内存dump、修复dex到重建生命周期。手动走一遍的好处是能搞清楚加固到底在Native层做了什么后续遇到其他加固或更新的版本也能举一反三。1.2 加固的本质藏与抽加固的攻防其实是“藏代码”和“找代码”的对抗。最早只是把整个dex加密运行时解密后load进内存那脱壳就是等它解密完dump一份完整dex就行。后来加固厂商发现这样太容易被dump就开始做抽取不是整个dex藏起来而是把每个类里敏感方法的方法体抽掉只保留方法签名和少量信息。运行时当程序真正调用某个方法时Native层再把方法体临时填回去执行完甚至再抹掉。360企业加固的抽取不是只抽一两个方法而是大范围抽取有的类甚至所有方法都被抽空。我打开dump出来的dex时发现很多方法在code_item里只剩一个空的NOP或者异常跳转这其实就是抽取后的样子。这也意味着光dump内存镜像还不够必须让方法真真正正跑一次让Native层把代码填回去我们才能抓到完整体。这里要特别强调一点抽取还原和整体dump是两个层面的事情。整体dump解决的是“dex不知道在哪”的问题抽取还原解决的是“代码明明就在内存却抓不完整”的问题。这次实战两个问题都涉及了这也是为什么标题里要把Native抽取还原单独提出来。1.3 适合谁来读这篇这篇文章的技术路径并不简单所以我先把读者对象说清楚。如果你已经会frida的基本hook能看懂smali也大概知道ArtMethod、code_item是什么那这篇就是给你准备的。如果还不太清楚dex文件格式、ArtMethod结构建议先花半小时补一下基础再来。从应用场景来说这套能力在几个方向很实用恶意样本分析时很多恶意APK都会上一道加固不脱壳根本拿不到行为代码网络爬虫方向经常要分析App的sign参数加固后的App不还原出来连算法函数都找不到另外做合规检测、SDK行为审计时也会遇到加固样本手动还原是最稳的办法。当然所有分析都要在法律允许的范围内进行自己的样本、已授权的样本、公开的CTF样本都可以别拿这套东西去碰未经授权的商业应用。2. 环境准备与工具选型2.1 测试机与运行环境这次实战强烈建议用一台root过的真机而不是模拟器。原因很简单360加固的so对模拟器检测非常敏感在模拟器里可能走的是另一套分支逻辑会出现行为不一致的情况分析结果不可靠。真机选择上Pixel系列、一加、小米这些解锁方便的机型都行Android版本建议在8到12之间太高或太低都会遇到额外问题。我用的是一台Android 10的小米手机Magisk已root并且安装了最新的ZygiskShamiko模块。为什么要提Shamiko因为加固样本和它的保护模块可能会检测root环境如果检测到root可能出现故意崩溃、代码路径改变等情况。Shamiko能把Magisk的痕迹隐掉一部分降低被反调试机制干扰的概率。测试机还有一点要注意把目标APK装上去之后先不要急着启动先用adb shell去看/data/data/包名/ 目录能不能正常访问。如果加固有反调试策略很多操作都要在它启动的瞬间抢时间完成所以提前把Frida server准备好、把脚本放在顺手的位置都是在给后面的实战节省时间。2.2 核心工具清单我把这次实战用到的工具分成三类静态分析、动态注入、文件修补。核心清单如下工具用途版本建议jadx-gui静态查看smali/java代码1.4.7 以上Frida / frida-tools动态hook与脚本注入frida 16.xobjection快速枚举与内存dump辅助最新010 Editordex头文件修复、十六进制对比最新dex2jar / jd-clidex转jar辅助阅读任意baksmalismali级修改与重打包2.xGDA静态动态结合分析任意Frida这把钥匙是核心。整个对抗过程中hook dlopen、hook ArtMethod、内存扫描、主动调用全靠它。jadx用来做还原前和还原后的对比判断方法体是否真正被补全。010 Editor虽然在很多人看来只是一个十六进制工具但处理dex头修复和code_item回填的时候非常好用比写脚本还要直观。2.3 Frida与调试环境的部署细节Frida server版本必须和电脑上的frida-tools版本严格匹配这地方我踩过坑版本不匹配时注入直接报错连登录加载脚本都做不到。先把server推到手机/data/local/tmp/frida-server然后chmod 755用root权限跑起来。手机端不需要额外装App。电脑端我习惯用虚拟环境部署frida-tools避免全局Python环境被搞乱。部署完以后先跑frida-ps -U验证和手机的连接能看到进程列表就说明基础链路通了。有一点要提前说明360加固对Frida是有检测的包括检测frida-server的默认端口27042、检测maps里有没有frida相关so等。如果运行中直接被崩溃不要慌可以尝试改Frida默认端口或者在启动AGENT的时候指定其他监听端口。更狠的样本会扫描线程名里有没有gmain等特征但企业版大部分情况没那么变态默认配置加上端口修改足够应付。3. 360企业加固的加载流程拆解3.1 修改入口的套路加固之后APK的AndroidManifest会被重写。原始入口类会被替换为加固自带的代理入口类这样做的好处是系统启动App时实际上启动的是代理入口原始Application和Activity类根本不会直接被系统加载加固可以在代理入口里完成“解密dex、加载业务代码、调用Application.attach”这一整套动作。以360为例代理Application通常是com.stub.StubApp代理Activity是com.stub.StubActivity。你如果把classes.dex拖进jadx看到的几乎只有这些stub类。系统启动app的时候先实例化StubAppStubApp的attachBaseContext里会把业务odex加载出来这才是原始代码首次进入DexClassLoader的时刻。理解了这个机制你就知道对抗的切入点在哪了。一旦StubApp触发了真正的加载逻辑业务dex就会出现在内存中我们要做的就是在这个时间窗口之后去枚举ClassLoader、遍历业务dex、dump出完整镜像。整篇文章的实战顺序其实也就是沿着这条加载链往下走。3.2 Native层与DexHelper360企业加固真正核心的逻辑并不在Java层而是在libjiagu.so和libjiagu_a64.so这两个Native库里。armeabi-v7a走的是libjiagu.soarm64-v8a走的是libjiagu_a64.so。我这次分析的是64位应用所以目标集中在libjiagu_a64.so。在Native层加固自实现了一套类加载逻辑其中有个类名就叫DexHelper这个类承担了从加密数据中提取dex、创建DexClassLoader、记录ArtMethod状态等一系列核心功能。通过IDA打开libjiagu_a64.so在符号表里能看到DexHelper相关的导出或者内部符号比如loadDex、dumpDex、readDex等。这些符号名每一版都在变但构造函数和关键虚表位置的模式是可以归纳的。DexHelper这类类和系统类加载器不同之处在于它把ArtMethod的code_entry抓在手里。通过修改ArtMethod里的成员值加固可以在运行时把某个方法的code_entry替换成自己的stub当这个方法被调用时stub会先跳转到Native的还原函数把真正的code_item填进内存再跳回原逻辑执行。3.3 抽取与动态还原的机制这一节把抽取还原机制讲透。一个ArtMethod在Art运行时的核心结构大概是这样dex_method_index用来定位它在原dex中的方法索引ptr_sized_fields中有一个字段指向方法当前生效的code_entry这个code_entry可以是编译后的机器码也可以是经过解释器包装的跳转入口。加固抽取方法后ArtMethod的code_entry被指向了一个自定义的trampoline跳板。这个trampoline本身是native代码它知道方法原本对应的dex_method_index。当方法第一次被调用时trampoline会调用加固自己的还原函数根据dex_method_index去查它保存的“原始code_item数据”把它写回到dex对应的位置再把ArtMethod的code_entry恢复成正常解释器入口最后才执行真正的代码。这里有一个关键点抽取的方法不是被动等调用就能全部还原的。如果一个方法在整个运行过程中根本没被调用过那它永远是残缺状态这也给dump完整dex增加了难度。于是就有了“主动调用”思路通过代码扫描所有已加载类的所有方法用反射或者直接构造调用去触发每个方法让它们的code_item被还原然后趁机dump。业内常见的FART脱壳机就是这个思路的具体实现我这次实战也借鉴了它的一部分手法但没有用现成工具而是写脚本自己控制。4. Native层定位与Dex内存转储4.1 从dlopen切入要分析加固的Native代码第一步是搞清楚libjiagu_a64.so是什么时候被加载的。系统启动App时StubApp在Java层调用System.loadLibrary(jiagu)之后so才被映射进进程空间。因此hook dlopen就是一个完美的切入方式。用Frida可以在该方法被调用时打印so名字并检查是否为libjiagu。这里要注意Android 7.0以上系统里dlopen会被android_dlopen_ext封装所以hook的时候最好两个一起hook避免漏掉。更简单的做法是直接遍历/proc/pid/maps看libjiagu_a64.so的加载地址但动态hook dlopen能拿到加载时刻这对设置后续断点非常关键。我自己比较喜欢在dlopen返回的瞬间去hookJNI_OnLoad因为此时so的基址已经确定我们可以快速计算函数偏移再在关键Native函数入口下断点。这个过程有点像打猎先看到猎物进了林子再盯着脚印追过去。4.2 定位关键函数so加载后下一步是定位DexHelper相关函数。这里有两类办法。第一类是静态定位用IDA加载libjiagu_a64.so等自动分析完成后打开Exports窗口搜索DexHelper、loadDex、dumpDex这类字符串。360企业版虽然做了符号混淆但部分的导出符号还是会留下线索尤其是和JNI导出相关的函数。第二类是动态定位在Frida里枚举so导出函数找到包含DexHelper特征的关键函数然后逐个hook观察哪个函数会触发dex解密。实际操作中我会先hookpthread_create很多加固会把副线程当作解密dex的工厂通过观察新线程的回调地址就能反推关键Native函数位置。还有一个屡试不爽的办法监控/proc/self/maps在App运行后每隔几毫秒扫描一次整个进程的内存映射看有没有新的匿名映射区域出现这些区域往往是dex解密后的落座位置。找到内存区域后再进一步读取数据校验是否可以导出为dex。4.3 内存扫描与DexDump当DexClassLoader加载完原始dex后dex的数据会以完整形式存在于内存中并且通常会以dex magicdex\n035或者dex\n037开头。这时我们直接暴力扫描整个heap区域就能找到dex。扫描逻辑并不复杂遍历所有可读内存段对每段数据逐个字节检查是否存在64 65 78 0a 03 35这样的魔数字节序列。找到之后打印内存地址和长度再把整块数据写入本地文件。这个思路也是很多一键dump工具的底层原理只不过我把这个过程写成了自己的Frida脚本这样能更精准地控制dump时机。dump时机是这个环节最要命的问题。太早了业务dex还没加载扫不到太晚了可能已经触发了反调试。实际操作中我一般用StubApplication的attachBaseContext之后作为起点延迟1到2秒再开始扫描基本都能拿到dex。如果扫到多个dex样本就按时间戳逐个保存最后再用哈希去重。4.4 修复Dex头部与歧义处理扫描到的内存数据并不一定是一份结构完整的dex文件。因为加载器在解析dex的时候很多字段可能已经经过修改比如class_defs_size、data_off、map_off会被调整或者dex header本身被局部修改。如果直接把内存里的原始字节写出来用jadx打开可能报错或者打开之后完全看不到类。解决思路是先校验dex header的magic和file_size然后检查header里的map_off、data_off是否超出了文件大小。如果超出则需要根据dex文件格式手工修复。常见的修复动作包括将checksum和signature重新计算置为合法值调整file_size为实际dump长度把map_off指向合理的map_list偏移。好在dump出的dex大部分情况都还是完整的真正需要做深度修复的内存dex其实是极少数。但抽取还原不同。内存中的dex即使结构完整方法体也已经被抽空用jadx打开能看到方法名点进去看不到代码。所以下面要单独拿一大段来说Native抽取还原这才是这次实战中技术含量最高的部分。5. Native抽取还原5.1 什么是抽空的方法体在抽取型加固的眼里方法分两类有的敏感方法在加载时就被抹掉方法体被替换成简短的返回或者异常跳转有的则是在运行前才动态填回。用010 Editor直接打开dump出来的dex去看class_data_item里对应方法的code_off会发现一个危险的现象code_off指向的位置只有几个字节根本不够一个完整的Dalvik指令序列。这就是抽空的直观表现。一个正常的方法在dalvik指令里应该有完整的code_item结构包括寄存器数、指令大小、try-catch信息、insns数组。而抽空之后这些信息只剩一个占位符比如一条return-void或者nop。对于逆向分析者来说这种状态下的dex几乎没有分析价值。我们看到的类名、方法名、参数、返回值都在表示结构信息还在但方法内部做了什么完全看不出来。所以必须想办法让加固在运行时把完整的code_item还回来。5.2 捕获ArtMethod的CodeEntry在Android Art运行时interpreter模式下方法执行的入口是ArtMethod的code_entry字段。抽取型加固通常会在方法第一次调用前把该字段设置为trampoline让执行流跳到Native还原函数。Frida里可以直接篡改ArtMethod结构体读取code_entry当前值并把它的调用过程打印出来。具体做法先通过Java反射拿到一个方法对应的ArtMethod指针然后读取ptr_sized_fields_从中取code_entry。在Frida中可以用Module.findBaseAddress(libart.so)来辅助定位ArtMethod结构相关偏移。但这个偏移量在不同Android版本、不同架构下不一样需要按设备实际计算。我的脚本里封装了getArtMethodCodeEntry和setArtMethodCodeEntry两个函数前者读取后者写入。捕获策略分为两步先对启动期的所有方法做一次遍历记录初始code_entry状态随后再定期扫描找出code_entry发生变化的那些方法。这些方法就是被抽取后又被动态还原的方法它们身上一定发生过我们最关心的还原动作。5.3 主动调用模式只等系统自己调用方法效率太低很多方法根本不会被触发。所以要引入主动调用模式思路就是FART脱壳机的核心逻辑遍历所有已加载的ClassLoader对每个ClassLoader中的每个类反射初始化它然后逐个调用类里所有非抽象方法让这些方法的code_item在运行时被还原。这一步听着简单实际坑很多。有些方法调用会抛出异常导致进程崩溃比如某些方法需要特殊参数或者状态。我的Frida脚本里做了三层防护用Java.use去反射类方法调用统一抛到子线程里执行对会抛异常的方法做try-catch有些类初始化本身会触发反调试这类类我会跳过并记录日志。虽然不能保证100%触发所有方法但大部分敏感方法的code_item都能在主动调用后被还原。主动调用结束之后再次扫描dump出来的dex你会发现很多之前是空的code_off现在指向了一段真正包含dalvik指令的区域。到这一步Native抽取还原的“抽取”已经被打破了剩下的就是数据层面的回填。5.4 将代码段回填Dex回填逻辑要解决的核心问题是如何把运行时还原出的方法体写回静态dex文件里让它永久完整。思路是这样的从已被主动调用触发还原的ArtMethod中拿到code_entry对应的指令数据同时通过dex_method_index定位它在原始dex里的位置。然后将指令数据复制到修复后的dex文件中对应的code_off处更新method相关的code_item字段。因为在运行时加固已经把数据按照原始dex的位置放好了回填最稳妥的方式其实是直接基于完整内存镜像重新切片把每个方法的code_item从内存中拷贝回去。实际操作中如果要处理的类很多手工用010 Editor一个个改不现实。所以我主要是写Frida脚本在内存里完成回填将修复好的dex通过Frida的send接口传到电脑再保存到磁盘。整个流程可以这样描述内存Dex镜像作为基础对每个方法比对运行时code_entry是否为正常地址如果正常就把该地址处的数据写回dex同时修正code_item的长度字段。回填完成后把修复后的dex拖进jadx验证方法体里的smali指令应该能直接看到了。这里你可能会发现一个问题有时候代码明明显示出来了但翻译成java却报错这可能是dex中某些偏移没有修对。可以再用baksmali把它转成smali查看如果smali能正常解析就证明dex结构基本恢复。5.5 验证还原结果验证还原是否成功我一般分三步走。第一步是静态验证用jadx打开修复后的dex随便选几个抽取过的敏感方法看方法体里有没有真实的smali指令比如字符串常量、invoke调用、分支指令等。第二步是动态验证把修复后的dex回填进APK重新签名安装运行功能确认没有崩溃。第三步是交叉验证用dump出的dex和修复后的dex做方法级对比统计code_item非空的方法数量。我这次样本中主动调用前约30%的方法code_item几乎为空主动调用后这个比例降到了5%以内。剩下的那5%大多是抽象方法、native方法或者主动调用时异常跳过的类。虽然不能做到100%完美还原但已经足够用于代码分析。如果你追求完整性可以结合unicorn主动计算或者换一种更暴力的做法在Native层直接hook加固的还原函数拿到它每次填入的原始数据。6. Activity生命周期重建6.1 为什么Activity会黑屏或闪退破解完dex后如果直接回填整个APK重新安装经常会遇到一个问题App可以启动但StubActivity自己不会干活原始Activity完全没有被调用界面一片黑或者秒退。这就是因为加固修改了AndroidManifest里的入口Activity系统实际启动的是StubActivity而StubActivity自身只是加载真实Activity的傀儡不会自动把生命周期方法转发过去。更准确地说StubActivity在加固框架里只是一个触发器真实Activity的生命周期事件是由代理框架手动分发的。如果抽取还原时没有把stub类一并替换回原始类系统就永远找不到真正的业务Activity。所以还原后必须手动重建Activity生命周期。这个环节的主题是“Activity生命周期重建”我们要做的是把原本由加固偷偷接管的生命周期重新还给原始Activity。方案有两个方向一个是修改smali把AndroidManifest改回原始入口去掉stub层另一种是运行时hook用Frida在Activity启动过程中把Intent的目标Component替换成真实Activity并把生命周期方法手工串联起来。6.2 重建Application的attach生命周期重建的第一步在Application层面。原始Application类被加固换成StubApp后StubApp会在attachBaseContext方法里加载业务dex并初始化原始Application。但加固的这套初始化逻辑是跟DexHelper绑定的如果你的样例在静态修改后运行时环境已经变了原始Application的attach方法可能没有正常被调用。这时候需要通过Frida去hookApplication.attach在调用时动态判断当前Application实例是不是StubApp如果是就再把原始Application类加载出来反射调用它的attach方法传入同一个Context。这个方法调用成功后原始Application的onCreate才能触发全局初始化逻辑才能跑起来。这一步别小看大量App会在Application里做全局参数初始化、埋点、启动崩溃收集如果Application生命周期没重建后面的Activity就算启动成功也会因为全局状态为空而崩溃。我在样本分析时就遇到过Activity明明启了但因为Application的某个静态变量没初始化结果在onCreate里NPE了。6.3 重建Activity启动链路Activity层面的生命重建更直接。系统启动的是StubActivity而我们希望在它后面启动真实Activity。用Frida hookActivityThread.handleLaunchActivity是最可控的方案这个函数是每创建一个Activity都会经过的入口。在该函数里读取当前要启动的Activity的Intent信息如果发现ComponentName是StubActivity就把它替换成真实的Activity类名。替换完后再让流程继续这样系统启动的就是真实Activity生命周期也能按正常顺序流转。有一个要注意的地方真实Activity的ClassLoader在业务dex被加载之后和系统默认的ClassLoader处于不同层级。直接把类名交给系统去找可能找不到。所以替换Intent组件的同时还要确保目标Activity类已经注册到PathClassLoader或者DexClassLoader里。如果类加载不到就要手动构造一个ClassLoader把业务dex的路径塞进去用反射的方式创建Activity实例再手动调用生命周期方法。6.4 生命周期方法的恢复逻辑生命周期重建的最终目标是让原始Activity的onCreate、onStart、onResume在正确的时机被调用。如果不想依赖系统hook也可以自己写一个代理生命周期调度器在StubActivity内部把用户真正的Activity创建出来然后在StubActivity的onCreate里调用真实Activity的onCreate在onResume里调用真实Activity的onResume这样手动转发。手动转发有个好处是非常直观出问题容易排查坏处是如果一个App里有几十个Activity你得在proxy层做分发逻辑判断当前真实Activity是哪个类再分别调用对应生命周期方法。这个分发逻辑很机械写起来不难但容易漏。我做实战时会优先选择用Frida去替换IPC的启动Intent让系统自己处理真实Activity的生命周期这样最接近正常App的启动路径能减少很多兼容性问题。如果替换Intent做的比较干净StubActivity连创建的机会都没有系统会直接启动真实Activity生命周期自动就正常了。7. 常见问题与排查技巧7.1 找不到so加载时机很多朋友在做这个对抗实验时第一步就卡住Frida脚本里hook dlopen却一直看不到libjiagu_a64.so出现。最常见的原因是加固应用在Frida初始化之前就已经把so加载完毕或者so加载走的是android_dlopen_ext但不是同一个线程hook时机没对上。解决办法是把Frida的启动模式从spawn替换成attach即先用spawn启动应用但挂在入口处再在dlopen和android_dlopen_ext两个函数上都下hook放行后再继续执行。如果还是没命中就去/proc/pid/maps里查看so实际加载路径再用延迟扫描的方式兜底。7.2 Frida注入后进程直接退出Frida注入后进程崩溃通常是加固的反调试机制检测到了frida-server的端口或特征。可以先尝试修改Frida默认端口在启动frida-server时加-l参数指定新的监听端口电脑端通过-H连接。如果还是不行需要hookpthread_create拦截加固的检测线程或者临时屏蔽/proc/self/maps里的frida痕迹。这个问题的排查没有标准答案要看加固版本具体检测了什么。我一般会开logcat看崩溃栈栈里如果出现了libjiagu相关的地址说明是so主动杀掉的那就往反调试对抗方向走如果是SIGSEGV可能是我们的Hook逻辑出问题比如hook了被加固内部保护的内存。7.3 dump出来的dex打不开打不开的原因分很多种常见的几个dump时机太早导致dex还没完全解密dex头被修改过dump出的内存区域有碎片文件长度不对。先检查文件头部magic然后检查file_size是否和实际文件大小一致再用010 Editor打开看map_off是否异常。如果映射极大且很多偏移对不上说明你把多个内存段混在一起dump了。更好的做法是先按内存地址范围分段dump然后用dex解析器逐个尝试最后再用哈希去重。不要一股脑把整个heap dump成一个文件那是浪费时间。7.4 回填后jadx还是看不到代码回填后jadx里看不到代码通常不是回填本身的问题而是方法体对应的code_item的insns_size没有被同步更新。code_off指向了正确位置但声明的大小不对解析器认为这个方法的指令区域很短后面的字节就被忽略。解决思路在回填时从运行时获取到code_item后把它的insns_size和registers_size也一并写回dex。更保险的办法是用dexlib2在Java层面做一次正式重打包让库来修复整个dex结构这样比手动写偏移可靠很多。7.5 Android版本带来的兼容性坑不同Android版本的ArtMethod结构体布局不一样ArtMethod::Invoke的符号名也不同甚至一些底层方法从解释器切换到了纯机器码执行。所以你在网上找到的某些Frida脚本很可能在自己的设备上完全跑不起来。建议先把设备固定在一个版本比如Android 10跑通一整套流程之后再换版本验证。跨版本时优先检查ArtMethod结构体偏移、libart.so里的关键符号名、Frida server与设备架构的匹配。这三个点对了大部分兼容性问题都能解决。8. 系列后续还能做什么这篇算是这个系列的第一篇主要目标是把对抗360企业加固这条主线走通。从结果来看最终拿到了一份方法体基本完整的dex也想办法重建了Activity生命周期让还原后的App在动态调试下能正常跑起来。这套流程放到其他抽取型加固上大的思路是完全通用的只要把so特征、类名、hook点替换成对应版本即可。我个人在实际操作中最深的体会是手工对抗加固最怕的就是在同一个环节反复试错却不知道为什么错。比如主动调用触发时机不对、ClassLoader加载顺序不对都会得到看似正常实则残缺的结果。所以每次dump完一定要先做静态验证确认dex确实完整再继续往下走。这个习惯能帮你省掉大量无用功。如果接下来还想深入有几个方向比较有价值一是研究360企业版对各版本Android上ArtMethod的适配逻辑把差异点整理成一份偏移表二是从抽取型加固的还原函数入手直接定位它内部维护的“原始代码仓库”一次dump就能拿到所有被抽取的方法体效率会比主动调用高很多三是把Frida脚本整理成一套工具把dump、修复、回填、生命周期重建的过程自动化。系列第二篇我可能会重点写主动调用和还原函数的动态追踪这也是抽取还原里更高级的玩法。最后再分享一个实战小技巧如果你不确定当前加固样本走的是整体加密还是抽取还原启动后立刻dump一次dex再用010 Editor检查几个普通类方法体的长度基本就能判断出来了。这个判断在做加固对抗时永远是起步动作先定战术再动手。