
Cercatore di metodi APK/DEX multipiattaforma con tracciamento della catena di chiamate, deoffuscamento ProGuard e rilevamento di API nascoste
# dexfinder
[Inglese](#english) | [Cinese](#中文) | [Sito web](https://junelegency.github.io/dexfinder/)

---
<a name="english"></a>
> **Sito web**: [junelegency.github.io/dexfinder](https://junelegency.github.io/dexfinder/)
Strumento multipiattaforma per la ricerca di riferimenti a metodi e campi in APK/DEX con tracciamento della catena di chiamate, deoffuscamento ProGuard/R8 e rilevamento di API nascoste Android.
Ispirato dallo strumento [veridex](https://android.googlesource.com/platform/art/+/refs/heads/master/tools/veridex/) di Android, reimplementato in Go con capacità migliorate: rilevamento più rapido della reflection, tracciamento della catena di chiamate (veridex mostra solo un livello) e formati di output flessibili.
## Caratteristiche
- **Scansione APK/DEX/JAR** — Analizza il bytecode DEX, estrae tutti i riferimenti a metodi/campi/stringhe
- **Ricerca multi-formato** — Cerca per nome Java, firma DEX/JNI o parola chiave semplice
- **Tracciamento della catena di chiamate** — Traccia i chiamanti fino a N livelli di profondità, albero unito o elenco piatto, con rilevamento di cicli
- **Deoffuscamento ProGuard/R8** — Carica mapping.txt, mostra i nomi originali accanto a quelli offuscati
- **Rilevamento API nascoste** — Carica hiddenapi-flags.csv, rileva API bloccate/non supportate
- **Rilevamento reflection** — Incrocia classi × stringhe per trovare l'uso di API nascoste basate su reflection
- **Output flessibile** — text / json / model / html / sarif, layout ad albero/elenco, stile nome java/dex — tutto ortogonale
- **Output terminale a colori** — Colori ANSI rilevati automaticamente per tag, connettori ad albero e livelli API
- **Diff APK** — Confronta due versioni APK/DEX, rileva riferimenti API aggiunti/rimossi/modificati
- **Report HTML** — HTML interattivo autonomo con alberi comprimibili, ricerca e tema scuro
- **Output SARIF** — SARIF 2.1.0 per GitHub Code Scanning, VS Code e pipeline CI
- **Integrazione CI** — `--fail-on blocked` termina con codice diverso da zero quando vengono trovate API ristrette
- **File di configurazione** — `.dexfinder.yaml` per impostazioni predefinite del progetto, i flag CLI sovrascrivono
- **Zero dipendenze esterne** — Puro Go, parser DEX autonomo
- **Multipiattaforma** — macOS (Intel / Apple Silicon), Linux (amd64 / arm64), Windows
## Installazione
**Homebrew** (macOS / Linux):```bash
brew install junelegency/tap/dexfinder
```
**Script** (rileva automaticamente 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
```
**Binario**: scarica da [Releases](https://github.com/JuneLeGency/dexfinder/releases).
## Avvio rapido```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
```
## Formati di Query
Il flag `--query` accetta più stili di input. dexfinder rileva e converte automaticamente tra di essi.
| Formato | Esempio | Comportamento |
|---|---|---|
| Simple name | `getDeviceId` | Corrispondenza fuzzy per sottostringa in tutte le API |
| Java class | `android.telephony.TelephonyManager` | Tutti i metodi/campi di quella classe |
| Java class#method | `android.telephony.TelephonyManager#getDeviceId` | Tutti gli overload di quel metodo |
| Java full signature | `...TelephonyManager#getDeviceId()` | Esatta + fallback agli overload |
| DEX/JNI signature | `Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;` | Solo corrispondenza esatta |```bash
# 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"
```
## Controllo Output
Tre assi indipendenti, liberamente combinabili:```
--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`
| Valore | Descrizione |
|---|---|
| `text` | Output di testo semplice con tag colorati (predefinito) |
| `json` | JSON — risultati di scansione o trace con layout ad albero/elenco |
| `model` | JSON strutturato con tipi MethodInfo/FieldInfo completi (per IDE/CI) |
| `html` | Report HTML autonomo con alberi collassabili e ricerca |
| `sarif` | Formato di analisi statica SARIF 2.1.0 (GitHub / VS Code) |
### `--layout` (usato con `--trace`)
| Valore | Descrizione |
|---|---|
| `tree` | Albero unito — percorsi di chiamata condivisi collassati in un unico albero (predefinito) |
| `list` | Elenco piatto — ogni catena di chiamata unica mostrata come stack indipendente |
### `--style`
| Valore | Esempio | Caso d'uso |
|---|---|---|
| `java` | `com.example.Foo.method(Foo.java)` | Leggibile dall'uomo (predefinito) |
| `dex` | `Foo.method(Ljava/lang/String;)V` | Analisi precisa della firma |
### `--scope` (ambito di ricerca)
Controlla **che tipo di riferimenti** la query corrisponde. Questo è fondamentale per comprendere i risultati.
| Valore | Cosa cerca | Domanda a cui risponde | Tag di output |
|---|---|---|---|
| `all` | API callee + campi + stringhe di codice | "Chi chiama questa API?" (predefinito) | `[METHOD]` `[FIELD]` `[STRING]` |
| `callee` | Solo firme API target nelle istruzioni `invoke-*` / `get/put` | "Chi chiama questo specifico metodo/campo?" | `[METHOD]` `[FIELD]` |
| `caller` | Solo la firma del metodo chiamante | "Cosa chiama internamente questo metodo?" | `[CALLER→]` |
| `string` | Costanti stringa nelle istruzioni `const-string` | "Dove viene usata questa stringa nel codice?" | `[STRING]` |
| `string-table` | Stringhe di codice + intera tabella stringhe DEX | "Questa stringa esiste da qualche parte in DEX?" (include annotazioni, codice morto) | `[STRING]` `[STRING_TABLE]` |
| `everything` | Tutto quanto sopra combinato | Quadro completo | tutti i tag |
**Comprendere 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`. La direzione `caller` è intenzionalmente esclusa dal default perché risponde a una domanda fondamentalmente diversa. Usa `--scope=caller` o `--scope=everything` esplicitamente quando ne hai bisogno.
**Comprendere i tag di output:**
| Tag | Significato |
|---|---|
| `[METHOD]` | Un metodo **chiamato** corrisponde alla tua query (corrispondenza callee). Le righe indentate sono i chiamanti. |
| `[FIELD]` | Un campo **accesso** corrisponde alla tua query. Le righe indentate sono gli accessori. |
| `[CALLER→]` | Un metodo **chiamante** corrisponde alla tua query. La riga indentata mostra quale API sta chiamando. |
| `[STRING]` | Una costante stringa nel codice corrisponde alla tua query. Le righe indentate mostrano dove viene usata. |
| `[STRING_TABLE]` | La stringa esiste nella tabella delle stringhe DEX ma non ha un riferimento `const-string` nel codice (potrebbe essere in annotazioni, ottimizzata da R8, ecc.) |
## Esempi
### 1. Scansione delle statistiche 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
```
### 2. Trova tutte le chiamate di tracciamento della posizione```bash
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. Traccia le catene di chiamata — vista ad albero```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)
```
### 4. Tracciare le catene di chiamate — vista elenco (stile crash Java)```bash
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)
--- Call chain #2 for android.telephony.TelephonyManager.getDeviceId() ---
at com.example.logging.LogStrategyManager.getInstance(LogStrategyManager.java)
at com.example.logging.ContextInfo.<init>(ContextInfo.java)
at com.example.logging.ClientIdHelper.initClientId(ClientIdHelper.java)
at com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java)
at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)
```
### 5. Traccia con stile di firma DEX```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --depth 3 --style dex
```
```
Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;
└── TelephonyManager.getDeviceId(Landroid/telephony/TelephonyManager;)Ljava/lang/String;
├── PhoneInfo.getImei(Landroid/content/Context;)Ljava/lang/String;
├── ClientIdHelper.initClientId(Landroid/content/Context;)Ljava/lang/String;
└── DeviceInfo.k(Landroid/content/Context;)V
```
### 6. Uscita JSON — albero```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --depth 2 --format json
```
```json
{
"targets": [{
"api": "android.telephony.TelephonyManager.getDeviceId()",
"tree": {
"method": "android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)",
"callers": [
{ "method": "com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java)",
"callers": [
{ "method": "com.example.session.PhoneInfo.getImei(PhoneInfo.java)" },
{ "method": "com.example.logging.ClientIdHelper.initClientId(ClientIdHelper.java)" }
]}
]
}
}]
}
```
### 7. Output JSON — lista```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --depth 2 --format json --layout list
```
```json
{
"targets": [{
"api": "android.telephony.TelephonyManager.getDeviceId()",
"chains": [
["com.example.session.PhoneInfo.getImei(PhoneInfo.java)",
"com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java)",
"android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)"],
["com.example.logging.ClientIdHelper.initClientId(ClientIdHelper.java)",
"com.example.aopsdk.TelephonyManager.getDeviceId(TelephonyManager.java)",
"android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)"]
]
}]
}
```
### 8. Output strutturato del modello (per CI/IDE)```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format model | jq '.call_chains[0]'
```
```json
{
"target": "Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;",
"chain": [
{ "method": { "dex_signature": "...", "class": "...", "name": "getImei",
"param_types": ["Landroid/content/Context;"], "return_type": "Ljava/lang/String;",
"java_readable": "com.example.session.PhoneInfo.getImei(...)" }},
{ "method": { "dex_signature": "...", "java_readable": "...TelephonyManager.getDeviceId(...)" }},
{ "method": { "dex_signature": "...", "java_readable": "...TelephonyManager.getDeviceId(...)" }}
],
"depth": 2
}
```
### 9. Mappatura ProGuard/R8 — query e visualizzazione
Con `--mapping`, sia **input** che **output** supportano nomi originali (non offuscati).
**Query per nome originale → si converte automaticamente in nome offuscato per la ricerca DEX:**```bash
# Query with original simple class name (mapping converts "KotlinCases" → "LJ7;" internally)
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt
# Query with original Java full name
dexfinder --dex-file app.apk --query "com.example.app.utils.Helper" --mapping mapping.txt
# Query with obfuscated name still works
dexfinder --dex-file app.apk --query "LJ7;" --mapping mapping.txt
```
**Output dei nomi deoffuscati nel trace:**```bash
# Tree trace with deobfuscated names
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --depth 3
```
```
com.example.kotlin.KotlinCases$$ExternalSyntheticLambda1.<init>(int)
└── com.example.TestEntry.runAllTests(TestEntry.java)
└── com.example.MainActivity.onCreate(MainActivity.java)
```
**Mostra sia i nomi offuscati che quelli originali:**```bash
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --show-obf --trace
```
```
com.example.kotlin.KotlinCases.fetchLocationAsync(KotlinCases.java)
└── com.example.kotlin.KotlinCases$testCoroutines$3.invokeSuspend(KotlinCases.java) [obf: G7.e]
└── com.example.kotlin.KotlinCases$testCoroutines$3.create(KotlinCases.java) [obf: G7.b]
```
**Tutte le combinazioni con altre flag:**```bash
# Original name + trace as flat list
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --layout list
# Original name + DEX signature style
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --style dex
# Original name + JSON tree + show-obf
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --show-obf --trace --format json
# Original name + reverse direction (what does this class call?)
dexfinder --dex-file app.apk --query "com.example.kotlin.KotlinCases" --mapping mapping.txt --scope caller
```
**Matrice Input × Output:**
| Input query | Nessun mapping | `--mapping` | `--mapping --show-obf` |
|---|---|---|---|
| Offuscato: `LJ7;` | ✓ output offuscato | ✓ output deoffuscato | ✓ entrambi i nomi |
| Originale semplice: `KotlinCases` | ✗ non trovato | ✓ auto-converte, output deoffuscato | ✓ auto-converte, entrambi i nomi |
| Originale completo: `com.example...KotlinCases` | ✗ non trovato | ✓ auto-converte, output deoffuscato | ✓ auto-converte, entrambi i nomi |
### 10. Rilevamento API nascoste```bash
# Download CSV (one-time)
curl -o hiddenapi-flags.csv \
https://dl.google.com/developers/android/baklava/non-sdk/hiddenapi-flags.csv
# Full scan — linking + reflection detection
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv
```
```
#1: Linking unsupported Lsun/misc/Unsafe;->allocateInstance(Ljava/lang/Class;)Ljava/lang/Object; use(s):
Lcom/google/gson/internal/UnsafeAllocator;->create()Lcom/google/gson/internal/UnsafeAllocator;
#2: Reflection blocked Landroid/location/ILocationManager;->getCurrentLocation potential use(s):
Lcom/example/monitor/LocationMonitor;->hookSystemLocationManager(Landroid/content/Context;)V
```
### 11. Costanti di stringhe di ricerca (content:// URI, chiavi API, ecc.)```bash
# Find content:// URIs in code
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope string
# Include strings only in DEX table (optimized out by R8, annotations, etc.)
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope everything
```
```
[STRING] "content://com.android.contacts/" (1 ref)
Lcom/example/imageloader/BaseImageDownloader;->getStreamFromContent(Ljava/lang/String;)Ljava/io/InputStream;
[STRING_TABLE] "content://com.android.contacts" (in DEX string table, no code reference found)
```
### 12. Filtra per prefisso di classe```bash
# Only scan classes in your own package
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/"
# Scan multiple packages
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/,Lcom/mylib/"
```
### 13. Combina tutto```bash
# Deobfuscated JSON tree of location API usage, filtered to your code
dexfinder --dex-file app.apk \
--query "android.location.LocationManager#requestLocationUpdates" \
--trace --depth 8 \
--format json --layout tree --style java \
--mapping mapping.txt --show-obf \
--class-filter "Lcom/mycompany/"
```
### 14. Report HTML```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format html --output report.html
```
Apre in qualsiasi browser — alberi di chiamata comprimibili, barra di ricerca, tema scuro.
### 15. SARIF per GitHub Code Scanning```bash
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --format sarif > results.sarif
# Upload to GitHub:
# gh api repos/OWNER/REPO/code-scanning/sarifs -f "[email protected]"
```
### 16. APK diff```bash
# Compare two APK versions
dexfinder --dex-file new.apk --diff old.apk --query "getDeviceId"
```
```
+ 1 added method(s)
+ Lcom/new/Feature;->trackDevice()V
- 1 removed method(s)
- Lcom/old/Legacy;->getIMEI()V
Summary: +1 added, -1 removed, ~0 changed
```
### 17. Gate CI con --fail-on```bash
# Fail CI if any blocked hidden APIs are used
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --fail-on blocked
# Exit code: 0 = clean, 2 = violations found
```
## Prestazioni
Testato su Apple M-series, singolo thread:
| Dimensione APK | File DEX | Classi | Riferimenti Metodi | Scansione | API nascoste |
|---|---|---|---|---|---|
| ~1 MB | 1 | ~2K | ~18K | **24ms** | — |
| ~10 MB | 2 | ~25K | ~100K | **335ms** | — |
| ~300 MB | 30+ | ~180K | ~1.2M | **3.9s** | **5.4s** |
Rispetto a veridex (C++, modalità imprecisa) sullo stesso APK da ~300MB:
- veridex preciso: **27s** (nessun reflection tramite Binder/AIDL)
- veridex impreciso: **>32 min** (terminato, esplosione del prodotto cartesiano)
- **dexfinder: 5.4s** (ottimizzazione con indice inverso)
## Tutte le Opzioni
| Flag | Descrizione | Predefinito |
|---|---|---|
| `--dex-file` | File APK/DEX/JAR da analizzare **(obbligatorio)** | — |
| `--query` | Parola chiave di ricerca (Java, DEX/JNI o nome semplice) | — |
| `--trace` | Abilita tracciamento della catena di chiamate (richiede `--query`) | `false` |
| `--depth` | Profondità massima della catena di chiamate | `5` |
| `--layout` | Layout di tracciamento: `tree` o `list` | `tree` |
| `--style` | Stile del nome: `java` o `dex` | `java` |
| `--format` | Formato di output: `text`, `json`, `model`, `html`, `sarif` | `text` |
| `--output` | Scrivi output su file invece di stdout | — |
| `--color` | Modalità colore: `auto`, `always`, `never` | `auto` |
| `--mapping` | Percorso di ProGuard/R8 mapping.txt | — |
| `--show-obf` | Mostra nomi offuscati insieme a quelli deoffuscati | `false` |
| `--api-flags` | Percorso di hiddenapi-flags.csv | — |
| `--class-filter` | Prefissi dei descrittori di classe separati da virgola | — |
| `--exclude-api-lists` | Elenchi API da escludere dal report | — |
| `--scope` | Ambito di ricerca: `all`, `callee`, `caller`, `string`, `string-table`, `everything` | `all` |
| `--diff` | Confronta con un altro APK/DEX e mostra le differenze API | — |
| `--fail-on` | Esci con codice non zero se vengono trovate API nascoste a questo livello (gate CI) | — |
| `--stats` | Mostra solo statistiche riassuntive | `false` |
| `--version` | Mostra versione | `false` |
### File di Configurazione
Crea `.dexfinder.yaml` nella directory root del tuo progetto per impostare i valori predefiniti:```yaml
mapping: ./build/outputs/mapping.txt
class-filter: "Lcom/mycompany/"
api-flags: ./hiddenapi-flags.csv
style: java
depth: 8
color: auto
```
I flag CLI sovrascrivono sempre i valori del file di configurazione.
## Compilazione dal sorgente```bash
git clone https://github.com/JuneLeGency/dexfinder.git
cd dexfinder
go build -o dexfinder ./cmd/dexfinder/
go test ./...
```
## Licenza
Apache License 2.0
---
<a name="中文"></a>
# dexfinder
> **官网**: [junelegency.github.io/dexfinder](https://junelegency.github.io/dexfinder/?lang=zh)
跨平台 APK/DEX 方法与字段引用查找器,支持调用链追踪、ProGuard/R8 反混淆、Android Hidden API 检测。
基于 Android [veridex](https://android.googlesource.com/platform/art/+/refs/heads/master/tools/veridex/) 原理,用 Go 重新实现并增强:更快的反射检测、多层调用链追踪(veridex 仅一层)、灵活的输出格式。
## 特性
- **APK/DEX/JAR 扫描** — 解析 DEX 字节码,提取所有方法/字段/字符串引用
- **多格式查询** — 支持 Java 类名、DEX/JNI 签名、简单关键字
- **调用链追踪** — 向上追溯 N 层调用者,合并树或展开列表,自动检测递归环
- **ProGuard/R8 反混淆** — 加载 mapping.txt,显示原始名称
- **Hidden API 检测** — 加载 hiddenapi-flags.csv,检测 blocked/unsupported API
- **反射检测** — 类名×字符串交叉匹配,发现反射调用的隐藏 API(兼容 veridex)
- **灵活输出** — text / json / model / html / sarif 格式,tree / list 布局,java / dex 命名风格——正交组合
- **彩色终端输出** — 自动检测 TTY,标签、树形连接线、API 级别着色
- **APK 差异对比** — 对比两个 APK 版本,检测新增/移除/变化的 API 引用
- **HTML 报告** — 自包含交互式 HTML,可折叠树、搜索过滤、暗色主题
- **SARIF 输出** — SARIF 2.1.0 格式,支持 GitHub Code Scanning、VS Code
- **CI 集成** — `--fail-on blocked` 检测到受限 API 时返回非零退出码
- **配置文件** — `.dexfinder.yaml` 项目默认配置,命令行参数覆盖
- **零外部依赖** — 纯 Go 实现,自包含 DEX 解析器
- **跨平台** — macOS (Intel / Apple Silicon)、Linux (amd64 / arm64)、Windows
## 安装
**Homebrew** (macOS / Linux):```bash
brew install junelegency/tap/dexfinder
```
**Installazione script** (sistema di rilevamento automatico):```bash
curl -sSL https://raw.githubusercontent.com/JuneLeGency/dexfinder/main/install.sh | bash
```
**Go 安装**:```bash
go install github.com/JuneLeGency/dexfinder/cmd/dexfinder@latest
```
**Download binario**: [Releases](https://github.com/JuneLeGency/dexfinder/releases)
## Avvio rapido```bash
# 查看 APK 概况
dexfinder --dex-file app.apk --stats
# 查找所有 getDeviceId 调用(获取 IMEI)
dexfinder --dex-file app.apk --query "getDeviceId"
# 追踪调用链(合并树形视图)
dexfinder --dex-file app.apk --query "getDeviceId" --trace
# 追踪调用链(展开为独立调用栈)
dexfinder --dex-file app.apk --query "getDeviceId" --trace --layout list
# 用精确 JNI 签名查询
dexfinder --dex-file app.apk \
--query "Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;" \
--trace --depth 8
```
## Formato di query (`--query`)
| Formato | Esempio | Comportamento |
|---|---|---|
| Nome semplice | `getDeviceId` | Corrispondenza per sottostringa fuzzy |
| Nome classe Java | `android.telephony.TelephonyManager` | Corrisponde a tutti i metodi della classe |
| Nome classe#metodo Java | `...TelephonyManager#getDeviceId` | Corrisponde a tutti gli overload di quel metodo |
| Firma completa Java | `...#getDeviceId()` | Corrispondenza esatta + fallback agli overload |
| Firma DEX/JNI | `Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;` | Corrispondenza esatta |
## Controllo output
Tre dimensioni indipendenti, liberamente combinabili:```
--format (text / json / model / html / sarif) 输出什么
--layout (tree / list) 怎么排列调用链
--style (java / dex) 怎么显示名称
--color (auto / always / never) 终端着色
```
### `--layout` confronto (con `--trace`)
**tree** — unisce percorsi comuni, un albero mostra la panoramica:```
android.telephony.TelephonyManager.getDeviceId()
└── ...aopsdk...TelephonyManager.getDeviceId(TelephonyManager.java)
├── PhoneInfo.getImei(PhoneInfo.java)
├── ClientIdHelper.initClientId(ClientIdHelper.java)
│ └── ContextInfo.<init>(ContextInfo.java)
└── DeviceInfo.k(DeviceInfo.java)
└── DeviceInfo.getInstance(DeviceInfo.java)
├── TidHelper.getIMEI(TidHelper.java)
└── DeviceCollector.collectData(DeviceCollector.java)
```
**list** — ogni catena mostrata indipendentemente (stile Java crash):```
--- Call chain #1 ---
at PhoneInfo.getImei(PhoneInfo.java)
at ...aopsdk...TelephonyManager.getDeviceId(TelephonyManager.java)
at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)
--- Call chain #2 ---
at ContextInfo.<init>(ContextInfo.java)
at ClientIdHelper.initClientId(ClientIdHelper.java)
at ...aopsdk...TelephonyManager.getDeviceId(TelephonyManager.java)
at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)
```
### `--style` confronto
**java** (predefinito): `com.example.Foo.method(Foo.java)`
**dex**: `Foo.method(Ljava/lang/String;)V`
### Output JSON```bash
# JSON 树
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format json
# JSON 列表
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format json --layout list
```
### `--scope` Ambito di ricerca
Controlla quale **tipo di riferimento** viene abbinato alla query. Comprendere questo parametro è cruciale per interpretare correttamente i risultati.
| Valore | Contenuto della ricerca | Domanda a cui risponde | Etichetta di output |
|---|---|---|---|
| `all` | API chiamate + campi + stringhe di codice | "Chi ha chiamato questo metodo?" (predefinito) | `[METHOD]` `[FIELD]` `[STRING]` |
| `callee` | Solo la firma di destinazione nelle istruzioni `invoke-*` / `get/put` | "Chi ha chiamato questo metodo/campo specifico?" | `[METHOD]` `[FIELD]` |
| `caller` | Solo la firma del metodo chiamante | "Cosa ha chiamato questo metodo al suo interno?" | `[CALLER→]` |
| `string` | Costanti stringa nelle istruzioni `const-string` | "Dove viene usata questa stringa nel codice?" | `[STRING]` |
| `string-table` | Stringhe di codice + intera tabella delle stringhe DEX | "Questa stringa esiste nel DEX?" (include annotazioni, dead code) | `[STRING]` `[STRING_TABLE]` |
| `everything` | Tutto quanto sopra | Vista completa | Tutte le etichette |
**Differenza tra callee e caller:**```
scope=callee: "谁调了 finish()?"
onCreate ──调用──→ finish() ← 显示这些调用者
onResume ──调用──→ finish()
scope=caller: "finish() 内部调了什么?"
finish() ──调用──→ Log.i() ← 显示这些被调用者
finish() ──调用──→ super.finish()
```
`--scope=all` (predefinito) = `callee` + `string`. La direzione `caller` è volutamente esclusa dal default perché risponde a una domanda completamente diversa. Se necessario, abilitala esplicitamente con `--scope=caller` o `--scope=everything`.
**Significato delle etichette di output:**
| Etichetta | Significato |
|---|---|
| `[METHOD]` | Il metodo che hai cercato **viene chiamato da qualcun altro**. La riga indentata è il chiamante. |
| `[FIELD]` | Il campo che hai cercato **viene acceduto da qualcun altro**. La riga indentata è l'accessore. |
| `[CALLER→]` | Il nome del metodo che hai cercato appare in un **chiamante**; la riga indentata mostra quale API ha chiamato. |
| `[STRING]` | Corrispondenza con una costante stringa nel codice. La riga indentata è il metodo che utilizza quella stringa. |
| `[STRING_TABLE]` | La stringa esiste solo nella tabella delle stringhe DEX, non ci sono riferimenti `const-string` nel codice (potrebbe essere in annotazioni, ottimizzato da R8, ecc.). |
## Ulteriori usi
### Deoffuscamento (--mapping)
Caricando `--mapping`, sia **l'input che l'output** supportano i nomi originali (non offuscati).
**Query con nome originale → conversione automatica in nome offuscato per la ricerca nel DEX:**```bash
# 用原始简短类名查(mapping 内部将 "KotlinCases" 转为 "LJ7;")
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt
# 用原始 Java 全名查
dexfinder --dex-file app.apk --query "com.example.app.utils.Helper" --mapping mapping.txt
# 用混淆名查也正常工作
dexfinder --dex-file app.apk --query "LJ7;" --mapping mapping.txt
```
**Nome deofuscato in output:**```bash
# trace 树形 + 反混淆
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace
```
**Mostra sia il nome offuscato che il nome originale:**```bash
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --show-obf --trace
```
```
com.example.KotlinCases.fetchLocationAsync(KotlinCases.java)
└── com.example.KotlinCases$testCoroutines$3.invokeSuspend(KotlinCases.java) [obf: G7.e]
```
**Combinazione libera con altri parametri:**```bash
# 原始名 + 展开列表
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --layout list
# 原始名 + DEX 签名风格
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --style dex
# 原始名 + JSON 树 + 显示混淆名
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --show-obf --trace --format json
# 原始名 + 反向查看(这个类内部调了什么)
dexfinder --dex-file app.apk --query "com.example.KotlinCases" --mapping mapping.txt --scope caller
```
**Matrice Input×Output:**
| Input di query | Nessun mapping | `--mapping` | `--mapping --show-obf` |
|---|---|---|---|
| Nome offuscato `LJ7;` | ✓ Output offuscato | ✓ Output deoffuscato | ✓ Entrambi affiancati |
| Nome breve originale `KotlinCases` | ✗ Non trovato | ✓ Conversione automatica + output deoffuscato | ✓ Conversione automatica + entrambi affiancati |
| Nome completo originale `com.example...` | ✗ Non trovato | ✓ Conversione automatica + output deoffuscato | ✓ Conversione automatica + entrambi affiancati |
### Rilevamento Hidden API```bash
# 下载 CSV(一次性)
curl -o hiddenapi-flags.csv \
https://dl.google.com/developers/android/baklava/non-sdk/hiddenapi-flags.csv
# 全量检测(直接链接 + 反射检测)
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv
```
### Ricerca di stringhe```bash
# 搜索代码中的 content:// URI
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope string
# 包含被 R8 优化掉的字符串(注解、死代码等)
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope everything
```
### Filtra per nome del pacchetto```bash
# 只扫描自己的代码
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/"
```
### Uso combinato```bash
# 反混淆 + JSON 树形输出 + 定位 API 调用 + 过滤自己的代码
dexfinder --dex-file app.apk \
--query "android.location.LocationManager#requestLocationUpdates" \
--trace --depth 8 \
--format json --layout tree --style java \
--mapping mapping.txt --show-obf \
--class-filter "Lcom/mycompany/"
```
### Report HTML```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format html --output report.html
```
Browser aperto e pronto all'uso – albero delle chiamate pieghevole, barra di ricerca, tema scuro.
### SARIF(GitHub Code Scanning)```bash
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --format sarif > results.sarif
```
### Confronto delle versioni APK```bash
dexfinder --dex-file new.apk --diff old.apk --query "getDeviceId"
```
```
+ 1 added method(s)
+ Lcom/new/Feature;->trackDevice()V
- 1 removed method(s)
- Lcom/old/Legacy;->getIMEI()V
Summary: +1 added, -1 removed, ~0 changed
```
### CI punto di blocco```bash
# 检测到 blocked API 时 CI 失败
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --fail-on blocked
# 退出码: 0 = 通过, 2 = 有违规
```
## Prestazioni
Chip serie Apple M, a singolo thread:
| Dimensione APK | Numero DEX | Classi | Riferimenti metodi | Scansione | API nascoste |
|---|---|---|---|---|---|
| ~1 MB | 1 | ~2K | ~18K | **24ms** | — |
| ~10 MB | 2 | ~25K | ~100K | **335ms** | — |
| ~300 MB | 30+ | ~180K | ~1.2M | **3.9s** | **5.4s** |
Confronto con veridex (C++) sullo stesso APK da ~300MB:
- veridex preciso: **27s** (non riesce a tracciare riflessione Binder/AIDL)
- veridex impreciso: **>32 minuti** (esplosione del prodotto cartesiano, killato)
- **dexfinder: 5.4s** (ottimizzazione con indice inverso)
## Tutti i parametri
| Parametro | Descrizione | Valore predefinito |
|---|---|---|
| `--dex-file` | Percorso file APK/DEX/JAR **** (obbligatorio) | — |
| `--query` | Parola chiave di ricerca (Java / DEX/JNI / nome semplice) | — |
| `--trace` | Abilita tracciamento della catena di chiamate (richiede `--query`) | `false` |
| `--depth` | Profondità massima della catena di chiamate | `5` |
| `--layout` | Layout del tracciamento: `tree` (albero mergiato) o `list` (elenco espanso) | `tree` |
| `--style` | Stile di denominazione: `java` (leggibile) o `dex` (firma JNI) | `java` |
| `--format` | Formato di output: `text`, `json`, `model`, `html`, `sarif` | `text` |
| `--output` | Output su file anziché stdout | — |
| `--color` | Modalità colore: `auto`, `always`, `never` | `auto` |
| `--mapping` | Percorso di ProGuard/R8 mapping.txt | — |
| `--show-obf` | Mostra sia nomi offuscati che deoffuscati | `false` |
| `--api-flags` | Percorso di hiddenapi-flags.csv | — |
| `--class-filter` | Filtro prefisso descrittore di classe (separato da virgola) | — |
| `--exclude-api-lists` | Livelli API da escludere | — |
| `--scope` | Ambito di ricerca: `all`, `callee`, `caller`, `string`, `string-table`, `everything` | `all` |
| `--diff` | Confronta un altro APK/DEX, mostra le differenze API | — |
| `--fail-on` | Restituisce codice di uscita non zero quando rileva API di livello specificato (punto di blocco CI) | — |
| `--stats` | Mostra solo il riepilogo statistico | `false` |
| `--version` | Mostra il numero di versione | `false` |
### File di configurazione
Crea un file `.dexfinder.yaml` nella directory radice del progetto per impostare i valori predefiniti:```yaml
mapping: ./build/outputs/mapping.txt
class-filter: "Lcom/mycompany/"
api-flags: ./hiddenapi-flags.csv
style: java
depth: 8
color: auto
```
Gli argomenti della riga di comando sovrascrivono sempre il file di configurazione.
## Compilazione dal codice sorgente```bash
git clone https://github.com/JuneLeGency/dexfinder.git
cd dexfinder
go build -o dexfinder ./cmd/dexfinder/
go test ./...
```
## Licenza