模块 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 逆向工程能力图谱
静态分析
APK 解包 → Manifest 分析 → Smali/Java 反编译 → 资源提取。jadx + apktool 是核心武器。
动态分析
Frida 运行时 Hook → 方法追踪 → 参数修改 → 加密算法还原。不需要修改 APK 即可分析运行时行为。
安全审计
敏感数据扫描 → 网络端点提取 → 安全配置检查 → 漏洞评估。自动化脚本提升审计效率。
对抗技术
代码混淆识别 → 加壳 DEX dump → 反调试绕过 → SSL Pinning 破解。攻击与防御的永恒博弈。
实战进阶路线
从入门到精通的推荐路径:先掌握 apktool + jadx 静态分析(1-2 周),再学习 Frida 动态插桩(2-3 周),然后参加 CTF 竞赛检验水平。进阶方向包括:Native 层分析(IDA Pro + ARM 汇编)、内核级安全研究(SELinux 策略、内核模块)、自动化漏洞挖掘(Fuzzing、符号执行)。逆向工程是一个需要大量实践的领域——理论只占 20%,动手分析真实应用占 80%。
以下哪种技术可以用于动态分析应用程序?