安卓逆向实战:从加壳特征识别到Frida动态脱壳与签名算法还原

安卓逆向实战:从加壳特征识别到Frida动态脱壳与签名算法还原

1. 加壳特征识别与加固原理剖析

当你拿到一个被加固的APK文件时,第一步就是要判断它是否被加固以及使用了哪种加固方案。以腾讯乐加固为例,通常会存在以下特征文件:

  • /lib/目录下存在libshellx-2.10.6.0.solibBugly.so等特定so文件
  • AndroidManifest.xml中Application类被替换为加固壳的入口(如TxAppEntry
  • 使用Jadx反编译后看不到原始业务代码,只有加固相关的桩代码

验证加固的三种实用方法

  1. 检查so文件:解压APK后查看lib目录,腾讯系加固通常会包含libtup.solibshell.so等特征文件
  2. 分析classes.dex:用Jadx打开时如果发现所有类都是com.stub开头,且没有业务逻辑代码
  3. 查看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点:

  1. Application.attachBaseContext:壳最先执行的初始化方法
  2. 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.js

3.3 二次处理Dex文件

dump出来的dex可能需要修复:

  1. 使用dexfixer工具修复头信息
  2. 用baksmali/smali工具重新打包
  3. 合并多个dex文件(如有分包)

4. 签名算法逆向分析

4.1 定位关键代码

通过以下特征快速定位签名逻辑:

  1. 网络请求拦截(Fiddler/Charles)发现signature参数
  2. 搜索包含"sign"、"md5"、"sha1"等关键字的类
  3. 跟踪参数拼接过程(常见时间戳+设备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 代码混淆对抗

对于控制流混淆的代码:

  1. 使用JEB等支持AST分析的逆向工具
  2. 关键位置下条件断点
  3. 结合动态执行轨迹分析

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。