不运行 APK 就看懂它内部:rea 基于 JADX headless 的 Android 静态分析完整指南
【免费下载链接】reaReverse engineer anything with agents, from app behavior down to native binaries.项目地址: https://gitcode.com/GitHub_Trending/rea2/rea
手里拿到一个.apk,想知道它到底做了什么,却不想、也不能在自己机器上真正把它跑起来?这正是 rea 的 Android APK 静态分析要解决的问题:它把一个现成的 jadx-headless-mcp 0.7.1 引擎(所谓 headless,就是没有图形界面的反编译程序)包在一层严格校验后面,让"打开 APK、读清单、找类、拆方法"变成五条可复现、留证据的命令。读完这篇,你能自己配好 JDK 与引擎 JAR 环境、跑通 CLI 和 MCP 两套入口,并正确解读输出里每一个字段的含义与边界。
先看能力边界:不执行 APK 的静态分析能走到哪
先说结论:rea 对 APK 的态度是"只看不碰"——它不执行 APK,也不启动目标代码,所以这套能力天生适合沙箱外的初步排查。
它明确不做的事,同样值得在动手前背下来:
- 不处理 split APK / AAB(
.aab后缀本质是 ZIP 归档,不会当成独立 APK 对待); - 不做签名校验,包检查里
signature_verification恒为not_performed; - 不提供完整资源表语义,不分析 native 库,也没有 Android 运行时捕获。
它能做的,是五个静态问题:包里有什么声明、某个类在不在、这个类有哪些成员、某个方法体写了什么、谁引用了这个类或方法。
如果还想看归档层面,可以用既有的产物清点/提取工具拿到归档证据,再喂给免执行投影命令rea project-android-application-graph(MCP 方法project_android_application_graph,参数inventory_evidence)。投影会报告精确的组件路径与哈希、运行时族提示和基于路径的桥接假设,但它不解码 DEX,也不声称观测到了运行时调用。
动手之前:跑静态 APK 分析要自备什么
结论:rea 本身不安装 Java、Android SDK、JADX、设备服务或模拟器——所有工具都由你供给,它只负责"管好"你给的东西。
你需要备齐两样:
- 完整的 JDK 17 或更新版本,必须包含
jdk.compiler编译器模块,因为 rea 随包分发的元数据桥 bridge/android/ReaJadxBridge.java 要用 JDK 的 source-file 模式对未修改的引擎 JAR 现场编译——一个只能运行的 JRE 不够用; - jadx-headless-mcp 0.7.1 的引擎 JAR,并且建议先核对它的 SHA-256 是否与 src/android/JadxRelease.ts 中固化的发布元数据一致(
6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75,上游提交5844800a486d2248fa949b74c9896285f5b54de2)。
然后在 shell 或 MCP 服务器环境里指给 rea:
export REA_JADX_MCP_JAR=/absolute/path/jadx-headless-mcp-0.7.1-all.jar # 可选:指定一个已存在的 JDK,而不是用 PATH 上的 java export JAVA_HOME=/absolute/path/existing-jdk解析规则(见 src/android/JadxConfiguration.ts)可以概括成一张表:
| 配置项 | 要求 | 不满足时 |
|---|---|---|
REA_JADX_MCP_JAR | 绝对路径、常规可读文件,最终以realpath归一 | 抛AnalysisCapabilityUnavailableError,错误里自带补救指引 |
JAVA_HOME | 可选;一旦设置就必须是绝对路径,且$JAVA_HOME/bin/java得是常规可执行文件 | 相对路径直接拒绝 |
| 两者都缺省 | 回落到PATH上的java | — |
注意两点。其一,这套配置只在 Android 操作被选中时才解析,跑别的命令不会被它卡住。其二,平台边界在解析第一步就强制:只支持linux与darwin,Windows 不支持该 provider 的自持进程边界;Linux 上 JADX 会启动一个 REA 自持的进程,需要PATH里有 procps 兼容的ps。仓库完成的真实验证环境是 macOS arm64 + OpenJDK 21 + 公开的 Appium ApiDemos fixture,Linux/JDK 25 尚未走完真实验证。另外提醒一句:该功能族存在于仓库 main 分支与 npm 4.1.0,选用前先核对已发布包的边界与目标服务器的工具列表。
你可以向 APK 提的五个问题:一个操作对应一条命令
结论:五个 CLI 命令对应五个同名的 snake_case MCP 方法,每个请求都带path,参数契约收得很紧——类/方法操作要求精确的全限定类名与声明方法名。
inspect-android-package:包里有什么?保留解码后的 manifest XML、摘要声明、权限和类/资源计数;摘要字段由上游引擎抽取,存疑时对照保留的 XML 核查。search-android-classes:有没有叫这个名字的类?query是大小写敏感的子串,空串等于全量清点类名;搜索会消费所有分页,计数对不上就失败。inspect-android-class:这个类有哪些成员?读解析后的元数据而不生成源码,返回完整展示类型、字段与内部类名;代码生成隐藏的 synthetic 成员可能出现,overload index 绑定到当前产物与引擎,不是跨构建的稳定身份。inspect-android-method:这个方法体写了什么?报告 Java 或 smali 表示、回退标记与完整/部分文本;同名重载用--overload-index指认。trace-android-references:谁在引用它?--method-name可选,省略时追的是类级引用;目标方法存在多个重载时会报 unsupported,因为引擎的引用操作没法替你选重载。
rea inspect-android-package /path/Example.apk rea search-android-classes /path/Example.apk MainActivity rea inspect-android-class /path/Example.apk example.MainActivity rea inspect-android-method /path/Example.apk example.MainActivity onCreate rea inspect-android-method /path/Example.apk example.MainActivity select --overload-index 1rea trace-android-references /path/Example.apk example.MainActivity rea trace-android-references /path/Example.apk example.MainActivity --method-name onCreateMCP 侧长这样(类/方法操作按需给class_name、method_name与可选overload_index):
{ "name": "inspect_android_method", "arguments": { "path": "/path/Example.apk", "class_name": "example.MainActivity", "method_name": "select", "overload_index": 1 } }解读输出:四个最容易读错的地方
结论:rea 的输出哲学是"不确定就不说",所以读输出时,没给你的一定不是没有,而是没把握。
重载消歧:宁拒勿猜。重载(overload)指同一个类里名字相同、参数不同的多个方法。rea 先让引擎给出成员清单,按引擎返回顺序为同名方法分配从 0 起的overload_index。如果你没带 index 而候选不止一个,请求会被直接拒绝,错误消息里列出全部候选签名——它绝不静默帮你选第一个。引擎在 Java 反编译失败时会退回 smali 表示(smali 是 Android 字节码的汇编级文本),而这条回退会把所有同名重载的函数体拼接在一起;rea 对重载方法直接拒绝该回退,避免把一锅函数体全算到某一个被选中的重载头上。没有函数体的 native/abstract 方法则返回body_status: "not_available",不猜测是哪个实现标志导致的。
partial意味着被截断。解码后的 manifest 与方法文本有 1 MiB 的上游字节预算,超限时截断会被显式报告、状态标为partial。反编译源码是派生表示——拿回了完整文本,不等于拿到了完整语义,更不等于字节级一致的重建。
引用不等于运行时调用。trace-android-references给出的是 provider 节点之间的入向静态关系,source_line与dex_offset显式为null(保持未知)。看到"谁调了谁",别脑补成"运行时确实发生了这次调用"。
Evidence 替你留了底。这里的 Evidence 是与产物绑定的结构化记录:原始请求、APK SHA-256、实际用到的引擎 JAR SHA-256、producer 的原始响应(含 server 版本、桥 SHA-256、加载状态和每次工具调用的原始响应)都留在里面(见 src/android/JadxSession.ts)。source_revision只在 JAR 字节与审计过的发布 JAR 匹配时才填充,否则为null——以后引用结论时,这条证据链就是出处。
幕后机制:rea 是怎么把引擎管住的
结论一句话:引擎不是 rea 的,但对引擎的控制权全是 rea 的。手段分三层:身份核验、会话管理、硬预算。
🔒 引擎"户口本":双重锁定。上游既有提交号也有 JAR 字节 SHA-256,两个锚点钉死在 src/android/JadxRelease.ts。每次分析前,rea 还会调用引擎的rea_jvm_status工具核对它自报的版本是不是 0.7.1,不一致直接抛AnalysisCapabilityUnavailableError;元数据桥自身的身份(rea-jadx-bridge,版本 1)也在连接时重新校验。
会话复用与确定性清理。每个实例保留一个序列化会话,工作区是私有的不可变副本(rea-android-前缀),里面放着 APK、引擎 JAR 和元数据桥的拷贝;桥拷入后被设为0o400只读并重新哈希比对,防止准入期间被动手脚。能不能复用旧会话由"复用键"决定——一个把 APK 路径 + APK SHA-256 + JAR 路径 + JAR 哈希 + 桥哈希 + Java 可执行文件 + JVM 参数 + 相关 Java 环境变量拼成的 JSON;键一变,先退役旧会话再起新的。准入前还会重哈希 APK 与target.sha256比对,事后发现文件变过就报 "APK bytes changed after admission"。会话在 60 秒空闲、目标/配置变化、失败、超时、取消、MCP 断开或服务器关闭时清理;一次性 CLI 命令返回前总是 join 清理。最坏情况:进程清理无法确认时,rea 保留工作区并报告其位置,该实例抛出携带runId与工作区路径的ProviderCleanupError,并阻塞后续引擎启动,直到你处理完报告的资源。无论哪条路径,rea 从不写回原始 APK,也不启动目标代码。这套逻辑的主干在 src/android/JadxProvider.ts。
⏱ 时间与字节都有硬顶。这不是"大概能扛多少"的经验值,而是一条条写死的内存安全边界:
| 边界 | 上限 | 触顶时的行为 |
|---|---|---|
| 单操作执行期限(到达队首后) | 120 秒(OPERATION_TIMEOUT_MS) | 按超时失败 |
| 上游反编译上限 | 90 秒 | 引擎侧边界 |
| manifest / 方法文本上游预算 | 1 MiB | 截断并标记partial |
| 类名分页 | 每片 4,096 个名称 | 切片翻页,不改变搜索契约 |
| 单条协议帧 | 8 MiB(src/android/JadxMcpTransport.ts) | 以诊断失败收场 |
| 单操作 stdout/stderr 合计 | 32 MiB | 拒绝把不完整清单当完整结果返回 |
JVM:单 worker,配置是你自己的事。单个 JADX 分析 worker 独立于 JVM 处理器与 GC 设置(load_apk固定threads: 1, resources: "full")。rea 不替你做默认堆或可见 CPU 的覆盖,Java 按自己的默认 ergonomics 运行并继承调用方的 JVM 设置;可选的 provider 变量会被映射成显式参数:REA_JADX_HEAP_MIB→-Xmx<N>m,REA_JADX_ACTIVE_PROCESSOR_COUNT→-XX:ActiveProcessorCount=<N>。两者都必须是正的 safe integer,处理器计数还必须落在 JVM 有符号 32 位范围内(超过 2,147,483,647 直接拒绝)。rea 保留JAVA_TOOL_OPTIONS、JDK_JAVA_OPTIONS、_JAVA_OPTIONS而不替换它们,Java 的标准优先级照旧生效。Evidence 里的heap_limit_mib是 worker 观测到的Runtime.maxMemory()向下取整的 MiB,可能与配置的-Xmx不同(Serial GC 下尤其明显);堆也不是总 RSS 上限,JVM 还会用 native 内存。
自己跑一遍:不装任何东西验证真实引擎
结论:仓库自带一条"真实引擎验证车道",全程不装任何工具,几分钟跑完。
npm run fixtures:android export REA_JADX_MCP_JAR="$PWD/_reference/apk-integration/jadx-headless-mcp-0.7.1-all.jar" export REA_ANDROID_TEST_APK="$PWD/_reference/apk-integration/ApiDemos-debug.apk" npm run verify:android下载脚本 scripts/fixtures/android/fetch.mjs 会校验固定 SHA-256,复用匹配文件,拒绝覆盖不同的已有文件;APK 与 JAR 落在被忽略的_reference/下,不会混进包内容。验证脚本 scripts/verify/android/analysis.mjs 先断言 APK 与 JAR 的哈希和 fixture/审计值一致,再对五个操作逐一跑真实的 CLI 与 MCP 请求——它和合成的协议回归测试相互独立。整条车道不需要 Gradle 构建、模拟器、Android SDK 或 Ghidra。
小结:反编译是引擎的事,"可引用"是 rea 的事
如果把反编译引擎比作相机,rea 就是那个给每张照片都盖章时间戳、校验和与设备登记号的拍摄助理。遇到歧义(同名重载)它拒绝猜,遇到不确定(字节预算、分页计数)它显式打标,遇到答不了的(运行时行为、签名、native)它把边界原样写进输出。对需要把分析结论拿出来给别人看的工程师来说,这种"每个字段都能溯源、每条拒绝都有理由"的确定性,比多一行反编译代码更值钱。
【免费下载链接】reaReverse engineer anything with agents, from app behavior down to native binaries.项目地址: https://gitcode.com/GitHub_Trending/rea2/rea
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考