模块 1:Android 逆向工程介绍

Android 逆向工程是一项强大的技能,可以帮助你理解和修改应用程序的行为。本模块将带你了解 Android 逆向工程的基本概念、应用场景和核心工具链。

什么是 Android 逆向工程?

Android 逆向工程是对已编译的 Android 应用程序(APK 文件)进行分析和修改的过程。通过逆向工程,安全研究人员可以发现漏洞、恶意软件分析师可以检测威胁、开发者可以学习优秀应用的设计模式。

重要声明:逆向工程应遵守法律法规。仅对自有应用或已获授权的应用进行分析。未经授权的逆向工程可能违反软件许可协议和相关法律。

核心应用场景

安全审计

发现应用中的安全漏洞,如硬编码密钥、不安全的网络通信、敏感数据泄露等。白帽黑客的核心技能。

恶意软件分析

分析可疑 APK 的行为,识别恶意功能(如短信劫持、数据窃取、后台下载)。安全团队的日常工具。

竞品分析

了解竞品应用的技术架构、API 调用和实现方式。帮助产品团队制定技术策略(需注意法律合规)。

CTF 竞赛

移动安全 CTF 比赛中的核心技能,涉及逆向分析、漏洞利用和flag提取。提升安全实战能力。

Android 应用基础知识

# APK 文件本质上是 ZIP 压缩包
# 解压查看内部结构
unzip -l target_app.apk

# 典型 APK 内部结构:
# AndroidManifest.xml   — 应用清单(权限、组件声明)
# classes.dex           — Dalvik 字节码(编译后的代码)
# classes2.dex          — 多 dex 文件(方法数超过 65536 时)
# resources.arsc        — 编译后的资源索引
# res/                  — 资源文件(图片、布局、字符串等)
# assets/               — 原始资源文件
# lib/                  — 原生库(.so 文件)
#   arm64-v8a/
#   armeabi-v7a/
# META-INF/             — 签名信息
#   CERT.RSA
#   CERT.SF
#   MANIFEST.MF

APK 文件是 Android 应用的安装包格式,本质是一个 ZIP 压缩包。核心组件包括:classes.dex 包含编译后的 Dalvik 字节码;AndroidManifest.xml 声明应用的权限和组件;lib/ 包含原生 C/C++ 库;META-INF/ 包含数字签名用于验证完整性。

工具链概览

逆向工程五件套:

  • apktool — 反编译 APK 为 smali 代码和资源文件,支持重新打包
  • jadx / jadx-gui — 将 Dalvik 字节码反编译为可读的 Java 源码
  • adb (Android Debug Bridge) — 与 Android 设备通信的命令行工具
  • Frida — 动态插桩框架,运行时修改应用行为
  • dex2jar — 将 DEX 文件转换为 Java JAR 文件
# 安装核心工具
## macOS
brew install apktool jadx frida

## Linux
sudo apt install apktool jadx

## 安装 Frida
pip install frida-tools frida

## 安装 Android SDK Platform Tools(包含 adb)
## 从 https://developer.android.com/studio/releases/platform-tools 下载

# 验证安装
apktool --version
jadx --version
adb version
frida --version

这五个工具构成 Android 逆向工程的核心工具链。在 macOS 上可以用 Homebrew 快速安装大部分工具。Frida 需要通过 pip 安装 Python 绑定。adb 是 Android SDK 的一部分,需要单独下载。

设置逆向工程环境

# 1. 启用设备的开发者选项和 USB 调试
# 设置 → 关于手机 → 连续点击"版本号"7次
# 设置 → 开发者选项 → 打开 USB 调试

# 2. 验证设备连接
adb devices
# 输出示例:
# List of devices attached
# R5CR80XXXX  device

# 3. 获取目标应用信息
adb shell pm list packages | grep target
adb shell dumpsys package com.target.app

# 4. 提取 APK 文件
adb shell pm path com.target.app
# 输出:package:/data/app/~~hash==/com.target.app-hash==/base.apk

adb pull /data/app/~~hash==/com.target.app-hash==/base.apk target.apk

# 5. 使用 apktool 解包
apktool d target.apk -o target_decoded

# 6. 使用 jadx 反编译
jadx-gui target.apk
# 或命令行版本
jadx -d output_java target.apk

逆向工程环境搭建六步走:1) 在 Android 设备上启用开发者选项;2) 用 adb 验证设备连接;3) 查找目标应用的包名和路径;4) 从设备中提取 APK 文件;5) 用 apktool 解包为 smali 代码和资源;6) 用 jadx 反编译为可读的 Java 代码。

Android 安全模型概述

理解 Android 的安全模型是有效进行逆向工程的前提。Android 采用多层安全架构来保护应用和用户数据。

Android 安全层次:

  • Linux 内核层 — 基于 UID/GID 的进程隔离,每个应用运行在独立的 Linux 用户空间
  • 权限系统 — 应用必须声明所需权限,危险权限需要用户运行时授权
  • Sandbox 沙箱 — 应用间数据隔离,SELinux 强制访问控制
  • 签名验证 — APK 必须签名,更新必须使用同一签名密钥
  • Play Protect — Google 的应用安全扫描服务
# 查看 Android 安全相关属性
adb shell getprop ro.build.version.sdk
adb shell getprop ro.build.type
adb shell getenforce  # SELinux 状态

# 查看应用沙箱
adb shell ls -la /data/data/com.target.app/
adb shell run-as com.target.app  # 调试版本可进入沙箱

# 查看应用权限
adb shell dumpsys package com.target.app | grep permission

# 检查应用是否可调试
adb shell dumpsys package com.target.app | grep debuggable

# 查看 SELinux 上下文
adb shell ls -Z /data/data/com.target.app/

# Android 版本与安全特性对照:
# Android 6 (API 23) — 运行时权限
# Android 7 (API 24) — 文件加密、SELinux 严格执行
# Android 8 (API 26) — 后台限制、通知渠道
# Android 9 (API 28) — DNS over TLS、锁屏加固
# Android 10 (API 29) — 分区存储、位置后台权限
# Android 11 (API 30) — 一次性权限、包可见性
# Android 12 (API 31) — 近似位置、蓝牙权限重构
# Android 13 (API 33) — 通知权限、照片选择器
# Android 14 (API 34) — 前台服务类型、隐式 intent 限制

Android 安全模型的核心是沙箱隔离:每个应用运行在独立的 Linux UID 下,应用间无法直接访问对方数据。SELinux 提供强制访问控制,即使 root 用户也受约束。了解这些安全机制有助于确定逆向工程的攻击面和可行的分析路径。

逆向工程的合法场景

Android 逆向工程在以下场景中是合法且有价值的:安全审计(发现漏洞并负责任披露)、恶意软件分析(识别间谍软件和木马)、兼容性开发(理解未公开的 API 行为)、学习研究(理解优秀应用的架构设计)、取证分析(数字证据提取)。始终确保你有权分析目标应用——遵守当地法律法规和应用的使用条款。

Android 逆向工程的主要目的是什么?

模块 2:核心概念 — APK 结构与字节码

了解 Android 应用程序的结构和关键组件是逆向工程的基础。本模块将深入介绍 APK 文件结构、Dalvik/ART 字节码、AndroidManifest.xml 和资源系统。

APK 文件深度解析

# 使用 aapt 分析 APK
aapt dump badging target.apk
# 输出包名、版本、SDK 版本、权限等信息

aapt dump permissions target.apk
# 列出所有声明的权限

aapt dump xmltree target.apk AndroidManifest.xml
# 解析 AndroidManifest.xml 的完整 XML 树

# 使用 apkanalyzer(Android Studio 自带)
apkanalyzer apk summary target.apk
apkanalyzer apk file-size target.apk
apkanalyzer dex list target.apk
apkanalyzer resources packages target.apk

aapt(Android Asset Packaging Tool)是分析 APK 的基础工具:dump badging 显示应用基本信息;dump permissions 列出所有权限;dump xmltree 解析二进制 XML。apkanalyzer 提供更详细的分析,包括 DEX 方法数、资源包等。

AndroidManifest.xml 关键元素


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.targetapp"
    android:versionCode="15"
    android:versionName="2.1.0">

    
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:debuggable="false"
        android:networkSecurityConfig="@xml/network_security_config">

        <!-- 四大组件:Activity -->
        <activity android:name=".ui.MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 四大组件:Service -->
        <service android:name=".service.DataSyncService"
            android:exported="false" />

        <!-- 四大组件:Broadcast Receiver -->
        <receiver android:name=".receiver.BootReceiver"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <!-- 四大组件:Content Provider -->
        <provider android:name=".provider.DataProvider"
            android:authorities="com.example.targetapp.provider"
            android:exported="false" />

    </application>
</manifest>

AndroidManifest.xml 是逆向分析的第一步。重点关注:权限声明(了解应用能做什么)、四大组件(Activity/Service/Receiver/Provider,应用的入口点)、exported 属性(标记组件是否可被外部调用,潜在攻击面)、debuggable(是否可调试)、networkSecurityConfig(网络安全配置)。

Smali 字节码入门

什么是 Smali?Smali 是 Dalvik 字节码的人类可读汇编语言。apktool 将 DEX 文件反编译为 Smali 文件,修改 Smali 后可以重新打包为 APK。Smali 语法类似汇编语言,但基于寄存器而非栈。

# Smali 基本语法示例

# 定义一个类
.class public Lcom/example/targetapp/MainActivity;
.super Landroid/app/Activity;

# 定义一个方法
.method public onCreate(Landroid/os/Bundle;)V
    .locals 3

    # 调用父类方法
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V

    # 设置布局
    const v1, 0x7f0b0015    # R.layout.activity_main
    invoke-virtual {p0, v1}, Landroid/app/Activity;->setContentView(I)V

    # 查找视图
    const v1, 0x7f08003a    # R.id.textView
    invoke-virtual {p0, v1}, Landroid/app/Activity;->findViewById(I)Landroid/view/View;
    move-result-object v0

    # 设置文本
    const-string v1, "Hello, Reverse Engineering!"
    check-cast v0, Landroid/widget/TextView;
    invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

    return-void
.end method

# 关键 Smali 指令对照:
# const          — 加载常量
# const-string   — 加载字符串常量
# invoke-virtual — 调用虚方法
# invoke-static  — 调用静态方法
# invoke-direct  — 调用私有/构造方法
# move-result    — 保存方法返回值
# iget/iput      — 读/写实例字段
# sget/sput      — 读/写静态字段
# if-eqz/if-nez  — 条件跳转
# goto           — 无条件跳转
# return-void    — 返回 void

Smali 是 Dalvik 字节码的汇编表示。每个方法以 .method 开始,.end method 结束。p0 是 this 引用,p1, p2, ... 是参数,v0, v1, ... 是局部变量。关键指令:invoke-* 系列用于方法调用,const-* 用于加载常量,move-result 获取返回值。

资源文件分析

# apktool 解包后查看资源结构
tree target_decoded/res/

# 常见资源目录:
# res/layout/       — UI 布局 XML
# res/values/       — 字符串、颜色、样式
# res/drawable/     — 图片资源
# res/xml/          — 配置 XML(如网络安全配置)
# res/raw/          — 原始文件(可能有证书、配置等)

# 分析字符串资源(可能包含 API 端点、密钥等)
cat target_decoded/res/values/strings.xml

# 分析网络配置
cat target_decoded/res/xml/network_security_config.xml

# 查找硬编码的敏感信息
grep -r "api_key\|secret\|password\|token" target_decoded/res/values/
grep -r "http://" target_decoded/  # 查找不安全的 HTTP 连接
grep -r "\.api\." target_decoded/  # 查找 API 端点

# 使用 aapt 解析资源 ID 映射
aapt dump resources target.apk | grep -E "0x7f[0-9a-f]+"

资源文件分析是逆向工程的重要环节。strings.xml 可能包含 API 密钥、服务器地址等敏感信息;network_security_config.xml 定义了网络安全策略(是否允许明文 HTTP);layout/*.xml 揭示 UI 结构和功能。使用 grep 搜索敏感关键词是快速定位关键信息的方法。

DEX 文件格式深入

# 使用 dexdump 分析 DEX 文件
dexdump -d classes.dex | head -100

# 使用 baksmali 反汇编 DEX
baksmali d classes.dex -o smali_output/

# 使用 dex2jar 转换为 JAR
d2j-dex2jar.sh target.apk
# 输出:target-dex2jar.jar

# 用 JD-GUI 查看反编译的 JAR
jd-gui target-dex2jar.jar

# DEX 文件头结构(0x70 bytes):
# magic:            64 65 78 0a 30 33 35 00  ("dex\n035\0")
# checksum:         Adler32 校验和
# signature:        SHA-1 签名
# file_size:        文件总大小
# header_size:      0x70
# string_ids_size:  字符串常量池大小
# type_ids_size:    类型索引大小
# method_ids_size:  方法索引大小
# class_defs_size:  类定义数量

# 快速统计 DEX 信息
dexdump -f classes.dex | grep -E "method_ids|class_defs|string_ids"

DEX(Dalvik Executable)是 Android 应用的字节码格式。一个 DEX 文件最多包含 65536 个方法引用(64K 限制),超限需要使用 MultiDex。DEX 文件头包含字符串池、类型索引、方法索引和类定义。通过分析 DEX 头部信息可以快速了解应用的复杂度。

Native SO 库分析

许多应用将关键逻辑(如加密算法、授权验证)放在原生 C/C++ 库(.so 文件)中。分析 SO 库需要理解 ARM 汇编和使用专门的工具。

# 查看 APK 中的 SO 库
ls -la target_decoded/lib/arm64-v8a/
# 常见库名:libnative.so, libutils.so, libsecurity.so

# 使用 file 查看 SO 文件类型
file target_decoded/lib/arm64-v8a/libnative.so
# ELF 64-bit LSB shared object, ARM aarch64

# 使用 readelf 分析 ELF 头
readelf -h libnative.so
readelf -S libnative.so    # 段表
readelf -s libnative.so    # 符号表(函数名列表)

# 使用 nm 查看导出符号
nm -D libnative.so | grep -i "encrypt\|decrypt\|verify\|check"

# 使用 objdump 反汇编
objdump -d libnative.so | head -100

# 使用 Ghidra 进行深度分析(GUI)
# 1. 创建项目 → 导入 SO 文件
# 2. 自动分析 → 查看 Decompiler 窗口(C 伪代码)
# 3. 搜索字符串定位关键函数
# 4. 使用 Frida Hook 原生函数

# Frida Hook SO 库中的原生函数
Interceptor.attach(Module.findExportByName("libnative.so", "encryptData"), {
    onEnter: function(args) {
        console.log("[*] encryptData called");
        console.log("    arg0 (input): " + Memory.readUtf8String(args[0]));
        console.log("    arg1 (length): " + args[1].toInt32());
    },
    onLeave: function(retval) {
        console.log("[*] encryptData returned: " + retval);
    }
});

SO 库分析步骤:1) readelf -s 列出所有导出函数名,搜索 encrypt/verify 等关键词;2) nm -D 查看动态符号表;3) 使用 Ghidra 反编译为 C 伪代码,更易理解逻辑;4) 用 Frida 的 Interceptor.attach Hook 原生函数,拦截参数和返回值。ARM 汇编知识在深度分析时非常有帮助。

Smali 修改实战:去除广告弹窗

最常见的 Smali 修改场景之一是去除应用内的广告弹窗。核心思路:找到广告弹窗的触发方法,将其返回值改为空或跳过调用。

步骤:1) 用 jadx 找到广告加载的类和方法名;2) 在 smali 目录中找到对应的 .smali 文件;3) 修改方法体,在开头插入 return-void 跳过原始逻辑;4) 用 apktool 重新打包;5) 用 apksigner 签名;6) 安装测试。注意:修改 Smali 时要保持寄存器声明正确,否则会崩溃。

APK 文件中包含哪些主要部分?

模块 3:实践应用 — 反编译与修改

在实际逆向工程中,你需要使用各种工具和技术来分析应用程序。本模块将通过实战案例,带你掌握 jadx 反编译、apktool 重打包、Smali 修改和 Frida 动态分析。

实战 1:使用 jadx 反编译分析

# jadx 命令行反编译
jadx -d output_dir target.apk \
  --no-res \              # 不解码资源文件
  --show-bad-code \       # 显示反编译失败的代码
  --deobf \               # 启用反混淆
  --deobf-min 3 \         # 最小重命名长度
  --deobf-max 64 \        # 最大重命名长度
  --threads-count 4       # 多线程处理

# jadx-gui 交互式分析
jadx-gui target.apk
# 功能:搜索类名、方法名、字符串
# 导航:Ctrl+Click 跳转到定义
# 搜索:Ctrl+Shift+F 全局搜索

# 常见搜索关键词(逆向分析优先级)
# 1. 搜索硬编码密钥
#    "api_key", "secret", "password", "token", "Bearer"
# 2. 搜索网络请求
#    "http://", "https://", "Retrofit", "OkHttp", "Volley"
# 3. 搜索加密相关
#    "Cipher", "SecretKey", "AES", "RSA", "MD5", "SHA"
# 4. 搜索本地存储
#    "SharedPreferences", "SQLite", "getWritableDatabase"
# 5. 搜索敏感操作
#    "root", "su", "Runtime.exec", "ProcessBuilder"

jadx 是最常用的 Java 反编译器。--deobf 参数对混淆的代码进行重命名,使变量和方法名更有意义。逆向分析时,优先搜索硬编码密钥、网络请求、加密操作和本地存储相关代码,这些通常包含最有价值的信息。

实战 2:Smali 修改与重打包

# 场景:绕过登录验证

# 1. 定位登录验证方法
# 在 jadx 中找到登录检查代码:
# public boolean checkLicense(String userId) {
#     // 验证逻辑...
#     return isValid;
# }

# 2. 找到对应的 Smali 文件
# target_decoded/smali/com/example/app/LicenseChecker.smali

# 3. 修改返回值(将 false 改为 true)
# 原始 Smali:
.method public checkLicense(Ljava/lang/String;)Z
    .locals 2
    # ... 验证逻辑 ...
    const/4 v0, 0x0    # false
    return v0
.end method

# 修改后:
.method public checkLicense(Ljava/lang/String;)Z
    .locals 2
    const/4 v0, 0x1    # true
    return v0
.end method

# 4. 重新打包
apktool b target_decoded -o modified.apk

# 5. 生成签名密钥
keytool -genkey -v -keystore my-release-key.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias my-key-alias

# 6. 签名 APK
jarsigner -verbose -sigalg SHA256withRSA \
  -digestalg SHA-256 \
  -keystore my-release-key.jks \
  modified.apk my-key-alias

# 7. 使用 apksigner(推荐,支持 v2 签名)
zipalign -v 4 modified.apk modified_aligned.apk
apksigner sign --ks my-release-key.jks \
  --out final_modified.apk \
  modified_aligned.apk

# 8. 安装并测试
adb install final_modified.apk
adb shell am start -n com.example.app/.ui.MainActivity

Smali 修改的完整流程:1) 用 jadx 找到目标方法;2) 在 apktool 解包的 smali 文件中定位对应代码;3) 修改 Smali 指令(如将返回值从 false 改为 true);4) 用 apktool 重新打包;5) 生成签名密钥并签名(修改后原始签名失效);6) 安装到设备测试。

实战 3:Frida 动态插桩

Frida 是什么?Frida 是一个动态代码插桩工具包,可以在运行时注入 JavaScript 脚本到目标进程中,修改函数行为、拦截参数和返回值。相比静态修改 Smali,Frida 无需重打包 APK,更加灵活。

// frida_script.js — Hook 登录验证方法

Java.perform(function() {
    console.log("[*] Frida script loaded");

    // Hook 特定类的方法
    var LicenseChecker = Java.use("com.example.app.LicenseChecker");

    // 方法 1:修改返回值
    LicenseChecker.checkLicense.implementation = function(userId) {
        console.log("[*] checkLicense called with: " + userId);
        var result = this.checkLicense(userId);  // 调用原始方法
        console.log("[*] Original result: " + result);
        return true;  // 强制返回 true
    };

    // 方法 2:拦截加密函数
    var Cipher = Java.use("javax.crypto.Cipher");
    Cipher.getInstance.overload("java.lang.String").implementation = function(algorithm) {
        console.log("[*] Cipher.getInstance: " + algorithm);
        return this.getInstance(algorithm);
    };

    Cipher.doFinal.overload("[B").implementation = function(input) {
        console.log("[*] Cipher.doFinal called");
        console.log("    Input length: " + input.length);
        var result = this.doFinal(input);
        console.log("    Output length: " + result.length);
        // 打印十六进制数据
        console.log("    Input hex: " + bytesToHex(input));
        console.log("    Output hex: " + bytesToHex(result));
        return result;
    };

    // 方法 3:拦截网络请求
    var URL = Java.use("java.net.URL");
    URL.$init.overload("java.lang.String").implementation = function(url) {
        console.log("[*] HTTP Request: " + url);
        return this.$init(url);
    };

    // 方法 4:Hook SSL Pinning 绕过
    var TrustManager = Java.use("javax.net.ssl.X509TrustManager");
    var SSLContext = Java.use("javax.net.ssl.SSLContext");

    // 辅助函数:字节数组转十六进制
    function bytesToHex(bytes) {
        var hex = "";
        for (var i = 0; i < bytes.length && i < 64; i++) {
            hex += ("0" + (bytes[i] & 0xFF).toString(16)).slice(-2);
        }
        return hex;
    }
});

Frida 脚本通过 Java.perform 进入 Java 运行时环境。Java.use 获取类引用,.implementation 替换方法实现。Hook 技巧:1) 在替换函数内调用 this.originalMethod() 保留原始逻辑;2) 拦截加密函数的输入/输出获取明文数据;3) 打印网络请求 URL 追踪 API 调用。

运行 Frida 进行动态分析

# 1. 在设备上启动 Frida Server
adb push frida-server-16.x.x-android-arm64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"

# 2. 列出设备上的进程
frida-ps -U

# 3. 附加到目标应用
frida -U -n "Target App" -l frida_script.js

# 4. Spawn 模式(从启动时 Hook)
frida -U -f com.example.targetapp -l fruda_script.js --no-pause

# 5. 使用 Python 脚本自动化
import frida
import sys

def on_message(message, data):
    if message['type'] == 'send':
        print(f"[*] {message['payload']}")
    elif message['type'] == 'error':
        print(f"[!] {message['description']}")

device = frida.get_usb_device()
session = device.attach("Target App")

with open("frida_script.js", "r") as f:
    script = session.create_script(f.read())

script.on('message', on_message)
script.load()

print("[*] Script loaded. Press Enter to detach...")
input()
session.detach()

Frida 动态分析流程:1) 将 Frida Server 推送到 Android 设备并启动;2) frida-ps -U 列出正在运行的应用;3) 用 -U -n 附加到运行中的应用,或用 -U -f 启动并立即 Hook;4) Python 脚本可以自动化整个流程,处理 Hook 输出的消息。注意:设备需要 root 权限才能运行 Frida Server。

网络流量分析

# 使用 mitmproxy 拦截 HTTPS 流量
pip install mitmproxy

# 启动代理
mitmproxy --listen-port 8080

# 配置 Android 设备代理
# 设置 → WLAN → 长按 → 修改网络 → 高级选项 → 代理
# 设置为电脑 IP:8080

# 安装 mitmproxy CA 证书
# 浏览器访问 mitm.it 下载证书
# 设置 → 安全 → 从存储安装证书

# 使用 Frida 绕过 SSL Pinning(如果应用有证书固定)
// ssl_bypass.js
Java.perform(function() {
    // OkHttp CertificatePinner 绕过
    var CertificatePinner = Java.use("okhttp3.CertificatePinner");
    CertificatePinner.check.overload("java.lang.String", "java.util.List")
        .implementation = function(hostname, peerCertificates) {
            console.log("[*] SSL Pinning bypassed for: " + hostname);
        };

    // TrustManager 绕过
    var TrustManager = Java.use("com.android.org.conscrypt.TrustManagerImpl");
    TrustManager.verifyChain.implementation = function() {
        console.log("[*] TrustManager bypassed");
        return Java.use("java.util.ArrayList").$new();
    };
});

# 同时运行 Frida + mitmproxy
frida -U -f com.target.app -l ssl_bypass.js --no-pause &
mitmproxy --listen-port 8080

网络流量分析三步走:1) 在电脑上启动 mitmproxy 代理服务器;2) 配置 Android 设备的代理指向电脑 IP;3) 安装 CA 证书使代理能解密 HTTPS。如果应用实现了 SSL Pinning(证书固定),需要用 Frida 绕过证书验证,然后才能看到明文的 HTTPS 请求内容。

以下哪种工具可以用于反编译 APK 文件?

模块 4:进阶主题 — 动态分析与对抗

本模块将深入探讨 Android 逆向工程的高级主题,包括动态分析框架、Hook 技术、应用加固对抗和自动化逆向。

Xposed Framework 深度使用

Xposed vs Frida:Xposed 通过修改 Zygote 进程实现全局 Hook,模块以 APK 形式安装,重启后持续生效。Frida 是临时的、可热加载的,更适合快速分析和调试。两者可以互补使用。

// Xposed 模块示例:Hook 微信/支付宝等应用
// 文件:app/src/main/java/com/example/xposed/MainHook.java

package com.example.xposed;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import android.util.Log;

public class MainHook implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam)
            throws Throwable {

        // 只 Hook 目标应用
        if (!lpparam.packageName.equals("com.target.app")) return;

        XposedBridge.log("[*] Hooking: " + lpparam.packageName);

        // Hook 特定方法
        XposedHelpers.findAndHookMethod(
            "com.target.app.auth.LoginManager",  // 类名
            lpparam.classLoader,
            "validateToken",                       // 方法名
            java.lang.String.class,                // 参数类型
            new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param)
                        throws Throwable {
                    // 方法执行前:读取/修改参数
                    String token = (String) param.args[0];
                    XposedBridge.log("[*] Token: " + token);
                    Log.i("Xposed", "Intercepted token: " + token);
                }

                @Override
                protected void afterHookedMethod(MethodHookParam param)
                        throws Throwable {
                    // 方法执行后:读取/修改返回值
                    Boolean result = (Boolean) param.getResult();
                    XposedBridge.log("[*] Result: " + result);
                    param.setResult(true);  // 强制返回 true
                }
            }
        );

        // Hook 构造函数
        XposedHelpers.findAndHookConstructor(
            "com.target.app.crypto.EncryptionHelper",
            lpparam.classLoader,
            java.lang.String.class,
            new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) {
                    String key = (String) param.args[0];
                    XposedBridge.log("[*] Encryption key: " + key);
                }
            }
        );
    }
}

// Xposed 模块配置:assets/xposed_init
// com.example.xposed.MainHook

Xposed 模块实现 IXposedHookLoadPackage 接口,在应用加载时自动执行 Hook。findAndHookMethod 定位并拦截目标方法,beforeHookedMethod 在方法执行前运行(可修改参数),afterHookedMethod 在方法执行后运行(可修改返回值)。Xposed 模块需要安装到设备上并在 Xposed Installer 中启用。

应用加固对抗

代码混淆(ProGuard/R8)

类名和方法名被重命名为 a.b.c 形式。对策:使用 jadx 的反混淆功能、动态调试获取运行时类名映射。

DEX 加壳(360加固/腾讯乐固)

DEX 文件被加密,运行时解密加载。对策:使用 Frida 在 DEX 加载后 dump 内存中的解密 DEX。

反调试检测

检测 ptrace、调试器附加、模拟器环境。对策:Hook 检测函数返回正常值,使用真机而非模拟器。

完整性校验

检查 APK 签名和文件哈希,检测是否被修改。对策:Hook 校验函数返回预期值,或修改校验逻辑本身。

// Frida 脚本:Dump 加壳应用的 DEX
Java.perform(function() {
    console.log("[*] Waiting for DEX decryption...");

    // 监控 DexFile.openDexFile 获取解密后的 DEX
    var DexFile = Java.use("dalvik.system.DexFile");

    DexFile.openDexFile.overload("java.lang.String")
        .implementation = function(sourcePathName) {
        console.log("[*] DEX loaded: " + sourcePathName);
        var result = this.openDexFile(sourcePathName);

        // 通过反射获取 cookie(mCookie 指向内存中的 DEX)
        var cookieField = DexFile.class.getDeclaredField("mCookie");
        cookieField.setAccessible(true);
        var cookie = cookieField.get(this);

        console.log("[*] DEX cookie obtained, ready to dump");
        return result;
    };

    // 方法 2:通过 ClassLoader 枚举所有已加载的类
    Java.enumerateClassLoaders({
        onMatch: function(loader) {
            try {
                loader.loadClass("com.target.app.MainActivity");
                console.log("[*] Found target class in loader: " + loader);
            } catch (e) {
                // 不是目标 ClassLoader
            }
        },
        onComplete: function() {
            console.log("[*] ClassLoader enumeration complete");
        }
    });
});

// Dump DEX 到文件
function dumpDex() {
    Java.perform(function() {
        var openMemory = Module.findExportByName("libc.so", "open");
        var readMemory = Module.findExportByName("libc.so", "read");

        Java.enumerateClassLoadersSync().forEach(function(loader, idx) {
            try {
                var dexFile = Java.cast(
                    loader.loadClass("dalvik.system.DexFile").$new(),
                    Java.use("dalvik.system.DexFile")
                );
                console.log("[*] Dumping DEX #" + idx);
                // 将 DEX 写入 /data/local/tmp/
            } catch(e) {}
        });
    });
}

对抗加壳应用的核心思路是等待 DEX 在内存中解密后再 dump。通过 Hook DexFile.openDexFile 可以捕获解密后的 DEX 加载事件;通过枚举 ClassLoader 可以找到目标类所在的 DEX。解密后的 DEX 可以保存到文件,然后用 jadx 正常分析。

自动化逆向分析

"""自动化 Android 逆向分析脚本"""
import subprocess
import os
import re
import json

class AutoReverser:
    def __init__(self, apk_path: str, output_dir: str = "analysis"):
        self.apk_path = apk_path
        self.output_dir = output_dir
        os.makedirs(output_dir, exist_ok=True)

    def run(self, cmd: str) -> str:
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
        return result.stdout + result.stderr

    def full_analysis(self):
        """一键全量分析"""
        print("[1/6] 反编译 APK...")
        self.decompile()

        print("[2/6] 提取 Manifest 信息...")
        manifest_info = self.analyze_manifest()

        print("[3/6] 搜索敏感字符串...")
        sensitive = self.find_sensitive_data()

        print("[4/6] 分析网络端点...")
        endpoints = self.find_endpoints()

        print("[5/6] 检查安全配置...")
        security = self.check_security()

        print("[6/6] 生成报告...")
        report = {
            "apk": self.apk_path,
            "manifest": manifest_info,
            "sensitive_data": sensitive,
            "endpoints": endpoints,
            "security_issues": security,
        }

        with open(f"{self.output_dir}/report.json", "w") as f:
            json.dump(report, f, ensure_ascii=False, indent=2)

        return report

    def decompile(self):
        """反编译 APK"""
        self.run(f"apktool d {self.apk_path} -o {self.output_dir}/decoded -f")
        self.run(f"jadx -d {self.output_dir}/java {self.apk_path} --no-res")

    def analyze_manifest(self) -> dict:
        """分析 AndroidManifest.xml"""
        manifest_path = f"{self.output_dir}/decoded/AndroidManifest.xml"
        with open(manifest_path, "r") as f:
            content = f.read()

        permissions = re.findall(r'uses-permission.*?name="([^"]+)"', content)
        activities = re.findall(r'activity.*?name="([^"]+)"', content)
        services = re.findall(r'service.*?name="([^"]+)"', content)
        exported = re.findall(r'exported="true"', content)

        return {
            "permissions": permissions,
            "activities": activities,
            "services": services,
            "exported_count": len(exported),
        }

    def find_sensitive_data(self) -> list:
        """搜索敏感数据"""
        patterns = {
            "api_key": r'(?i)(api[_-]?key|apikey)\s*[=:]\s*["\']([^"\']+)["\']',
            "password": r'(?i)(password|passwd|pwd)\s*[=:]\s*["\']([^"\']+)["\']',
            "token": r'(?i)(token|bearer)\s*[=:]\s*["\']([^"\']+)["\']',
            "secret": r'(?i)(secret|private[_-]?key)\s*[=:]\s*["\']([^"\']+)["\']',
        }

        findings = []
        java_dir = f"{self.output_dir}/java"
        for root, dirs, files in os.walk(java_dir):
            for file in files:
                if file.endswith(".java"):
                    path = os.path.join(root, file)
                    with open(path, "r", errors="ignore") as f:
                        content = f.read()
                    for name, pattern in patterns.items():
                        matches = re.findall(pattern, content)
                        if matches:
                            findings.append({
                                "type": name,
                                "file": path.replace(java_dir + "/", ""),
                                "matches": [m[1] if isinstance(m, tuple) else m for m in matches[:5]],
                            })

        return findings

    def find_endpoints(self) -> list:
        """提取网络端点"""
        endpoints = set()
        java_dir = f"{self.output_dir}/java"
        url_pattern = r'https?://[^\s"\'<>]+'

        for root, dirs, files in os.walk(java_dir):
            for file in files:
                if file.endswith(".java"):
                    path = os.path.join(root, file)
                    with open(path, "r", errors="ignore") as f:
                        content = f.read()
                    matches = re.findall(url_pattern, content)
                    endpoints.update(matches)

        return sorted(list(endpoints))

    def check_security(self) -> list:
        """检查安全配置"""
        issues = []

        # 检查 debuggable
        manifest = f"{self.output_dir}/decoded/AndroidManifest.xml"
        with open(manifest, "r") as f:
            if 'debuggable="true"' in f.read():
                issues.append("CRITICAL: debuggable=true in release build")

        # 检查明文 HTTP
        with open(manifest, "r") as f:
            content = f.read()
            if 'usesCleartextTraffic="true"' in content:
                issues.append("WARNING: Cleartext HTTP traffic allowed")

        # 检查备份
        if 'allowBackup="true"' in content:
            issues.append("INFO: Backup enabled (adb backup possible)")

        return issues

# 使用示例
reverser = AutoReverser("target.apk", "analysis_output")
report = reverser.full_analysis()
print(json.dumps(report, ensure_ascii=False, indent=2)[:2000])

这个自动化脚本整合了完整的逆向分析流程:反编译、Manifest 解析、敏感数据扫描、端点提取和安全检查。通过正则表达式批量扫描 Java 源码,可以快速发现硬编码的 API 密钥、密码、Token 和服务器地址。安全检查识别 debuggable、明文流量等常见安全问题。最终生成 JSON 格式的分析报告。

学习路径与资源

OWASP Mobile Security

OWASP MASVS(移动应用安全验证标准)和 MASTG(移动应用安全测试指南)是移动安全的权威参考。

Android Security CTF

参加 Mobile CTF 竞赛(如 Android CTF、DIVA、InsecureBankv2)在实战中提升逆向技能。

Frida 官方文档

frida.re/docs 提供完整的 API 文档和示例,是学习动态插桩的最佳资源。

高级进阶

学习 ARM 汇编、Linux 内核安全、SELinux 策略分析,向 Android 安全专家进阶。

课程总结:Android 逆向工程能力图谱

01

静态分析

APK 解包 → Manifest 分析 → Smali/Java 反编译 → 资源提取。jadx + apktool 是核心武器。

02

动态分析

Frida 运行时 Hook → 方法追踪 → 参数修改 → 加密算法还原。不需要修改 APK 即可分析运行时行为。

03

安全审计

敏感数据扫描 → 网络端点提取 → 安全配置检查 → 漏洞评估。自动化脚本提升审计效率。

04

对抗技术

代码混淆识别 → 加壳 DEX dump → 反调试绕过 → SSL Pinning 破解。攻击与防御的永恒博弈。

实战进阶路线

从入门到精通的推荐路径:先掌握 apktool + jadx 静态分析(1-2 周),再学习 Frida 动态插桩(2-3 周),然后参加 CTF 竞赛检验水平。进阶方向包括:Native 层分析(IDA Pro + ARM 汇编)、内核级安全研究(SELinux 策略、内核模块)、自动化漏洞挖掘(Fuzzing、符号执行)。逆向工程是一个需要大量实践的领域——理论只占 20%,动手分析真实应用占 80%。

以下哪种技术可以用于动态分析应用程序?