
إضافة Binary Ninja تقوم بمزامنة التحليل ثنائي الاتجاه مع مستودع Ghidra Server، عبر عملية فرعية لجسر Java.
إضافة Binary Ninja تتصل بمستودع Ghidra Server وتستورد تحليله — الرموز، وأسماء الدوال، والتعليقات — مباشرةً إلى عرض ثنائي مفتوح في Binary Ninja.
لكل من Ghidra وBinary Ninja نقاط قوة. تتيح لك هذه الإضافة استخدام كليهما على نفس الملف الثنائي دون نسخ الأسماء أو التعليقات يدويًا بينهما. اتصل بـ Ghidra Server قيد التشغيل، وتصفح مستودعاته، وانقر نقرًا مزدوجًا على أي ملف مشروع لسحب تحليله إلى عرض BN المفتوح حاليًا.
المزامنة تسير في الاتجاهين.
| BN ← Ghidra (استيراد) | BN → Ghidra (إيداع) | |
|---|---|---|
| الرموز (التسميات، أسماء الدوال) | ✓ | ✓ |
| التعليقات (EOL/PRE/POST/PLATE/REP) | ✓ | ✓ |
| توقيعات الدوال (نوع الإرجاع، اصطلاح الاستدعاء) | ✓ | ✓ |
| معاملات الدوال (إعادة التسمية، إعادة تحديد النوع، الإضافة) | ✓ | ✓ |
| أنواع البيانات (struct/union/enum/typedef + pointer/array) | ✓ | ✓ |
| المعادلات (أسماء الثوابت + المراجع) | ✓ | ✓ |
| الإشارات المرجعية | ✓ | ✓ |
| عناصر البيانات المُنمّطة | ✓ | ✓ |
| أعلام الدوال (thunk، no-return، inline) | ✓ كوسوم BN | — |
| المتغيرات المحلية (مدركة للتخزين) | ✓ | جزئي — تعيين تخزين السجلات غير مُنفَّذ |
Binary Ninja (C++ plugin)
│ TCP / newline-delimited JSON
▼
ghidra-bridge-*.jar (Java, runs as a subprocess)
│ Java RMI / SSL
▼
Ghidra Server (ghidraSvr, running on the network)
تُشغّل الإضافة عملية Java فرعية (الجسر "bridge") عند التحميل. يحتفظ الجسر باتصال RMI بـ Ghidra Server ويتحدث بروتوكول JSON بسيط مع الإضافة عبر مقبس TCP محلي. يُبقي هذا كل شيفرة Java/RMI خارج عملية C++ ويتيح لـ JVM البدء في الخلفية بينما يُكمل BN التحميل.
كما تهيّئ JVM الخاصة بالجسر إطار عمل Application الخاص بـ Ghidra عند بدء التشغيل حتى يتمكن مسار الكتابة من استخدام واجهات برمجة نموذج البرنامج عالية المستوى في Ghidra (ProgramDB، DataTypeManager، SymbolTable، FunctionManager) بدلًا من كتابات db.Table.putRecord() الخام — انظر مسار كتابة الإيداع أدناه.
| المسار | اللغة | الدور |
|---|---|---|
plugin/ | C++ / Qt6 | إضافة الشريط الجانبي في Binary Ninja |
bridge/ | Java 17 | عميل Ghidra RMI + خادم جسر JSON |
الإضافة (C++):
plugin.cpp — تسجّل الإعدادات وأداة الشريط الجانبي؛ تبدأ JVM الخاصة بالجسر بحماس عند التحميلGhidraConnection.cpp — singleton؛ تدير دورة حياة الجسر وجميع العمليات المدعومة بـ RMIBridgeProcess.cpp — تُطلق JAR الخاص بالجسر كعملية فرعية مع أنابيب stdout/stderr؛ تقرأ سطر المصافحة READY port=NBridgeClient.cpp — عميل TCP؛ يرسل طلبات JSON، ويستقبل الردود، ويوزّع الأحداث غير المتزامنةSyncEngine.cpp — تطبّق GhidraDbExport على BinaryView (الرموز، التعليقات، الأعلام)ui/ProjectPanel.cpp — أداة الشريط الجانبي: شجرة المستودع، حوار الاتصال، سجل النشاطui/ConnectDialog.cpp — حوار المضيف/المنفذ/المستخدم/كلمة المرورالجسر (Java):
BridgeMain.java — تحليل الوسائط؛ تهيّئ UniversalIdGenerator وإطار عمل Application الخاص بـ Ghidra؛ تبدأ خادم TCP؛ تطبع READY port=N إلى stdoutBridgeServer.java — تقبل اتصال عميل TCP واحد وتُسلّمه BridgeConnectionBridgeConnection.java — موزّع طلبات JSON؛ تُسلسل ردود Ghidra API إلى JSON؛ تتعامل مع opCheckin (تنشئ نسخة برنامج جديدة على الخادم)GhidraSession.java — جلسة RMI مُصادَق عليها؛ تغلّف RemoteRepositoryServerHandleEventStreamer.java — خيط خلفي لكل مستودع مفتوح؛ تدفع RepositoryChangeEvents إلى الإضافة كأحداث JSON غير متزامنةDatabaseExporter.java — مسار القراءة: تستخرج جداول الرموز/التعليقات/أعلام الدوال/أنواع البيانات/المعادلات/الإشارات المرجعية من ManagedBufferFileHandle (مخزن قاعدة بيانات Ghidra البعيد) عبر وصول db.jar الخامProgramApplier.java — مسار الكتابة: تفتح ملف المخزن كـ ProgramDB حقيقي وتطبّق جميع تغييرات جانب BN عبر واجهات Ghidra عالية المستوى (انظر مسار كتابة الإيداع أدناه)DatabaseImporter.java — مساعدات الكتابة الخام القديمة محفوظة فقط كمنفذ اختبار؛ apply(...) الإنتاجي يفوّض إلى ProgramApplierيفتح opCheckin ملف المخزن المُدار الخاص بالبرنامج في وضع الكتابة، ويُنشئ ProgramDB فوقه، ويطبّق تغييرات جانب BN عبر واجهات نموذج البرنامج في Ghidra. يُتجنّب الكتابة الخام db.Table.putRecord() — فقد كانت مصدر كل خطأ إفساد في الإيداع واجهناه على الإطلاق:
| كتابة على الطبقة الخاطئة | نمط الفشل |
|---|---|
setIntValue(col, longTypeId) على جدول Function Data | IntField.setLongValue يقتطع l2i بصمت → تلف StackPurge عند كل تحديث توقيع |
setByteValue(col, isUnion) على V5V6 Composite Data Types | العمود هو BooleanField في Ghidra 12.x → IllegalFieldAccessException ("Illegal field access") |
setIntValue(col, 0) على عمود V2 Typedef Flags | العمود هو ShortField → نفس الانهيار، مخطط مختلف |
| كتابة ترويسة مركّبة بدون صفوف إعدادات المكوّنات | CompositeEditorModel.cloneAllComponentSettings يرمي ArrayIndexOutOfBoundsException عند فتح البنية في Ghidra |
كتابة رمز PARAMETER مع SYM_ADDR_COL = عنوان RAM | Address is not a VariableAddress يرميه FunctionDB.loadSymbolBasedVariables عند أي وصول للدالة |
تمرير DBChangeSet بقيمة null إلى DBHandle.save() | يكتب الخادم ملف بيانات تغيير بحجم 0 بايت → يفشل الاستخراج التالي بـ EOFException في ProgramContentHandler.loadProgramChangeSet |
لا يعاني ProgramApplier من هذه المزالق لأنه يمرّ عبر DataTypeManager.addDataType، SymbolTable.createLabel، Listing.setComment، Function.setReturnType، إلخ — واجهات تحافظ تلقائيًا على ثوابت الجداول المتشابكة في Ghidra. كما يشغّل تمريرة cleanupBadVariableSymbols في بداية كل إيداع لتنقية الإفساد الذي تركته إصدارات الجسر الأقدم في قاعدة البيانات.
server-package/CleanupBadVariableSymbols.java هو GhidraScript مستقل يشغّل نفس التنظيف عبر analyzeHeadless — مفيد عندما يكون الملف مُفسَدًا لدرجة تعذّر فتحه في واجهة Ghidra الرسومية.
./test.sh # macOS / Linux: tiers 0-3 (C++ unit + BN-headless + Java)
test.bat # Windows equivalent
test.bat --parity # cross-DB parity tier only (C++ BN tests + gradlew parityTest)
test.bat --e2e # live Ghidra-server E2E (starts a local ghidraSvr)
المجموعة منظّمة في خمس طبقات. الطبقات 2–4 موجودة لإثبات خاصية واحدة: نفس البيانات المتوافقة تنتهي مخزّنة في كلٍّ من .bndb وقاعدة بيانات برنامج Ghidra (مصفوفة التوافق في أعلى هذا README).
| الطبقة | ماذا | أين | البوابة |
|---|---|---|---|
| 0 | اختبارات وحدة خالصة | plugin/test/*.cpp (binja-ghidra-tests)، الجسر *Test.java | دائمًا |
| 1 | ذهاب وإياب Ghidra-DB | الجسر *RoundTripTest.java (ProgramApplier مقابل ProgramDB حقيقي) | يحتاج ghidra.home / GHIDRA_HOME |
| 2 | ذهاب وإياب BN BinaryView/.bndb | plugin/test/bn/ (binja-ghidra-bn-tests؛ binaryninjacore بلا واجهة) | يتخطّى بنظافة بدون ترخيص BN قادر على العمل بلا واجهة (يُحترم متغير البيئة BN_LICENSE) |
| 3 | تكافؤ عبر قواعد البيانات | CanonicalParityTest (C++ و Java) مقابل القيم الذهبية المشتركة في testdata/parity/fixtures/ | مع الطبقتين 1+2 |
| 4 | E2E على خادم حي | الجسر LiveServerE2ETest — يُقلع ghidraSvr حقيقيًا في مجلد مؤقت، ويزرع عبر analyzeHeadless، ويقود checkout → export → checkin → re-export عبر RMI | test.bat --e2e (يضبط GHIDRA_E2E=1) |
مرجع التكافؤ (الطبقة 3). يتحقق الجانبان بشكل مستقل مقابل نفس
JSON القانوني المُودَع (شكل DatabaseExporter في الجسر). اتجاه الاستيراد:
تُحمَّل القيمة الذهبية في ProgramDB (Java) وفي BinaryView
عبر SyncEngine (C++)، ويجب أن يساوي كل إعادة تصدير القيمة الذهبية. اتجاه الإيداع:
يجب أن تُنتج تعديلات BN النصية بالضبط
fixtures/checkin/*/expected-preview.json (C++)، ويجب أن يُعيد تطبيق تلك المعاينة عبر
ProgramApplier التصدير كـ expected-after.json (Java). إذا طابق الجانبان
القيم الذهبية المشتركة، فإن قاعدتي البيانات تتفقان بالتعدّي. توجد أوضاع مقارنة الحقول
وجدول تطبيع أسماء الأنواع في
testdata/parity/RULES.md؛ والملف التنفيذي للاختبار هو
testdata/bin/parity_x64.bin (التخطيط في parity_x64.md).