
محلل ثابت للقطات AOT الخاصة بـ Flutter/Dart — يستعيد أسماء الدوال، والتسلسلات الهرمية للفئات، ومخططات الاستدعاء، والإشارات السلوكية من ملف libapp.so دون تضمين أو تشغيل آلة Dart الافتراضية. يدعم ARM64 وx86_64، وDart 2.10–3.12.
محلّل لقطات AOT الخاصة بـ Dart. يحوّل libapp.so — كود Dart المُجمَّع داخل حزمة APK لإصدار Flutter — إلى أسماء دوال، وتخطيطات أصناف، ورسوم بيانية للاستدعاءات، وإشارات سلوكية، وشيفرة زائفة قابلة للقراءة. دون Dart VM، ودون تجميع SDK، ودون أي رجوع إلى وقت التشغيل.
إشعار التفرّع: AOTopsy هو تفرّع من unflutter، الذي أنشأه في الأصل Anthony Zboralski. لم يعد مستودع
zboralski/unflutterالأصلي متاحًا (أزاله المؤلف)؛ ويوجد استمرار مجتمعي علىKristijanZic/unflutter. يعود كل الفضل في محلّل اللقطات الأصلي، ومفكّك تسلسل العناقيد، وخط أنابيب تفكيك ARM64، وتكامل Ghidra/IDA إلى المؤلف الأصلي. ويوسّعه AOTopsy بدعم x86_64، ومفكّك شيفرة أصلي، واستنتاج أنواع على مستوى البرنامج بأكمله، وتوليد سكربتات Frida، وتوثيق شامل.
| المخرجات | ما هي |
|---|---|
| أسماء الدوال | اسم Dart الأصلي لكل دالة مُجمَّعة |
| بنى الأصناف | أسماء الحقول، وإزاحات البايت، وسلاسل الوراثة |
| الرسم البياني للاستدعاءات | حواف الاستدعاء المباشر (BL) وغير المباشر (BLR/dispatch) مع مصدرها |
| مراجع النصوص | أي الدوال تحمّل أي نصوص حرفية من مجمّع الكائنات |
| الإشارات السلوكية | تصنيف كلمات مفتاحية للتشفير، والشبكة، والمقامرة، وSIM، والموقع، وWebView، وسلسلة الكتل |
| الشيفرة الزائفة | مخرجات مفكوكة محايدة المعمارية من شيفرة آلة ARM64 أو x86_64 |
| تصدير مصدر Dart | ملفات .dart معيارية تُعاد بناؤها للمشروع بأكمله مع الأصناف والحقول والدوال |
يدعم ARM64 وx86_64. يغطي Dart 2.10 حتى 3.13 (3.13.2 هو الحد المستقر الحالي) — كل إصدار مُنمذَج يُحلَّل بنظافة على كلتا المعماريتين، انظر COVERAGE.md (93 بناءً عينيًا، 0 إخفاقات).
يُقاس AOTopsy مقابل الحقيقة المرجعية، لا يُدَّعى. تُفرَض خاصيتان بواسطة مجموعة الاختبارات عند كل تغيير:
| المقياس | القيمة | ما يعنيه |
|---|---|---|
| توافق استعادة الأسماء | 90.2% إجمالًا (81.3% أسوأ نطاق، ≥ 0.81 حد أدنى للبوابة) عبر 44 بناءً مرجعيًا | أسماء الدوال المستعادة تُقارَن مقابل .symtab الخاص بـ ELF لكل بناء، وهي الحقيقة المرجعية الخارجية — TestSymtabDifferential. لوحة النتائج الكاملة لكل بناء: BENCHMARK.md (make bench). |
| صلاحية صياغة مفكّك الشيفرة | 100% Dart صالح | كل دالة شيفرة زائفة مُصدَرة تُحلَّل كـ Dart — TestDecompileQualityCorpus. |
| معدل التلفيق | 0% | قاعدة §2: لا تُصدِر أبدًا اسمًا أو نوعًا أو هدف استدعاء مُخمَّنًا كحقيقة. تُعرَض المجهولات بصدق (indirectCall، <unknown>، dynamic). |
التوائم المرجعية هي عمليات بناء إنتاجية حقيقية لا يمكننا إعادة توزيعها، لذا تعمل
بوابات الفروق تلك محليًا؛ أما CI العام فيتحقق من البناء، وvet، وgofmt،
وstaticcheck، واختبارات الوحدة (مخلوطة)، وحد أدنى للتغطية عبر مصفوفة
المنصات. مجلد samples/ غير قابل لإعادة التوزيع ومُستبعَد عبر gitignore، لذا تتخطاه
الاختبارات المعتمدة على العينات — لكن فقط عندما تكون المجموعة غائبة كليًا؛ أما المجموعة
الموجودة التي ينقصها عينة مسجّلة فتفشل، لأن هذين الأمرين يعنيان نقيضين.
كيف يُقاس كل رقم هنا (الحقيقة المرجعية، وتعريفات المقاييس، وإعادة الإنتاج): METHODOLOGY.md. انظر
SECURITY.md للتحقق من ثنائيات الإصدار والنطاق الصادق أدناه.
make build
./aotopsy libapp.so # full pipeline
./aotopsy doctor libapp.so # quick diagnostic
./aotopsy export-dart --lib libapp.so --out ./lib # reconstruct entire Dart source project
./aotopsy _debug decompile-native --lib libapp.so --find MyClass # find and decompile a function
راجع DEMO.md للحصول على جولة إرشادية مدتها خمس دقائق على لقطة حقيقية، أو WORKFLOW.md للمنهجية خطوة بخطوة عندما يكون لديك ملف APK خام ولا تعرف من أين تبدأ.
يتعامل AOTopsy مع لقطة Dart AOT كقواعد نحوية ثنائية حتمية. لكل بايت تفسير صحيح واحد فقط بشرط توفر القيود الصحيحة (بنية ELF، وسحر اللقطة، وتجزئة الإصدار، وجدول CID، وترميز العنقود). يطبّق المحلّل القيود حتى لا يبقى سوى تفسير واحد — دون استدلالات إرشادية، ودون تخمين.
تعمل خطوط المعالجة على مراحل، كل منها دالة نقية من البايتات إلى بيانات منظّمة:
flowchart TD
A[libapp.so] --> B[ELF parse]
B --> C[snapshot region extraction]
C --> D[version detection]
D --> E[cluster alloc<br/>object census]
E --> F[cluster fill<br/>field values, names, strings]
F --> G[instructions table<br/>code ranges, stub boundaries]
G --> H{architecture?}
H -->|ARM64| I[ARM64 disassembly]
H -->|x86_64| J[x86_64 disassembly]
I --> K[CFG + call edges<br/>register provenance]
J --> K
K --> L[type inference<br/>BLR receiver type resolution]
L --> M[signal classification<br/>behavioral keyword matching]
M --> N[JSONL + HTML + DOT<br/>pseudocode output]
يتشارك backendان مستقلان النصف الأمامي نفسه (من ELF حتى تعبئة العنقود)، ثم ينفصلان حسب المعمارية: internal/disasm لـ ARM64، وinternal/disasm/x86.go لـ x86_64. يتعامل المُفكِّك (internal/decompiler) مع كلتا المعماريتين عبر IR موحّد.
flowchart LR
subgraph "Shared front half"
A[elfx] --> B[snapshot]
B --> C[cluster]
end
subgraph "ARM64 backend"
C --> D1[disasm ARM64]
D1 --> E1[callgraph]
E1 --> F1[signal]
end
subgraph "x86_64 backend"
C --> D2[disasm x86_64]
D2 --> E2[callgraph]
E2 --> F2[signal]
end
subgraph "Decompiler (both archs)"
C --> G[decompiler IR]
G --> H[pseudocode]
end
flowchart LR
subgraph Blutter
direction TB
B1[libapp.so] --> B2[Compile matching<br/>Dart SDK]
B2 --> B3[Embed Dart VM]
B3 --> B4[Deserialize via<br/>VM internal APIs]
B4 --> B5[Perfect fidelity]
end
subgraph AOTopsy
direction TB
A1[libapp.so] --> A2[Parse binary format<br/>directly]
A2 --> A3[No VM, no SDK]
A3 --> A4[Version-specific<br/>format modeling]
A4 --> A5[Portability + speed]
end
Blutter يضمّن Dart VM لإلغاء تسلسل اللقطة (snapshot) عبر مسارات الشيفرة الخاصة به. دقة مثالية، لكنه يتطلب تجميع Dart SDK مطابق لكل إصدار هدف — وهو مخصص لمعمارية ARM64 فقط، دون دعم ثابت لمعمارية x86_64. AOTopsy هو المحلل الثابت الوحيد المستقل عن الإصدار، مع مفكك شيفرة زائفة أصلي ودقة أساس مرجعي منشورة.
يحلل AOTopsy تنسيق الملف الثنائي مباشرةً. لا VM، ولا SDK. المقايضة: يجب نمذجة كل تغيير في التنسيق عبر إصدارات Dart بشكل صريح. لا يوجد وقت تشغيل للتعامل مع ذلك تلقائيًا.
aotopsy libapp.so # disasm + call edges + signal + metadata (ARM64: + Ghidra/IDA)
aotopsy signal libapp.so # same, skip metadata
aotopsy libapp.so --graph # also build call graph DOT files
الخيارات: --out <dir> (الافتراضي: <basename>.aotopsy/)، --quiet، --strict، --max-steps <n>، --k <n> (عمق سياق الإشارة، الافتراضي 2)، --decompile.
يكتب --decompile شيفرة Dart الزائفة لكل دالة إلى <out>/dart/. وهو معطّل افتراضيًا لأنه يضاعف حجم دليل الإخراج ثلاث مرات تقريبًا؛ كل تشغيل بدونه ينبّه إلى ذلك، بحيث تكون هذه الإمكانية قابلة للاكتشاف لا مجرد موجودة.
aotopsy doctor libapp.so # Dart version, pointer size, support status, build features
aotopsy find-libapp --apk app.apk # locate libapp.so inside an APK