rev-mobile

移动端逆向参考手册

SKILL.md

GitHub

移动端逆向参考手册

Scope

目标与操作范围以用户当前请求为准;核对方法前置条件、工具和附件,按实际执行层级记录结果。

一、APK 结构与反编译

APK 结构

app.apk (ZIP)
├── AndroidManifest.xml      # 应用清单(二进制 XML)
├── classes.dex              # 主 DEX 字节码
├── lib/                     # Native SO 库(armeabi-v7a/arm64-v8a/x86_64)
├── res/                     # 编译后的资源
├── assets/                  # 原始资源
├── resources.arsc           # 资源索引表
└── META-INF/                # 签名信息

jadx-gui 分析

jadx-gui app.apk
# Ctrl+Shift+F 文本搜索 → API URL、密钥、硬编码凭证
# Ctrl+N 类搜索 → 定位特定类
# X 交叉引用 → 查找方法/字段所有调用点

apktool 工作流

apktool d app.apk -o app_decompiled/    # 反编译
apktool b app_decompiled/ -o app_mod.apk # 重新打包
zipalign -v 4 app_mod.apk app_aligned.apk
apksigner sign --ks my.keystore --ks-pass pass:123456 app_aligned.apk
adb install app_aligned.apk

二、Smali 编程

寄存器与方法签名

# v0,v1... = 本地寄存器, p0,p1... = 参数寄存器
# 非静态方法 p0=this, p1=第一个参数
# 静态方法 p0=第一个参数

.method public onCreate(Landroid/os/Bundle;)V
    .registers 4
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
    return-void
.end method

常用操作码

invoke-virtual {v0, v1}, Lcom/example/Foo;->bar(I)V    # 调用实例方法
invoke-static {v0}, Lcom/Util;->encrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Hello"     # 字符串常量
move-result-object v0        # 获取返回值
return-object v0             # 返回对象
if-eqz v0, :label            # if (v0 == 0) goto label

编辑示例:强制返回 true

.method public isRegistered()Z
    .registers 2
    const/4 v0, 0x1    # v0 = 1 (true)
    return v0
.end method

三、IPA 分析

# class-dump 提取 ObjC 头文件
class-dump -H Payload/App.app/App -o headers/

# Cycript 运行时探索
cycript -p AppName
UIApp.keyWindow.rootViewController

# Flexdecrypt 解密 App Store 应用
flexdecrypt /path/to/App

四、Frida 完整参考

环境搭建

pip install frida-tools objection
# 下载 frida-server 推送到设备
adb push frida-server /data/local/tmp/
adb shell "chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server &"

CLI 命令

frida -U -f com.example.app -l script.js --no-pause  # spawn 模式
frida -U com.example.app -l script.js                  # 附加模式
frida-ps -Ua                                           # 列出应用
frida-trace -U -f com.example.app -j "*!*encrypt*"     # Java 方法追踪
frida-trace -U -f com.example.app -i "encrypt"           # Native 函数追踪
frida-discover -U -f com.example.app                     # 自动发现函数

frida-discover(自动函数发现)

# 黑盒分析:不需要知道函数名,自动探测可 Hook 的函数
# 输出 CSV 格式函数列表,可用于后续精确 Hook

Stalker(指令级跟踪)

// 指令级别跟踪目标函数,适用于分析混淆后的加密逻辑
Interceptor.attach(Module.findExportByName("lib.so", "target"), {
    onEnter(args) {
        Stalker.follow(this.threadId, {
            events: { call: true, ret: true },
            onCallSummary(summary) { console.log(JSON.stringify(summary)); }
        });
    },
    onLeave(retval) {
        Stalker.unfollow(this.threadId);
        Stalker.garbageCollect();
    }
});

Frida 检测绕过(来自官方文档)

1. 改名 frida-server → 随机名称
2. 移动到 /dev/ 目录
3. 使用 frida-gadget 代替 frida-server(免 Root 方案)
4. 修改监听端口: frida-server -l 0.0.0.0:8888

Hook Java 方法(Android)

Java.perform(function() {
    var MainActivity = Java.use("com.example.MainActivity");

    // Hook 方法(带重载)
    MainActivity.encrypt.overload('java.lang.String').implementation = function(input) {
        console.log("[*] encrypt() input=" + input);
        var result = this.encrypt(input);
        console.log("[*] encrypt() output=" + result);
        return result;
    };

    // 修改返回值
    MainActivity.isRegistered.implementation = function() {
        return true;
    };
});

Hook Native 函数

// Hook SO 中的函数
Interceptor.attach(Module.findExportByName("libnative.so", "encrypt"), {
    onEnter: function(args) {
        console.log("[*] arg0=" + args[0].readUtf8String());
    },
    onLeave: function(retval) {
        console.log("[*] retval=" + retval.readUtf8String());
    }
});

// Hook 指定地址
var libBase = Module.findBaseAddress("libnative.so");
Interceptor.attach(libBase.add(0x1234), { onEnter(args) { /* ... */ }, onLeave(retval) { /* ... */ } });

内存操作

// 读写
var bytes = Memory.readByteArray(ptr("0x12345678"), 64);
Memory.writeByteArray(ptr("0x12345678"), [0x90, 0x90, 0x90, 0x90]);

// 扫描
Memory.scan(Module.findBaseAddress("lib.so"), 0x100000, "48 89 e5", {
    onMatch(address, size) { console.log("Found: " + address); },
    onComplete() { console.log("Done"); }
});

ObjC Bridge(iOS)

if (ObjC.available) {
    var VC = ObjC.classes.ViewController;
    Interceptor.attach(VC['- viewDidAppear:'].implementation, {
        onEnter(args) { console.log("[*] viewDidAppear"); }
    });
    // 遍历实例
    ObjC.choose(ObjC.classes.UIButton, {
        onMatch(obj) { console.log("UIButton: " + obj.description()); },
        onComplete() {}
    });
}

Frida Gadget(非 Root)

apktool d app.apk -o app_gadget/
# 将 libfrida-gadget.so 复制到 lib/arm64-v8a/
# 在 Application smali 中添加: System.loadLibrary("frida-gadget")
apktool b app_gadget/ -o app_gadget.apk
# 签名安装后: frida -U Gadget -l script.js

Objection 常用命令

objection -g com.example.app explore
android root disable              # 绕过 root 检测
android sslpinning disable        # 绕过 SSL 锁定
android hooking list classes      # 列出所有类
android hooking watch class com.example.App  # 监控类方法
ios sslpinning disable
ios keychain dump

SSL 解锁脚本

Java.perform(function() {
    // TrustManager 绕过
    try {
        var TMI = Java.use("com.android.org.conscrypt.TrustManagerImpl");
        TMI.verifyChain.implementation = function() { return arguments[0]; };
    } catch(e) {}
    // OkHttp3 CertificatePinner
    try {
        var CP = Java.use("okhttp3.CertificatePinner");
        CP.check.overload('java.lang.String','java.util.List').implementation = function() {};
    } catch(e) {}
    // WebView SSL
    try {
        var WVC = Java.use("android.webkit.WebViewClient");
        WVC.onReceivedSslError.implementation = function(v,h,e) { h.proceed(); };
    } catch(e) {}
    console.log("[*] SSL bypass loaded");
});

Root 检测绕过脚本

Java.perform(function() {
    // 直接修改返回值
    try {
        var RD = Java.use("com.example.security.RootDetection");
        RD.isRooted.implementation = function() { return false; };
    } catch(e) {}
    // 文件检测绕过
    var File = Java.use("java.io.File");
    File.exists.implementation = function() {
        var p = this.getAbsolutePath();
        if (p.indexOf("su")!==-1 || p.indexOf("Superuser")!==-1) return false;
        return this.exists();
    };
    // Build.TAGS
    Java.use("android.os.Build").TAGS.value = "release-keys";
});

五、Xposed 框架

模块结构

public class HookMain implements IXposedHookLoadPackage {
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals("com.example.target")) return;

        XposedHelpers.findAndHookMethod("com.example.target.SecurityManager",
            lpparam.classLoader, "isRooted", new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    param.setResult(false);
                }
            });
    }
}
// assets/xposed_init
com.example.xposed.HookMain

Frida vs Xposed

特性 Frida Xposed/LSPosed
Root 要求 可用 Gadget 绕过 必须 Root
语言 JavaScript Java
即时修改 支持(热重载) 需重启应用
Native Hook 原生支持 需额外库
持久性 临时 永久
iOS 支持 支持 不支持

六、Unity 游戏引擎逆向

Unity Mono 模式

# DLL 位置: assets/bin/Data/Managed/Assembly-CSharp.dll
# 用 dnSpy 打开反编译,查看 MonoBehaviour 方法(Awake/Start/Update)

Unity IL2CPP 模式

# 识别: libil2cpp.so + global-metadata.dat
# Il2CppDumper
./Il2CppDumper libil2cpp.so global-metadata.dat output/
# 输出: dump.cs(类/方法定义), script.json(地址映射), stringliteral.json(字符串字面量)
# script.json 可导入 IDA/Ghidra 用于重命名函数

Metadata 版本差异:

MetadataVersion 24: Unity 5.x
MetadataVersion 27: Unity 2017-2018
MetadataVersion 29: Unity 2019-2020
MetadataVersion 29 (v2): Unity 2021+
MetadataVersion 29 (v3): Unity 2022+
# Il2CppDumper 自动检测版本,新版本可能需要更新工具

IL2CPP API 动态查找(无需地址):

// 通过 IL2CPP 导出 API 动态查找函数,不依赖 Il2CppDumper 地址
function il2cpp_resolve(className, methodName, paramCount) {
    var domain = new NativeFunction(
        Module.findExportByName(null, "il2cpp_domain_get"), 'pointer', [])();
    var klass = new NativeFunction(
        Module.findExportByName(null, "il2cpp_class_from_name"),
        'pointer', ['pointer', 'pointer', 'pointer'])(
            ptr(0), Memory.allocUtf8String(""), Memory.allocUtf8String(className));
    var method = new NativeFunction(
        Module.findExportByName(null, "il2cpp_class_get_method_from_name"),
        'pointer', ['pointer', 'pointer', 'int'])(
            klass, Memory.allocUtf8String(methodName), paramCount);
    return method;
}
// 用法: var addr = il2cpp_resolve("GameManager", "AddGold", 1);
// Interceptor.attach(addr, { onEnter(args) { ... } });

Frida Hook IL2CPP

var libil2cpp = Module.findBaseAddress("libil2cpp.so");
var addGoldAddr = libil2cpp.add(0x1234567); // RVA from Il2CppDumper
Interceptor.attach(addGoldAddr, {
    onEnter(args) {
        console.log("[*] AddGold amount=" + args[1].toInt32());
        args[1] = ptr(999999); // 修改参数
    }
});

Unreal Engine

# UObject 反射: GNames/GObjects
# UE4Dumper 生成 SDK
# Frida hook: Module.findExportByName("libUE4.so", "GetObjectName")

Cocos2d-x

# Lua 脚本提取: assets/ 下的 .luac 文件
java -jar unluac.jar script.luac > script.lua

# Frida hook Lua 引擎
Interceptor.attach(Module.findExportByName("libcocos2dcpp.so", "luaL_loadbuffer"), {
    onEnter(args) {
        console.log("[Lua] " + args[1].readUtf8String(args[2].toInt32()).substring(0, 500));
    }
});

七、Android 脱壳

Frida dump DEX

// frida_dump_dex.js — 在 DEX 加载时 dump
Java.perform(function() {
    var DexFile = Java.use("dalvik.system.DexFile");
    DexFile.$init.overload('java.lang.String').implementation = function(path) {
        console.log("[*] DexFile opened: " + path);
        var fis = Java.use("java.io.FileInputStream").$new(path);
        var bytes = Java.use("java.io.ByteArrayOutputStream").$new();
        var buf = Java.array('byte', new Array(4096).fill(0));
        var len;
        while ((len = fis.read(buf)) !== -1) bytes.write(buf, 0, len);
        fis.close();
        var outputPath = "/data/local/tmp/dump_" + Date.now() + ".dex";
        var fos = Java.use("java.io.FileOutputStream").$new(outputPath);
        fos.write(bytes.toByteArray());
        fos.close();
        console.log("[*] Dumped to: " + outputPath);
        return this.$init(path);
    };
});

BlackDex / FART

# BlackDex — 免 Root 脱壳,利用虚拟化技术
# 安装后打开 → 选择目标应用 → 一键脱壳
# 输出: /sdcard/BlackDex/com.target.app/

# FART — ART 环境脱壳(需刷入修改 ROM 或使用 Frida 版脚本)
# 原理: 类初始化时 dump DEX,通杀大多数壳

八、网络抓包(免代理)

工具 原理 Root 特点
R0capture Frida 绕 SSL 抓明文 需要 免代理,输出 pcap
PCAPdroid VPN 模式抓包 不需要 Android App
HttpCanary VPN 模式 HTTP(S) 不需要 图形化
tcpdump 底层抓包 需要 命令行
# R0capture
frida -U -f com.target.app -l r0capture.js --no-pause -o capture.pcap

# 设备端 tcpdump
adb push tcpdump /data/local/tmp/ && adb shell chmod 755 /data/local/tmp/tcpdump
adb shell su -c "/data/local/tmp/tcpdump -i any -w /sdcard/cap.pcap"
adb pull /sdcard/cap.pcap

九、ADB 高级操作

# 信息收集
adb shell dumpsys package com.target.app | head -50
adb shell dumpsys activity com.target.app
adb shell cat /data/data/com.target.app/shared_prefs/*.xml
adb shell sqlite3 /data/data/com.target.app/databases/app.db "SELECT * FROM users"

# 启动组件
adb shell am start -n com.target.app/.MainActivity
adb shell am start -a android.intent.action.VIEW -d "https://example.com"
adb shell am broadcast -a com.target.app.CUSTOM_ACTION

# 备份提取
adb backup -f backup.ab com.target.app
# 解包: java -jar abe.jar unpack backup.ab backup.tar

十、drozer 安全测试

adb forward tcp:31415 tcp:31415
drozer console connect

# 信息收集
run app.package.info -a com.target.app
run app.package.attacksurface com.target.app

# Content Provider
run scanner.provider.finduris -a com.target.app
run app.provider.query content://com.target.app/users --vertical

# Activity / Service / Receiver
run app.activity.info -a com.target.app
run app.activity.start --component com.target.app com.target.app.DebugActivity
run app.service.info -a com.target.app
run app.broadcast.info -a com.target.app

十一、Root 方案对比

方案 原理 Android 特点
Magisk Systemless boot 6.0-15 主流,Zygisk + DenyList
KernelSU 内核级 Root 11+ 更隐蔽
APatch 内核补丁 11+ 新方案

隐藏链路: Magisk → Zygisk → DenyList + Shamiko → Play Integrity Fix

十二、SO 文件分析

readelf -s libtarget.so | grep -E "(encrypt|JNI|Register)"
readelf -s libtarget.so | grep Java_        # JNI 静态注册
strings libtarget.so | grep -iE "(key|secret|api|http)"

IDA 分析 SO: 搜索 JNI_OnLoad → 分析动态注册 → 定位加密函数 → F5 反编译

十三、反检测绕过

检测类型 方案
Root 检测 Magisk DenyList / Shamiko / KernelSU / Frida 脚本
SSL 锁定 Frida 脚本 / JustTrustMe / ReFlutter (Flutter)
签名校验 CorePatch / 手动 patch smali
模拟器检测 修改 ro.product.model 等 prop
Play Integrity Play Integrity Fix 模块
Frida 检测 Gadget / 改名 frida-server / 反检测脚本
调试检测 Frida hook ptrace / Xposed 绕过

使用前核对

目标与操作范围以用户当前请求为准;核对方法前置条件、工具和附件,按实际执行层级记录结果。正文不表示示例代码已经在本机执行或验证。