
أداة بحث عبر الأنظمة الأساسية عن الطرق (methods) في ملفات APK/DEX مع تتبع سلسلة الاستدعاءات، وإزالة تشويش ProGuard، واكتشاف الواجهات البرمجية المخفية.
English | 中文 | الموقع الإلكتروني

الموقع الإلكتروني: junelegency.github.io/dexfinder
أداة عبر الأنظمة الأساسية للبحث عن مراجع الدوال والحقول في APK/DEX مع تتبع سلاسل الاستدعاء، وإزالة التعتيم ProGuard/R8، واكتشاف واجهات برمجة التطبيقات المخفية في Android.
مستوحاة من أداة veridex الخاصة بـ Android، وأُعيد تنفيذها بلغة Go مع قدرات محسّنة: كشف الانعكاس بشكل أسرع، تتبع سلسلة الاستدعاءات (veridex يُظهر مستوى واحدًا فقط)، وتنسيقات إخراج مرنة.
--fail-on blocked يخرج برمز غير صفري عند العثور على واجهات API المقيدة.dexfinder.yaml للإعدادات الافتراضية للمشروع، مع إمكانية تجاوز عن طريق علامات CLIHomebrew (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
**ثنائي**: تنزيل من [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 بالكشف التلقائي والتحويل بينها.
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 و 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)
--- 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 keys, etc.)```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 لـ 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، بخيط واحد:
مقارنةً بـ 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، والكشف عن واجهات برمجة التطبيقات المخفية في Android.
مبنية على مبدأ veridex من Android، مع إعادة التنفيذ والتحسين باستخدام Go: كشف أسرع للانعكاس، تتبع سلسلة استدعاء متعددة المستويات (veridex مستوى واحد فقط)، تنسيق إخراج مرن.
--fail-on blocked عند اكتشاف واجهات برمجة تطبيقات مقيدة، يُرجع رمز خروج غير صفري.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)
قائمة — كل سلسلة معروضة بشكل مستقل (نمط 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 و 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...` | ✗ غير موجود | ✓ تحويل تلقائي + إخراج غير مبهم | ✓ تحويل تلقائي + كليهما جنبًا إلى جنب |
### 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
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++) على نفس APK ~300MB:
- veridex precise: **27s** (لا يمكنه تتبع انعكاس Binder/AIDL)
- veridex imprecise: **أكثر من 32 دقيقة** (انفجار حاصل ضرب ديكارتي، تم قتله)
- **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 ./...
## الترخيص
| الصيغة | مثال | السلوك |
|---|
| اسم بسيط | getDeviceId | مطابقة جزئية غير دقيقة عبر جميع واجهات API |
| كلاس Java | android.telephony.TelephonyManager | جميع دوال/حقول ذلك الكلاس |
| كلاس#دالة في Java | android.telephony.TelephonyManager#getDeviceId | جميع التحميلات الزائدة لتلك الدالة |
| التوقيع الكامل في Java | ...TelephonyManager#getDeviceId() | مطابقة تامة مع الرجوع إلى التحميلات الزائدة |
| توقيع DEX/JNI | Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; | مطابقة تامة فقط |
| القيمة | الوصف |
|---|
text | إخراج نصي عادي مع وسوم ملونة (الافتراضي) |
json | JSON — نتائج المسح أو التتبع بتنسيق شجري/قائمة |
model | JSON منظم مع أنواع MethodInfo/FieldInfo كاملة (لبيئات التطوير/التكامل المستمر) |
html | تقرير HTML مستقل مع أشجار قابلة للطي وبحث |
sarif | تنسيق تحليل ثابت SARIF 2.1.0 (GitHub / VS Code) |
| القيمة | ما يبحث عنه | السؤال الذي يجيب عليه | الوسم الناتج |
|---|
all | واجهات برمجة التطبيقات المستدعاة + الحقول + سلاسل النص | "من يستدعي هذه الواجهة البرمجية؟" (الافتراضي) | [METHOD] [FIELD] [STRING] |
callee | توقيعات واجهة برمجة التطبيقات المستهدفة فقط في تعليمات invoke-* / get/put | "من يستدعي هذه الطريقة/الحقل بالتحديد؟" | [METHOD] [FIELD] |
caller | توقيع الطريقة المستدعية فقط | "ما الذي تستدعيه هذه الطريقة داخليًا؟" | [CALLER→] |
string | ثوابت النص في تعليمات const-string | "أين يُستخدم هذا النص في الكود؟" | [STRING] |
string-table | نصوص الكود + جدول النص الكامل لـ DEX | "هل هذا النص موجود في أي مكان في DEX؟" (يشمل التعليقات التوضيحية، الكود الميت) | [STRING] [STRING_TABLE] |
everything | جميع ما سبق مجتمعًا | الصورة الكاملة | جميع الوسوم |
| استعلام الإدخال | بدون تعيين | --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 | إنهاء برمز غير صفري إذا تم العثور على APIs مخفية في هذا المستوى (بوابة CI) | — |
--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 | كل ما سبق | عرض كامل | جميع الوسوم |