JADX MCP Server 配置教程:让 Claude Code / Codex 读 APK 反编译代码(含实例)
本页目录
想让 Claude Code 或 Codex 直接读 APK 的反编译代码,做法是把 JADX 以无头方式跑在一个 MCP Server 后面,给 agent 一组「传 APK 路径就能查」的工具。本文用的 Server 是第三方开源工具 REA,配合 jadx-headless-mcp 0.7.1 的 JAR:在 Server 的 env 里设好 REA_JADX_MCP_JAR(可选 JAVA_HOME),重启客户端,agent 就多了 5 个 Android 工具,不需要一直开着 jadx-gui。
下文所有命令都在 macOS 上实际跑过:npm 版 REA 6.7.0、Temurin JDK 17.0.18、0.7.1 的 JAR,Claude Code 和 Codex 都用全新配置。请只分析你有权分析的 APK,本文两个示例都是开源应用。
无头 JADX 和 jadx-ai-mcp 的区别
另一个常见方案 jadx-ai-mcp 是 JADX 图形界面的插件,外加一个独立的 Python MCP Server。你在 jadx-gui 里打开 APK,agent 读的是 GUI 当前加载的内容,适合边点边看的交互式场景。
| REA + jadx-headless-mcp | jadx-ai-mcp | |
|---|---|---|
| 是否需要 GUI | 不需要 | 需要 jadx-gui 加插件 |
| 怎么指定 APK | 每次调用都带 APK 路径 | GUI 里当前打开的那个 |
| 输出 | 带 APK 和引擎 JAR 摘要的 Evidence | 插件返回的内容 |
| 同一 Server 能否分析其他目标 | 原生、JavaScript、.NET、Web、固件 | 只有 Android |
无头方案的好处是结果可复现,CI 或直接用 rea 命令行也能跑。
需要准备什么
- 完整的 JDK 17 或更高版本。 REA 启动时要编译一个小的 Java 元数据桥,需要
jdk.compiler模块,JRE 没有。 - jadx-headless-mcp 0.7.1,v0.7.1 release 里的
-all.jar,SHA-256 为6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75。 - REA 6.7.0,已注册到你的 agent(见 安装指南)。
- Linux、macOS 或 Windows x64。其他平台(如 Windows arm64)会返回
unsupported_host。
这些 REA 都不替你装。每次调用 JADX 工具时,它会检查平台、找到 JAR 和 java、执行 java --list-modules,再读 JAR 的 ZIP 目录确认桥接所需的类都在。任一步失败都返回 capability_unavailable,JADX 不会启动。
第 1 步:检查 JDK
java --list-modules | grep -E '^(java\.base|jdk\.compiler)@'
要有 jdk.compiler 这一行,且 java.base@ 版本不低于 17。没有 jdk.compiler 就说明装的是 JRE。装了多个 Java 时用 JAVA_HOME 指定,必须是绝对路径。
第 2 步:下载并校验 JAR
shasum -a 256 jadx-headless-mcp-0.7.1-all.jar
# Windows PowerShell:Get-FileHash .\jadx-headless-mcp-0.7.1-all.jar -Algorithm SHA256
一定要用 -all 的 JAR。REA 会检查里面有没有 jadx-headless-mcp 的会话类、JADX 的 JadxDecompiler、Gson 和 Kotlin MCP SDK 的 Server 类,普通 JADX 发行包或瘦 JAR 都过不了。放到固定位置,比如 ~/tools/jadx/。
第 3 步:先在终端里跑通
改 agent 配置之前,先确认 Java 和 JAR 没问题。以 F-Droid 客户端 的 APK 为例:
export REA_JADX_MCP_JAR="$HOME/tools/jadx/jadx-headless-mcp-0.7.1-all.jar"
export JAVA_HOME=/absolute/path/to/jdk-21 # 可选
rea doctor --provider jadx --json
rea inspect-android-package /absolute/path/F-Droid.apk --json
doctor 的输出(节选):
"name": "jadx", "ok": true, "classification": "healthy",
"detail": "JADX MCP bridge JAR validated at /…/jadx-headless-mcp-0.7.1-all.jar; its path is recorded for agent registrations. This checks configuration, not whether a selected APK fits in the worker's memory."
我们 2026-10-20 下载的 F-Droid 是 2.0.2(SHA-256 19d51fad…7b52),耗时 4.5 秒,报告包名 org.fdroid.fdroid、min SDK 24、target SDK 37、31 个权限、11,960 个类。这一步通了,之后 MCP 再出问题就只可能是 agent 配置。
第 4 步:在 Claude Code 里加 env
在已经设好 REA_JADX_MCP_JAR 的终端里运行 setup,它会把变量写进注册信息(JAVA_HOME、REA_JADX_HEAP_MIB、REA_JADX_ACTIVE_PROCESSOR_COUNT、REA_JADX_OPERATION_TIMEOUT_MS 设了的话也一并写入):
export REA_JADX_MCP_JAR="$HOME/tools/jadx/jadx-headless-mcp-0.7.1-all.jar"
npx [email protected] setup --client claude_code --client codex --yes
加 --dry-run 会先打印 Detected provider environment: JAVA_HOME=…, REA_JADX_MCP_JAR=…,不写文件。MCP 客户端启动 Server 时用的是配置文件里的环境变量,不是你终端的,所以要打开文件确认。Claude Code 的配置在 ~/.claude.json:
{
"mcpServers": {
"rea": {
"command": "npx",
"args": ["-y", "[email protected]", "mcp"],
"env": {
"JAVA_HOME": "/absolute/path/to/jdk-17",
"REA_JADX_MCP_JAR": "/Users/you/tools/jadx/jadx-headless-mcp-0.7.1-all.jar"
}
}
}
}
如果运行 setup 时没设变量,就手动补上 env。然后彻底退出 Claude Code 再开新会话(正在运行的客户端会沿用旧的 Server 进程和旧环境),用 /mcp 确认 rea 已连接。以后重跑 setup 不会覆盖你已有的 env。
第 5 步:在 Codex 里加 env
Codex 读 ~/.codex/config.toml(或 $CODEX_HOME/config.toml)。setup 写入的内容:
[mcp_servers.rea]
command = "npx"
args = [ "-y", "[email protected]", "mcp" ]
startup_timeout_sec = 30
tool_timeout_sec = 450
[mcp_servers.rea.env]
JAVA_HOME = "/absolute/path/to/jdk-17"
REA_JADX_MCP_JAR = "/home/you/tools/jadx/jadx-headless-mcp-0.7.1-all.jar"
Windows 上路径用 TOML 字面量字符串,例如 'D:\tools\jadx-headless-mcp-0.7.1-all.jar';setup 写的 command 保持原样。改完开一个新的 Codex 会话。
第 6 步:大 APK 的可选限制
| 变量 | 作用 | 示例 |
|---|---|---|
REA_JADX_HEAP_MIB |
-Xmx<value>m |
8192 |
REA_JADX_ACTIVE_PROCESSOR_COUNT |
-XX:ActiveProcessorCount=<value> |
4 |
REA_JADX_OPERATION_TIMEOUT_MS |
单次操作超时(默认 120 秒) | 300000 |
三个都只接受纯整数,堆写 8g、超时写 5m 都被拒绝了。大 APK 跑不动时,加大堆内存通常比反复重试有效。
5 个工具与提问方式
| MCP 工具 | CLI 命令 | 用途 |
|---|---|---|
inspect_android_package |
rea inspect-android-package |
Manifest、SDK、权限、数量统计 |
search_android_classes |
rea search-android-classes |
按类名子串搜索(区分大小写) |
inspect_android_class |
rea inspect-android-class |
字段、方法、重载序号 |
inspect_android_method |
rea inspect-android-method |
反编译单个方法(必要时回退 smali) |
trace_android_references |
rea trace-android-references |
静态入向引用 |
每次调用都带 APK 的绝对路径 path,不存在「先打开」这一步。提问时说清目标和想要的证据,不用规定工具顺序:
Using REA's Android tools, inspect /absolute/path/F-Droid.apk.
1. Report the package name, minSdk/targetSdk and the declared permissions.
2. Find the class registered as android:name on <application> in the manifest,
list its methods, and decompile onCreate.
3. Tell me which statements come from decompiled code and which are your
inference. List anything you could not determine.
从 manifest 入手,agent 拿到的是真实类名。发布版常被压缩和重命名,只靠类名搜索容易漏。要求列出「无法确定的部分」,能让 agent 把观察到的事实和推测分开。
实例:Aegis 的六位验证码是怎么算出来的
目标是 Aegis Authenticator v3.4.3(GPL-3.0),从 GitHub release 下载。先记下摘要,每个结果都会带上它:
shasum -a 256 aegis-v3.4.3.apk
# fa091c91912c0d56ec3553394fac4158f9a9d178e93bcbb35419601d788cb528 (6,881,897 字节)
F-Droid 上的版本由 F-Droid 自己构建签名,字节和摘要都不一样。下面是 agent 通过 MCP 会做的同一组调用,这里用 CLI 演示。
包信息。 rea inspect-android-package /absolute/path/aegis-v3.4.3.apk --json 用时 3.8 秒,normalized_result 节选:
"package_name": "com.beemdevelopment.aegis",
"version_name": "3.4.3",
"version_code": "82",
"min_sdk": "23",
"target_sdk": "36",
"class_count": 3077,
"resource_count": 1106,
"signature_verification": "not_performed"
注意 version_code 和 SDK 级别是字符串。这一步不验签名,也不会运行应用。
通过 MCP 分析大 APK 时传 detail: "summary":Aegis 的响应从 26,991 个字符降到 4,634 个,去掉的是解码后的 manifest 文本。之后要看 manifest,把摘要里的 parent_evidence_id 传给 inspect_analysis_view,参数为 {"source": {"kind": "retained-evidence", "evidence_id": "<parent_evidence_id>"}, "view": {"kind": "android", "facet": "manifest", "offset": 0, "limit": 4000}},分页读取(offset 按 UTF-16 码元计,Aegis 的 manifest 共 10,988)。这个 ID 只在产生它的会话里有效,拿到别的会话里用会返回 evidence_integrity_mismatch。
搜索类名。
rea search-android-classes /absolute/path/aegis-v3.4.3.apk Totp
三个结果:otp.TotpInfo、ui.views.TotpProgressBar、importers.TotpAuthenticatorImporter(省略了包名前缀),只有第一个像是计算逻辑。
列成员,再读指定重载。
rea inspect-android-class /absolute/path/aegis-v3.4.3.apk com.beemdevelopment.aegis.otp.TotpInfo
rea inspect-android-method /absolute/path/aegis-v3.4.3.apk \
com.beemdevelopment.aegis.otp.TotpInfo getOtp --overload-index 1
getOtp() 是 0 号重载,getOtp(long) 是 1 号。不给序号 REA 会直接拒绝,不会替你猜。序号只对这个 APK 和引擎有效,换了构建不要沿用。
找调用者。
rea trace-android-references /absolute/path/aegis-v3.4.3.apk \
com.beemdevelopment.aegis.otp.OtpInfo --method-name getOtp
两个调用者:ui.views.EntryHolder.getOtp(显示验证码)和 ui.MainActivity.copyEntryCode(复制验证码)。每条引用都带 direct_invokes 列表,包含操作码(如 INVOKE_VIRTUAL)、DEX 方法索引和代码单元偏移。OtpInfo.getOtp 只有一个重载;追踪 TotpInfo.getOtp 时不带 --overload-index 会被拒绝:Select one of 2 overloads: 0: getOtp(): java.lang.String; 1: getOtp(long): java.lang.String。
怎么看这些结果。
- 反编译出的 Java 是推导出来的视图,文本完整不代表语义完整。静态初始化块(
<clinit>)返回的是 smali。 - 引用是静态的入向关系,不是运行时实际发生的调用,反射和动态加载仍然未知。
- 压缩过的构建里类名不可信:Aegis 的 OTP 生成逻辑出现在
kotlin.ExceptionsKt下,所以要从已知入口顺着调用往下跟。
资源、设备和 Flutter 相关的工具见 用 REA 分析 Android APK。
连不上时先查这些
以下都是我们自己踩到过的:
- Server 读不到
REA_JADX_MCP_JAR。 没写进 Server 的env,或者写的是相对路径。在终端里 export 影响不到客户端启动的 Server。 JAVA_HOME是相对路径或用了~。 设成JAVA_HOME=jdk17时,6.7.0 的 Server 打印REA configuration is invalid: JAVA_HOME must be absolute.,不响应initialize,/mcp里rea显示失败。请写字面量绝对路径。- 装的是 JRE。 元数据桥编译失败,把
JAVA_HOME指向完整 JDK。 - JAR 不对。 非
all的包或 JADX 发行包通不过类检查。 - 堆大小带了单位。 报错
REA_JADX_HEAP_MIB must be a positive safe integer; received 8g. - 大 APK 超时。 加大堆内存、缩小请求范围,超大应用不要用空查询列全部类。
- 手里是
.aab。 App Bundle 会被拒绝,请用独立 APK(见 APK 与 AAB 解包)。
Windows 我们没有测试。Windows x64 上如果 Server 找不到 Java,就在 env 里设 JAVA_HOME;也可以在 WSL 里配 Linux 版 JDK。