
Кроссплатформенный поисковик методов APK/DEX с трассировкой цепочек вызовов, деобфускацией ProGuard и обнаружением скрытых API
Английский | Китайский | Сайт

Кроссплатформенное средство поиска ссылок на методы и поля APK/DEX с трассировкой цепочек вызовов, деобфускацией ProGuard/R8 и обнаружением скрытых API Android.
Вдохновлено инструментом veridex от Android, перереализовано на Go с расширенными возможностями: более быстрое обнаружение рефлексии, трассировка цепочек вызовов (veridex показывает только один уровень) и гибкие форматы вывода.
--fail-on blocked завершается с ненулевым кодом при обнаружении ограниченных API.dexfinder.yaml для настроек проекта, флаги CLI переопределяютHomebrew (macOS / Linux):```bash brew install junelegency/tap/dexfinder
**Скрипт** (автоопределение ОС/архитектуры):```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
**Бинарный файл**: скачать из [Релизы](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 автоматически определяет и преобразует между ними.
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--layout (используется с --trace)| Значение | Описание |
|---|---|
tree | Объединенное дерево — общие пути вызовов свернуты в одно дерево (по умолчанию) |
list | Плоский список — каждая уникальная цепочка вызовов показана как независимый стек |
--style| Значение | Пример | Случай использования |
|---|---|---|
java | com.example.Foo.method(Foo.java) | Человекочитаемый (по умолчанию) |
dex | Foo.method(Ljava/lang/String;)V | Точный анализ сигнатур |
--scope (область поиска)Управляет типом ссылок, по которым выполняется поиск. Это критически важно для понимания результатов.
Понимание 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` (по умолчанию) = `callee` + `string`. Направление `caller` намеренно исключено из значения по умолчанию, так как оно отвечает на принципиально другой вопрос. Используйте `--scope=caller` или `--scope=everything` явно, когда это необходимо.
**Понимание выходных тегов:**
| Тег | Значение |
|---|---|
| `[METHOD]` | **Вызываемый** метод соответствует вашему запросу (совпадение callee). Строки с отступом — это вызывающие методы. |
| `[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)
--- Call chain #2 for android.telephony.TelephonyManager.getDeviceId() --- at com.example.logging.LogStrategyManager.getInstance(LogStrategyManager.java) at com.example.logging.ContextInfo.(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. Трассировка с использованием стиля подписи 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
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)" }
]}
]
}
}]
}
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)"]
]
}]
}
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
}
С помощью --mapping как входные, так и выходные данные поддерживают оригинальные (необфусцированные) имена.
Запрос по оригинальному имени → автоматически преобразуется в обфусцированное имя для поиска в DEX:```bash
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt
dexfinder --dex-file app.apk --query "com.example.app.utils.Helper" --mapping mapping.txt
dexfinder --dex-file app.apk --query "LJ7;" --mapping mapping.txt
**Выводить деобфусцированные имена в трассировке:**```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)
Показать как обфусцированные, так и исходные имена:```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]
**Все комбинации с другими флагами:**```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
Матрица «Вход × Результат»:
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
#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. Поиск строковых констант (content:// URIs, ключи API, и т.д.)```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)
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/"
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/,Lcom/mylib/"
### 13. Объединить всё```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/"
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format html --output report.html
Открывается в любом браузере — сворачиваемые деревья вызовов, строка поиска, темная тема.
### 15. SARIF for 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]"
dexfinder --dex-file new.apk --diff old.apk --query "getDeviceId"
Summary: +1 added, -1 removed, ~0 changed
### 17. CI-шлюз с --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
Тестировалось на Apple M-series, один поток:
По сравнению с veridex (C++, неточный режим) на том же APK ~300MB:
Создайте .dexfinder.yaml в корне вашего проекта, чтобы установить значения по умолчанию:```yaml
mapping: ./build/outputs/mapping.txt
class-filter: "Lcom/mycompany/"
api-flags: ./hiddenapi-flags.csv
style: java
depth: 8
color: auto
Флаги CLI всегда переопределяют значения конфигурационного файла.
## Сборка из исходного кода```bash
git clone https://github.com/JuneLeGency/dexfinder.git
cd dexfinder
go build -o dexfinder ./cmd/dexfinder/
go test ./...
Apache License 2.0
Официальный сайт: junelegency.github.io/dexfinder
Кросс-платформенный инструмент для поиска ссылок на методы и поля в APK/DEX, поддерживает отслеживание цепочек вызовов, деобфускацию ProGuard/R8, обнаружение скрытых API Android.
Основан на принципах Android veridex, переписан и улучшен на Go: более быстрое обнаружение рефлексии, многоуровневое отслеживание цепочек вызовов (veridex — только один уровень), гибкие форматы вывода.
--fail-on blocked возвращает ненулевой код выхода при обнаружении ограниченных API.dexfinder.yaml для проекта, параметры командной строки переопределяютHomebrew (macOS / Linux):```bash brew install junelegency/tap/dexfinder
**Установка скрипта** (система автоматического обнаружения):```bash
curl -sSL https://raw.githubusercontent.com/JuneLeGency/dexfinder/main/install.sh | bash
Go Установка:```bash go install github.com/JuneLeGency/dexfinder/cmd/dexfinder@latest
**Загрузка бинарных файлов**: [Releases](https://github.com/JuneLeGency/dexfinder/releases)
## Быстрый старт```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
--query)Три независимых аспекта, свободно комбинируемых:``` --format (text / json / model / html / sarif) 输出什么 --layout (tree / list) 怎么排列调用链 --style (java / dex) 怎么显示名称 --color (auto / always / never) 终端着色
### `--layout` сравнение (с `--trace`)
**tree** — объединяет общие пути, одно дерево показывает всю картину:```
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 — каждая цепочка отображается независимо(стиль 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.(ContextInfo.java) at ClientIdHelper.initClientId(ClientIdHelper.java) at ...aopsdk...TelephonyManager.getDeviceId(TelephonyManager.java) at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java)
### `--style` сравнение
**java** (по умолчанию): `com.example.Foo.method(Foo.java)`
**dex**: `Foo.method(Ljava/lang/String;)V`
### 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 搜索范围控制查询匹配哪种引用类型。理解这个参数对正确解读结果至关重要。
callee vs caller 的区别:``` scope=callee: "谁调了 finish()?" onCreate ──调用──→ finish() ← 显示这些调用者 onResume ──调用──→ finish()
scope=caller: "finish() 内部调了什么?" finish() ──调用──→ Log.i() ← 显示这些被调用者 finish() ──调用──→ super.finish()
`--scope=all` (по умолчанию) = `callee` + `string`. Направление `caller` намеренно исключено из умолчания, так как отвечает на совершенно другой вопрос. При необходимости включайте явно с помощью `--scope=caller` или `--scope=everything`.
**Значения меток вывода:**
| Метка | Значение |
|---|---|
| `[METHOD]` | Искомый метод **вызывается кем-то другим**. Строка с отступом — вызывающий. |
| `[FIELD]` | Искомое поле **доступно кем-то другим**. Строка с отступом — обращающийся. |
| `[CALLER→]` | Имя искомого метода встречается в **вызывающем коде**, строка с отступом показывает, какой API он вызывает. |
| `[STRING]` | Константная строка в коде совпадает. Строка с отступом — метод, использующий эту строку. |
| `[STRING_TABLE]` | Строка существует только в таблице строк DEX, в коде нет ссылки `const-string` (возможно, в аннотациях, оптимизирована R8 и т.д.). |
## Дополнительные возможности
### Деобфускация (--mapping)
После загрузки `--mapping` **ввод и вывод** поддерживают оригинальные (необфусцированные) имена.
**Запрос по оригинальному имени → автоматическое преобразование в обфусцированное имя для поиска в 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
Вывод имени после деобфускации:```bash
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace
**Показать одновременно обфусцированное и исходное имена:**```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]
Свободное сочетание с другими параметрами:```bash
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --layout list
dexfinder --dex-file app.apk --query "KotlinCases" --mapping mapping.txt --trace --style dex
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
**Матрица ввода×вывода:**
| Входной запрос | Без mapping | `--mapping` | `--mapping --show-obf` |
|---|---|---|---|
| Запутанное имя `LJ7;` | ✓ Запутанный вывод | ✓ Деобфусцированный вывод | ✓ Оба рядом |
| Исходное короткое имя `KotlinCases` | ✗ Не найдено | ✓ Автоконвертация + деобфусцированный вывод | ✓ Автоконвертация + оба рядом |
| Исходное полное имя `com.example...` | ✗ Не найдено | ✓ Автоконвертация + деобфусцированный вывод | ✓ Автоконвертация + оба рядом |
### Обнаружение скрытых 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
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope string
dexfinder --dex-file app.apk --query "content://com.android.contacts" --scope everything
### 按包名过滤```bash
# 只扫描自己的代码
dexfinder --dex-file app.apk --query "getDeviceId" --class-filter "Lcom/mycompany/"
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/"
### HTML Отчёт```bash
dexfinder --dex-file app.apk --query "getDeviceId" --trace --format html --output report.html
Открывается в браузере — сворачиваемое дерево вызовов, строка поиска, тёмная тема.
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --format sarif > results.sarif
### 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
dexfinder --dex-file app.apk --api-flags hiddenapi-flags.csv --fail-on blocked
## Производительность
Apple M серии, однопоточный:
| Размер APK | Количество DEX | Количество классов | Ссылки на методы | Сканирование | Hidden API |
|---|---|---|---|---|---|
| ~1 MB | 1 | ~2K | ~18K | **24ms** | — |
| ~10 MB | 2 | ~25K | ~100K | **335ms** | — |
| ~300 MB | 30+ | ~180K | ~1.2M | **3.9s** | **5.4s** |
Сравнение с veridex (C++) на том же ~300MB APK:
- veridex precise: **27s** (не может отслеживать рефлексию Binder/AIDL)
- veridex imprecise: **>32 min** (комбинаторный взрыв, был убит)
- **dexfinder: 5.4s** (оптимизация обратного индекса)
## Все параметры
| Параметр | Описание | Значение по умолчанию |
|---|---|---|
| `--dex-file` | Путь к файлу APK/DEX/JAR ** (обязательно)** | — |
| `--query` | Ключевое слово для поиска (Java / DEX/JNI / простое имя) | — |
| `--trace` | Включить трассировку цепочки вызовов (требует `--query`) | `false` |
| `--depth` | Максимальная глубина цепочки вызовов | `5` |
| `--layout` | Раскладка трассировки: `tree` (объединённое дерево) или `list` (развёрнутый список) | `tree` |
| `--style` | Стиль именования: `java` (читаемый) или `dex` (подпись JNI) | `java` |
| `--format` | Формат вывода: `text`, `json`, `model`, `html`, `sarif` | `text` |
| `--output` | Вывод в файл, а не в stdout | — |
| `--color` | Режим цвета: `auto`, `always`, `never` | `auto` |
| `--mapping` | Путь к ProGuard/R8 mapping.txt | — |
| `--show-obf` | Показывать как обфусцированные, так и деобфусцированные имена | `false` |
| `--api-flags` | Путь к hiddenapi-flags.csv | — |
| `--class-filter` | Фильтр по префиксу дескриптора класса (через запятую) | — |
| `--exclude-api-lists` | Исключённые уровни API | — |
| `--scope` | Область поиска: `all`, `callee`, `caller`, `string`, `string-table`, `everything` | `all` |
| `--diff` | Сравнить с другим APK/DEX, показать различия API | — |
| `--fail-on` | Вернуть ненулевой код выхода при обнаружении API указанного уровня (шлюз CI) | — |
| `--stats` | Показать только сводку статистики | `false` |
| `--version` | Показать номер версии | `false` |
### Файл конфигурации
Создайте `.dexfinder.yaml` в корне проекта, чтобы установить значения по умолчанию:```yaml
mapping: ./build/outputs/mapping.txt
class-filter: "Lcom/mycompany/"
api-flags: ./hiddenapi-flags.csv
style: java
depth: 8
color: auto
Параметры командной строки всегда переопределяют файл конфигурации.
git clone https://github.com/JuneLeGency/dexfinder.git cd dexfinder go build -o dexfinder ./cmd/dexfinder/ go test ./...
## 许可证
Apache License 2.0
| Format | Example | Behavior |
|---|
| Simple name | getDeviceId | Нечеткое совпадение подстроки во всех API |
| Java class | android.telephony.TelephonyManager | Все методы/поля этого класса |
| Java class#method | android.telephony.TelephonyManager#getDeviceId | Все перегруженные версии этого метода |
| Java full signature | ...TelephonyManager#getDeviceId() | Точное совпадение + возврат к перегрузкам |
| DEX/JNI signature | Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; | Только точное совпадение |
| Значение | Описание |
|---|
text | Обычный текстовый вывод с цветными тегами (по умолчанию) |
json | JSON — результаты сканирования или трассировка с древовидной/списковой структурой |
model | Структурированный JSON с полными типами MethodInfo/FieldInfo (для IDE/CI) |
html | Автономный HTML-отчет со сворачиваемыми деревьями и поиском |
sarif | Формат статического анализа SARIF 2.1.0 (GitHub / VS Code) |
| Значение | Что ищет | На какой вопрос отвечает | Тег вывода |
|---|
all | API вызываемых + поля + строки кода | "Кто вызывает этот API?" (по умолчанию) | [METHOD] [FIELD] [STRING] |
callee | Только сигнатуры целевого API в инструкциях invoke-* / get/put | "Кто вызывает этот конкретный метод/поле?" | [METHOD] [FIELD] |
caller | Только сигнатура вызывающего метода | "Что этот метод вызывает внутри?" | [CALLER→] |
string | Строковые константы в инструкциях const-string | "Где используется эта строка в коде?" | [STRING] |
string-table | Строки кода + полная таблица строк DEX | "Существует ли эта строка где-либо в DEX? (включает аннотации, мертвый код)" | [STRING] [STRING_TABLE] |
everything | Все вышеперечисленное вместе | Полная картина | все теги |
| Вход запроса | Без --mapping | --mapping | --mapping --show-obf |
|---|
Обфусцированный: LJ7; | ✓ обфусцированный вывод | ✓ деобфусцированный вывод | ✓ оба имени |
Исходный простой: KotlinCases | ✗ не найдено | ✓ авто-преобразование, деобф. вывод | ✓ авто-преобразование, оба имени |
Исходный полный: com.example...KotlinCases | ✗ не найдено | ✓ авто-преобразование, деобф. вывод | ✓ авто-преобразование, оба имени |
| Размер APK | Файлы DEX | Классы | Ссылки на методы | Сканирование | Скрытый API |
|---|
| ~1 MB | 1 | ~2K | ~18K | 24ms | — |
| ~10 MB | 2 | ~25K | ~100K | 335ms | — |
| ~300 MB | 30+ | ~180K | ~1.2M | 3.9s | 5.4s |
| Флаг | Описание | По умолчанию |
|---|
--dex-file | APK/DEX/JAR файл для анализа (обязательно) | — |
--query | Ключевое слово поиска (Java, DEX/JNI или простое имя) | — |
--trace | Включить трассировку цепочек вызовов (требуется --query) | false |
--depth | Максимальная глубина цепочки вызовов | 5 |
--layout | Макет трассировки: tree или list | tree |
--style | Стиль имени: java или dex | java |
--format | Формат вывода: text, json, model, html, sarif | text |
--output | Записать вывод в файл вместо stdout | — |
--color | Режим цвета: auto, always, never | auto |
--mapping | Путь к ProGuard/R8 mapping.txt | — |
--show-obf | Показывать обфусцированные имена вместе с деобфусцированными | false |
--api-flags | Путь к hiddenapi-flags.csv | — |
--class-filter | Префиксы дескрипторов классов через запятую | — |
--exclude-api-lists | Списки API для исключения из отчета | — |
--scope | Область поиска: all, callee, caller, string, string-table, everything | all |
--diff | Сравнить с другим APK/DEX и показать различия API | — |
--fail-on | Выйти с ненулевым кодом, если найдены скрытые API на этом уровне (CI gate) | — |
--stats | Показать только сводную статистику | false |
--version | Показать версию | false |
| Формат | Пример | Поведение |
|---|
| Простое имя | getDeviceId | Нечеткое подстроковое совпадение |
| Имя класса Java | android.telephony.TelephonyManager | Совпадение всех методов данного класса |
| Имя класса Java#метод | ...TelephonyManager#getDeviceId | Совпадение всех перегрузок данного метода |
| Полная сигнатура Java | ...#getDeviceId() | Точное совпадение + откат к перегрузкам |
| DEX/JNI сигнатура | Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; | Точное совпадение |
| 值 | 搜索内容 | 回答的问题 | 输出标签 |
|---|
all | 被调 API + 字段 + 代码字符串 | "谁调了这个方法?"(默认) | [METHOD] [FIELD] [STRING] |
callee | 仅 invoke-* / get/put 指令中的目标签名 | "谁调了这个具体方法/字段?" | [METHOD] [FIELD] |
caller | 仅调用方法的签名 | "这个方法内部调了什么?" | [CALLER→] |
string | const-string 指令中的字符串常量 | "这个字符串在代码哪里使用了?" | [STRING] |
string-table | 代码字符串 + DEX 完整字符串表 | "这个字符串是否存在于 DEX 中?"(含注解、死代码) | [STRING] [STRING_TABLE] |
everything | 以上全部 | 完整视图 | 全部标签 |