跨平台 APK/DEX 方法和字段引用查找工具,支持调用链追踪、ProGuard/R8 反混淆以及 Android 隐藏 API 检测。
灵感来源于 Android 的 veridex 工具,使用 Go 重新实现并增强了功能:更快的反射检测、调用链追踪(veridex 仅显示一层)以及灵活的输出格式。
--fail-on blocked 在发现受限 API 时返回非零退出码.dexfinder.yaml 用于项目默认值,命令行参数可覆盖Homebrew(macOS / Linux):```bash brew install junelegency/tap/dexfinder
**脚本**(auto-detects OS/arch):```bash
curl -sSL https://raw.githubusercontent.com/JuneLeGency/dexfinder/main/install.sh | bash
Go install:```bash go install github.com/JuneLeGency/dexfinder/cmd/dexfinder@latest
**二进制文件**: 从 [Releases](https://github.com/JuneLeGency/dexfinder/releases) 下载。
## 快速开始```bash
# Show APK overview
dexfinder --dex-file app.apk --stats
# Find all calls to getDeviceId (IMEI)
dexfinder --dex-file app.apk --query "getDeviceId"
# Trace call chains as merged tree
dexfinder --dex-file app.apk --query "getDeviceId" --trace
# Trace as flat call stacks (Java crash style)
dexfinder --dex-file app.apk --query "getDeviceId" --trace --layout list
# Exact JNI signature query
dexfinder --dex-file app.apk \
--query "Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;" \
--trace --depth 8
# Hidden API detection
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv
--query 标志接受多种输入样式。dexfinder 会自动检测并在它们之间进行转换。
| 格式 | 示例 | 行为 |
|---|---|---|
| 简单名称 | getDeviceId | 对所有的API执行模糊子串匹配 |
| Java 类 | android.telephony.TelephonyManager | 该类的所有方法/字段 |
| Java 类#方法 | android.telephony.TelephonyManager#getDeviceId | 该方法的所有重载 |
| Java 完整签名 | ...TelephonyManager#getDeviceId() | 精确匹配 + 重载回退 |
| DEX/JNI 签名 | Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; | 仅精确匹配 |
dexfinder --dex-file app.apk --query "requestLocationUpdates" dexfinder --dex-file app.apk --query "android.location.LocationManager#requestLocationUpdates" dexfinder --dex-file app.apk --query "Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V"
## 输出控制
三个独立的轴,可自由组合:```
--format (text / json / model / html / sarif) what to output
--layout (tree / list) how to arrange traces
--style (java / dex) how to display names
--color (auto / always / never) terminal colors
--format| 值 | 描述 |
|---|---|
text | 带彩色标签的纯文本输出(默认) |
json | JSON — 扫描结果或带有树形/列表布局的跟踪 |
model | 包含完整 MethodInfo/FieldInfo 类型的结构化 JSON(适用于 IDE/CI) |
html | 自包含 HTML 报告,带有可折叠树和搜索功能 |
sarif | SARIF 2.1.0 静态分析格式(GitHub / VS Code) |
--layout(与 --trace 一起使用)| 值 | 描述 |
|---|---|
tree | 合并树 — 共享调用路径折叠为一棵树(默认) |
list | 平列表 — 每个唯一调用链显示为独立栈 |
--style| 值 | 示例 | 使用场景 |
|---|---|---|
java | com.example.Foo.method(Foo.java) | 人类可读(默认) |
dex | Foo.method(Ljava/lang/String;)V | 精确签名分析 |
--scope(搜索范围)控制查询匹配的引用类型。这对理解结果至关重要。
| 值 | 搜索内容 | 回答的问题 | 输出标签 |
|---|---|---|---|
all | 被调用 API + 字段 + 代码字符串 | “谁调用了这个 API?”(默认) | [METHOD] [FIELD] [STRING] |
callee | 仅 invoke-* / get/put 指令中的目标 API 签名 | “谁调用了这个特定方法/字段?” | [METHOD] [FIELD] |
caller | 仅调用方法的签名 | “这个方法内部调用了什么?” | [CALLER→] |
string | const-string 指令中的字符串常量 | “这个字符串在代码中哪里被使用?” | [STRING] |
string-table | 代码字符串 + 完整 DEX 字符串表 | “这个字符串在 DEX 中的任何地方存在吗?”(包括注解、死代码) | [STRING] [STRING_TABLE] |
everything | 以上所有组合 | 全貌 | 所有标签 |
理解 callee 与 caller:``` scope=callee: "Who calls finish()?" onCreate ──calls──→ finish() ← these callers are shown onResume ──calls──→ finish()
scope=caller: "What does finish() call internally?" finish() ──calls──→ Log.i() ← these callees are shown finish() ──calls──→ super.finish()
`--scope=all`(默认值)等同 `callee` + `string`。默认排除 `caller` 方向,因为它回答的是截然不同的问题。当需要时,请显式使用 `--scope=caller` 或 `--scope=everything`。
**理解输出标签:**
| 标签 | 含义 |
|---|---|
| `[METHOD]` | 一个**被调用**的方法匹配你的查询(被调用者匹配)。缩进行为调用者。 |
| `[FIELD]` | 一个**被访问**的字段匹配你的查询。缩进行为访问者。 |
| `[CALLER→]` | 一个**调用方法**匹配你的查询。缩进行显示它正在调用的 API。 |
| `[STRING]` | 代码中的字符串常量匹配你的查询。缩进行为使用它的位置。 |
| `[STRING_TABLE]` | 字符串存在于 DEX 字符串表中,但在代码中没有 `const-string` 引用(可能位于注解中、被 R8 优化掉等)。 |
## 示例
### 1. 扫描 APK 统计信息```bash
dexfinder --dex-file app.apk --stats
Loaded 31 DEX file(s): 183913 classes, 1250566 method refs
Method references: 680610
Field references: 625572
String constants: 654353
Referenced types: 192586
Time: 3.9s
dexfinder --dex-file app.apk --query "requestLocationUpdates"
[METHOD] Landroid/location/LocationManager;->requestLocationUpdates(Ljava/lang/String;JFLandroid/location/LocationListener;)V (3 ref) Lcom/example/TestEntry;->init(Landroid/content/Context;)V (2 occurrences) Lcom/example/service/LocationService;->onStartCommand(Landroid/content/Intent;II)I
### 3. 跟踪调用链——树状视图```bash
dexfinder --dex-file app.apk \
--query "Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;" \
--trace --depth 5
android.telephony.TelephonyManager.getDeviceId()
└── com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java)
├── com.example.session.PhoneInfo.getImei(PhoneInfo.java)
├── com.example.logging.ClientIdHelper.initClientId(ClientIdHelper.java)
│ └── com.example.logging.ContextInfo.<init>(ContextInfo.java)
│ ├── com.example.logging.LogStrategyManager.getInstance(LogStrategyManager.java)
│ └── com.example.logging.LogContextImpl.<init>(LogContextImpl.java)
├── com.example.msp.DeviceInfo.k(DeviceInfo.java)
│ └── com.example.msp.DeviceInfo.<init>(DeviceInfo.java)
│ └── com.example.msp.DeviceInfo.getInstance(DeviceInfo.java)
│ ├── com.example.msp.TidHelper.getIMEI(TidHelper.java)
│ ├── com.example.msp.TidHelper.getIMSI(TidHelper.java)
│ └── com.example.msp.DeviceCollector.collectData(DeviceCollector.java)
└── com.example.weex.WXEnvironment.getDevId(WXEnvironment.java)
└── com.example.weex.WXEnvironment.<clinit>(WXEnvironment.java)
dexfinder --dex-file app.apk
--query "Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;"
--trace --depth 5 --layout list
--- Call chain #1 for android.telephony.TelephonyManager.getDeviceId() --- at com.example.session.PhoneInfo.getImei(PhoneInfo.java) at com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java) at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)