
Cross-platform APK/DEX method finder with call chain tracing, ProGuard deobfuscation, and hidden API detection
Website: junelegency.github.io/dexfinder
Cross-platform APK/DEX method & field reference finder with call chain tracing, ProGuard/R8 deobfuscation, and Android hidden API detection.
Inspired by Android's veridex tool, reimplemented in Go with enhanced capabilities: faster reflection detection, call chain tracing (veridex only shows one level), and flexible output formats.
--fail-on blocked exits non-zero when restricted APIs are found.dexfinder.yaml for project defaults, CLI flags overrideHomebrew (macOS / Linux):
brew install junelegency/tap/dexfinder
Script (auto-detects OS/arch):
curl -sSL https://raw.githubusercontent.com/JuneLeGency/dexfinder/main/install.sh | bash
Go install:
go install github.com/JuneLeGency/dexfinder/cmd/dexfinder@latest
Binary: download from Releases.
# 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
The --query flag accepts multiple input styles. dexfinder auto-detects and converts between them.
| Format | Example | Behavior |
|---|---|---|
| Simple name | getDeviceId | Fuzzy substring match across all APIs |
| Java class | android.telephony.TelephonyManager | All methods/fields of that class |
| Java class#method | android.telephony.TelephonyManager#getDeviceId | All overloads of that method |
| Java full signature | ...TelephonyManager#getDeviceId() | Exact + overload fallback |
| DEX/JNI signature | Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; | Exact match only |
# All equivalent — find requestLocationUpdates in LocationManager:
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"
Three independent axes, freely combinable:
--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| Value | Description |
|---|---|
text | Plain text output with colored tags (default) |
json | JSON — scan results or trace with tree/list layout |
model | Structured JSON with full MethodInfo/FieldInfo types (for IDE/CI) |
html | Self-contained HTML report with collapsible trees and search |
sarif | SARIF 2.1.0 static analysis format (GitHub / VS Code) |
--layout (used with --trace)| Value | Description |
|---|---|
tree | Merged tree — shared call paths collapsed into one tree (default) |
list | Flat list — each unique call chain shown as independent stack |
--style| Value | Example | Use case |
|---|---|---|
java | com.example.Foo.method(Foo.java) | Human-readable (default) |
dex | Foo.method(Ljava/lang/String;)V | Precise signature analysis |
--scope (search scope)Controls what kind of references the query matches against. This is critical for understanding results.
| Value | What it searches | Question it answers | Output tag |
|---|---|---|---|
all | Callee APIs + fields + code strings | "Who calls this API?" (default) | [METHOD] [FIELD] [STRING] |
callee | Only target API signatures in invoke-* / get/put instructions | "Who calls this specific method/field?" | [METHOD] [FIELD] |
caller | Only the calling method's signature | "What does this method call internally?" | [CALLER→] |
string | String constants in const-string instructions | "Where is this string used in code?" | [STRING] |
string-table | Code strings + full DEX string table | "Does this string exist anywhere in DEX?" (includes annotations, dead code) | [STRING] [STRING_TABLE] |
everything | All of the above combined | Full picture | all tags |
Understanding callee vs 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 (default) = callee + string. The caller direction is intentionally excluded from default because it answers a fundamentally different question. Use --scope=caller or --scope=everything explicitly when you need it.
Understanding output tags:
| Tag | Meaning |
|---|---|
[METHOD] | A method being called matches your query (callee match). Indented lines are the callers. |
[FIELD] | A field being accessed matches your query. Indented lines are the accessors. |
[CALLER→] | A calling method matches your query. The indented line shows what API it's calling. |
[STRING] | A string constant in code matches your query. Indented lines are where it's used. |
[STRING_TABLE] | String exists in DEX string table but has no const-string reference in code (may be in annotations, optimized out by R8, etc.) |
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