1. 加壳特征识别与加固原理剖析当你拿到一个被加固的APK文件时第一步就是要判断它是否被加固以及使用了哪种加固方案。以腾讯乐加固为例通常会存在以下特征文件/lib/目录下存在libshellx-2.10.6.0.so、libBugly.so等特定so文件AndroidManifest.xml中Application类被替换为加固壳的入口如TxAppEntry使用Jadx反编译后看不到原始业务代码只有加固相关的桩代码验证加固的三种实用方法检查so文件解压APK后查看lib目录腾讯系加固通常会包含libtup.so、libshell.so等特征文件分析classes.dex用Jadx打开时如果发现所有类都是com.stub开头且没有业务逻辑代码查看manifest文件使用apktool反编译后真正的Application类会被隐藏在meta-data中提示不同厂商的加固特征不同360加固常用libjiagu.so爱加密则会有ijiami.dat文件2. 动态脱壳技术选型与Frida环境搭建静态分析遇到加固保护时动态脱壳就成为必选方案。当前主流脱壳方式有两种2.1 传统Xposed方案通过Hook ClassLoader的loadClass方法在内存中dump解密后的dex。常用工具有FDex2适合新手使用的可视化工具DumpDex支持多厂商加固的通用方案但Xposed方案需要Root环境且在新版Android系统上兼容性较差。2.2 Frida动态注入方案相比XposedFrida具有以下优势无需Root可配合objection使用支持跨平台Windows/Mac/Linux实时交互式调试环境搭建步骤# 安装Python环境 pip install frida-tools # 下载对应版本的frida-server adb push frida-server-15.2.2-android-arm64 /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server 3. Frida内存Dump实战3.1 定位脱壳点通过分析加固壳的执行流程通常选择这两个Hook点Application.attachBaseContext壳最先执行的初始化方法DexClassLoader.loadClass解密后的类被加载的时机3.2 内存搜索与Dump脚本Java.perform(function(){ // Hook PathClassLoader var PathClassLoader Java.use(dalvik.system.PathClassLoader); PathClassLoader.loadClass.overload(java.lang.String).implementation function(name){ // 打印加载的类名 console.log([*] Loading class: name); // Dump整个Dex文件 var dexFiles this.getDex(); var bytes Java.array(byte, dexFiles.getBytes()); // 保存到/sdcard var file new File(/sdcard/dex_dump.dex, wb); file.write(bytes); file.close(); return this.loadClass(name); }; });执行脚本frida -U -f com.target.app -l dump_dex.js3.3 二次处理Dex文件dump出来的dex可能需要修复使用dexfixer工具修复头信息用baksmali/smali工具重新打包合并多个dex文件如有分包4. 签名算法逆向分析4.1 定位关键代码通过以下特征快速定位签名逻辑网络请求拦截Fiddler/Charles发现signature参数搜索包含sign、md5、sha1等关键字的类跟踪参数拼接过程常见时间戳设备ID的拼接4.2 算法还原案例以某App的签名算法为例核心逻辑如下public static String generateSign(String udid, String timestamp) { String raw udid timestamp 固定密钥; return md5(raw); }用Python还原算法import hashlib def generate_sign(udid, timestamp): secret f1190aca-d08e-4041-8666-29931cd89dde raw f{udid}{timestamp}{secret} return hashlib.md5(raw.encode()).hexdigest()4.3 动态Hook验证Java.perform(function(){ var SignUtils Java.use(com.target.util.SignUtils); SignUtils.generateSign.implementation function(a, b){ var result this.generateSign(a, b); console.log(参数: ${a}, ${b} - 签名: ${result}); return result; }; });5. 对抗与反调试绕过在实际逆向过程中可能会遇到各种防护措施5.1 反调试检测检查/proc/self/status中的TracerPid检测frida相关端口默认27042检查线程名是否包含frida绕过方案// 重命名frida线程 Process.enumerateThreads().forEach(thread { Thread.rename(thread.id, javaWorker); }); // 隐藏端口 Interceptor.replace(Module.findExportByName(libc.so, getaddrinfo), ...);5.2 代码混淆对抗对于控制流混淆的代码使用JEB等支持AST分析的逆向工具关键位置下条件断点结合动态执行轨迹分析6. 自动化脚本开发建议将常用操作封装成自动化脚本import frida import sys def on_message(message, data): if message[type] send: print([*] message[payload]) else: print(message) device frida.get_usb_device() pid device.spawn([com.target.app]) session device.attach(pid) with open(dump_dex.js) as f: script session.create_script(f.read()) script.on(message, on_message) script.load() device.resume(pid) sys.stdin.read()这个领域最关键的还是多实践每个加固方案都有其独特之处。建议先从一些CTF题目入手如阿里的加固保挑战逐步积累经验。遇到问题时要善用Frida的Stalker功能追踪执行流有时候一个看似复杂的保护可能只是对某个系统函数的简单Hook。