静态分析是逆向的基础。jadx 把乱码般的 DEX 变回人能读懂的 Java,但真正的挑战是从一堆 a.b.c() 里找到你想要的逻辑。
一、jadx 是什么?
jadx 是目前最好用的开源 Android 反编译工具,能将 DEX/APK 直接还原为可读的 Java 代码,并自动完成以下工作:
与 dex2jar + JD-GUI 的区别:
| 维度 | jadx | dex2jar + JD-GUI |
|---|
| 还原质量 | 更高,直接从 DEX 分析 | 较低,经过中间格式转换 |
| 处理混淆 | 内置反混淆支持 | 基本无 |
| 搜索功能 | 全局文本搜索 | 无 |
| 活跃维护 | ✅ 持续更新 | ❌ 基本停更 |
二、安装与启动
# 方式一:下载 Release 包(推荐)# https://github.com/skylot/jadx/releases# 下载 jadx-X.X.X.zip,解压即用# 启动 GUI./bin/jadx-gui# 命令行反编译./bin/jadx -d output/ target.apk# 只输出 Java 源码(不含资源)./bin/jadx -d output/ --no-res target.apk
三、基本操作
导入 APK
打开 jadx-gui → File → Open files
选择 .apk / .dex / .aab 文件
等待分析完成(大 APK 需要 1-2 分钟)
全局搜索(最常用)
| 快捷键 | 功能 |
|---|
Ctrl+F | 在当前文件中搜索 |
Ctrl+Shift+F | 全局文本搜索(最常用) |
Ctrl+N | 按类名搜索 |
Alt+F7 | 查找所有引用 |
Ctrl+G | 跳转到行号 |
F5 | 刷新反编译结果 |
四、代码混淆原理
ProGuard / R8 混淆器做的事情:
原始类名:UserLoginHelper → 混淆后:a原始方法:checkPassword → 混淆后:a原始字段:encryptionKey → 混淆后:b
混淆后的代码特征:
类名、方法名、字段名全为单字母或无意义短名
字符串常量可能被加密
调用链被打散,难以追踪
五、反混淆实战技巧
技巧一:字符串搜索(命中率最高)
从抓包中找到关键字符串,在 jadx 中全局搜索:
Ctrl+Shift+F 搜索:"sign" → 找签名相关代码"encrypt" → 找加密函数"token" → 找 token 生成"Authorization" → 找请求头处理"/api/login" → 找登录接口
技巧二:引用追踪
找到字符串常量后,右键 → Find Usage,追踪到调用它的方法:
// 搜索到 "AES/CBC/PKCS5Padding"// → 右键 Find Usage// → 定位到 EncryptUtil.encrypt() 方法// → 继续追踪该方法的调用者// → 找到登录签名生成逻辑
技巧三:从 AndroidManifest 入手
jadx 会解析 AndroidManifest.xml,从这里可以找到:
技巧四:重命名注释
jadx 支持直接在 GUI 中重命名:
右键类/方法/字段 → Rename(N键)
重命名会同步到所有引用处
配合注释逐步还原逻辑
// 重命名前publicStringa(Stringb, Stringc) {returna.a(b, c, "k3y");}// 重命名后(边读边改)publicStringgenerateSign(Stringdata, Stringtimestamp) {returnHmacUtils.hmacSha256(data, timestamp, "k3y");}技巧五:搜索 Android API 特征
不同功能有固定的 Android API 调用,直接搜 API 名:
| 目标 | 搜索关键词 |
|---|
| 网络请求 | OkHttpClient / Retrofit / HttpURLConnection |
| 加密 | MessageDigest / SecretKeySpec / Cipher |
| 存储 | SharedPreferences / SQLiteDatabase |
| 设备标识 | getDeviceId / Settings.Secure |
六、读懂 smali 基础
有时 Java 反编译结果不完整,需要看 smali(Android 字节码的文本表示):
# smali 对照 Java.method public encrypt(Ljava/lang/String;)Ljava/lang/String;# → public String encrypt(String)invoke-virtual {v0, v1}, Lcom/app/Util;->doEncrypt(Ljava/lang/String;)Ljava/lang/String;# → v0.doEncrypt(v1)const-string v2, "secret_key"# → String v2 = "secret_key"move-result-object v3# → v3 = 上一条指令的返回值
七、实战:定位登录签名函数
步骤一: Burp 抓包,发现请求体中有 sign 字段
步骤二: jadx 全局搜索 "sign",找到赋值位置:
params.put("sign", SignUtil.generate(body, timestamp));步骤三: 点击 SignUtil,进入类查看 generate 方法:
publicstaticStringgenerate(Stringbody, longts) {Stringraw=body+ts+"SECRET_2024";returnmd5(raw).toUpperCase();}步骤四: 重命名相关类和方法,理清完整调用链。
步骤五: 用 Python 复现:
importhashlibdefgenerate_sign(body: str, ts: int) ->str:raw = f"{body}{ts}SECRET_2024"returnhashlib.md5(raw.encode()).hexdigest().upper()
八、常用快捷键速查
| 快捷键 | 功能 |
|---|
Ctrl+Shift+F | 全局文本搜索 |
Alt+F7 | 查找所有引用 |
Ctrl+B | 跳转到声明 |
N | 重命名 |
Ctrl+Z | 撤销重命名 |
Ctrl+E | 最近打开文件 |
Ctrl+W | 关闭当前标签 |
Ctrl+P | 查看方法参数 |
下一篇:Frida 从零搭建到第一个 Hook