安卓逆向实战:从加壳特征识别到Frida动态脱壳与签名算法还原
📅 2026/7/6 11:55:40
👁️ 阅读次数
📝 编程学习
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动态注入方案
相比Xposed,Frida具有以下优势:
- 无需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。
编程学习
技术分享
实战经验