
إضافة 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).
مثبّتات الانحدار طويلة الأمد على جانب Java:
DataTypesRoundTripTest.struct_cloneSettings_doesNotThrow — يجب أن تبقى إعدادات المركّب متسقة مع الترويسة (انهيار cloneAllComponentSettings)FunctionSignaturesRoundTripTest.returnType_doesNotCorruptStackPurge — اقتطاع IntFieldParametersRoundTripTest.noParameterSymbol_endsUpAtRamAddress — ثابت VariableAddressتتطلب اختبارات الذهاب والإياب والتكافؤ تثبيت Ghidra (يُستخدم وقت التشغيل
لخدمات اللغة). يُقرأ المسار من خاصية نظام Gradle ghidra.home أو متغير البيئة
GHIDRA_HOME؛ ويمرّر build.gradle قيمة ghidraHome افتراضيًا. تحتاج اختبارات C++ للطبقتين 2/3
إضافةً إلى ذلك أن يكون binaryninjacore قابلًا للتحميل
(تضع السكربتات مجلد تثبيت BN على PATH).
| التبعية | ملاحظات |
|---|---|
| Binary Ninja (تجاري) | مُختبَر مقابل الإصدار المطابق لـ api_REVISION.txt في تثبيت BN |
| Ghidra Server | مُختبَر مع Ghidra 12.0.4. يجب أن يكون قيد التشغيل وقابلًا للوصول عبر RMI/SSL |
| Java 17+ JDK | يُوصى بـ Eclipse Adoptium JDK 21 |
| CMake 3.24+ | |
| Ninja | |
| مترجم C++ | MSVC 2022+ على Windows؛ clang على macOS؛ gcc/clang على Linux |
| Qt 6.7+ | انظر إعداد Qt أدناه؛ يجب أن يكون qmake على PATH وقت البناء |
| Gradle (عبر wrapper) | يستخدم الجسر Gradle wrapper — لا حاجة لتثبيت منفصل |
| Poetry (لبناء Qt فقط) | مطلوب فقط عند بناء Qt من الوحدة الفرعية qt-build. ثبّته بـ pip install poetry أو pipx install poetry. |
| libclang 19 (لبناء Qt فقط) | مطلوب من نظام بناء Qt. انظر qt-build/README.md لتعليمات التنزيل. |
ترتبط الإضافة بنفس بناء Qt 6 الذي يستخدمه Binary Ninja. لديك خياران:
الخيار A — استخدام تثبيت Qt موجود (الأسرع إذا كان لديك Qt بالفعل)
مرّر Qt6_DIR مشيرًا إلى دليل CMake الخاص بـ Qt لديك:
Qt6_DIR=/path/to/Qt/6.x.y/clang_64/lib/cmake/Qt6 ./build.sh
على macOS يكتشف سكربت البناء Qt تلقائيًا إذا ثُبّت بواسطة مثبّت Qt عبر الإنترنت تحت /usr/local/Qt*.
الخيار B — بناء Qt من الوحدة الفرعية qt-build (~1-2 ساعة، مرة واحدة لكل جهاز)
الوحدة الفرعية qt-build (سكربتات بناء Qt من Vector35) تُصرّف Qt 6 مع تصحيحات Binary Ninja. تتطلب Poetry وlibclang 19 (انظر المتطلبات الأساسية أعلاه وqt-build/README.md).
يُثبَّت Qt في qt/<version>/<compiler>/ داخل المستودع:
| المنصة | مسار التثبيت |
|---|---|
| macOS | qt/6.10.1/clang_64/ |
| Linux x86-64 | qt/6.10.1/gcc_64/ |
| Windows | qt/6.10.1/msvc2022_64/ |
# First time on a new machine:
./build.sh qt # compiles Qt — takes 1-2 hours
# All subsequent builds (Qt cached in qt/, reused automatically):
./build.sh
خطوة qt مطلوبة مرة واحدة فقط. يكتشف CMake وسكربتات البناء Qt المبني في qt/ في كل تشغيل لاحق ويتخطى الوحدة الفرعية بالكامل. دليل qt/ مُتجاهَل في git.
git clone https://github.com/mutinylaboratories/ghidra_svr_bridge.git
cd ghidra_svr_bridge
git submodule update --init # populates binaryninja-api and qt-build (~seconds)
ثم اتبع إعداد Qt أعلاه (الخيار A أو B)، وشغّل:
./build.sh install
# Incremental build of both components
./build.sh
# Full clean rebuild + install into BN plugins folder
./build.sh clean install
# Build only the C++ plugin
./build.sh plugin
# Build only the Java bridge
./build.sh bridge
# Build Qt once on a machine without Qt installed
./build.sh qt
متغيرات البيئة (كلها اختيارية — يضبط السكربت قيمًا افتراضية معقولة):
BN_INSTALL=/Applications/Binary\ Ninja.app/Contents/MacOS
Qt6_DIR=/usr/local/Qt-6.7.2/lib/cmake/Qt6
حرّر المسارات في أعلى build.bat لتطابق بيئتك قبل الاستخدام الأول:
set "JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-21.0.11.10-hotspot"
set "VSDEVCMD=C:\Program Files\Microsoft Visual Studio\2022\Professional\Common7\Tools\VsDevCmd.bat"
set "Qt6_DIR=C:\qt\v6.7.2\lib\cmake\Qt6"
set "BN_INSTALL=C:\Program Files\Vector35\BinaryNinja"
rem Incremental build of both components
build.bat
rem Full clean rebuild + install into BN plugins folder
build.bat clean install
rem Build only the C++ plugin
build.bat plugin
rem Build only the Java bridge
build.bat bridge
rem Build Qt once on a machine without Qt installed
build.bat qt
يستخدم بناء C++ خاصية CMake FetchContent لاستنساخ binaryninja-api عند الالتزام الدقيق المسجّل في api_REVISION.txt، بحيث تتطابق ABI الإضافة دائمًا مع إصدار BN المثبّت. يُنزَّل Ghidra تلقائيًا بواسطة CMake عند أول تهيئة إذا لم يُضبط GHIDRA_HOME.
بعد التثبيت، اضبط ما يلي في إعدادات Binary Ninja (Edit → Preferences → Settings، ابحث عن "Ghidra"):
| الإعداد | الوصف |
|---|---|
ghidra.javaExe | المسار الكامل إلى java.exe |
ghidra.ghidraHome | جذر تثبيت Ghidra لديك (يحتوي على Ghidra/Framework/…) |
ghidra.trustAllCerts | اضبطه على true إذا كان Ghidra Server لديك يستخدم شهادة موقّعة ذاتيًا |
ghidra.defaultHost | يملأ حوار الاتصال مسبقًا |
ghidra.defaultPort | الافتراضي: 13100 |
ghidra.defaultUser | يملأ حوار الاتصال مسبقًا |
شرط مسبق للخطوة 5: يجب أن يكون ملف البرنامج مُودَعًا في مستودع Ghidra Server (وليس مجرد مفتوح محليًا في Ghidra). في Ghidra: انقر بزر الفأرة الأيمن على الملف في نافذة Project → Version Control → Add to Version Control….
تتواصل الإضافة والجسر عبر مقبس TCP محلي باستخدام JSON محدّد بأسطر جديدة. يحمل كل طلب عددًا صحيحًا id وسلسلة op؛ وكل رد يعيد صدى id. تحمل الأحداث غير المتزامنة (تغييرات المستودع من جانب الخادم) مفتاح "event" بدلًا من ذلك.
| العملية | الاتجاه | الغرض |
|---|---|---|
ping, status, connect, disconnect | طلب/رد | دورة حياة الجلسة |
list_repos, open_repo, close_repo | طلب/رد | تعداد المستودعات |
list_items, get_subfolders | طلب/رد | تصفح المستودع |
get_versions, get_checkouts | طلب/رد | حالة التحكم بالإصدارات |
checkout, terminate_checkout | طلب/رد | قفل كتابة حصري |
open_db | طلب/رد | قراءة قاعدة بيانات Ghidra كاملة → JSON (ثقيل) |
checkin | طلب/رد | تطبيق تغييرات جانب BN → نسخة مستودع جديدة (ثقيل، عبر ProgramApplier) |
download_binary, upload_binary | طلب/رد | نقل الملف الثنائي الأصلي للداخل/للخارج |
delete_item | طلب/رد | إزالة ملف من المستودع |
repo_changed | حدث (غير متزامن) | دفع RepositoryChangeEvent من جانب الخادم |
يحتوي المستودع على كل ما يلزم لإعادة البناء من الصفر. إعداد كل مطوّر غير الموجود في git:
git clone https://github.com/mutinylaboratories/ghidra_svr_bridge.git
cd ghidra_svr_bridge
git submodule update --init --recursive
bridge/gradle.properties:
ghidraHome=C:/Users/<you>/ghidra/ghidra_12.0.4_PUBLIC
Qt6_DIR إلى تثبيت موجود أو شغّل ./build.sh qt (Windows: build.bat qt) مرة واحدة.binaryninja-api المطابق من GitHub:
./build.sh --channel stable # default — latest stable release (from GitHub)
./build.sh --channel dev # latest dev (dev branch head, from GitHub)
./build.sh --bn-api <commit> # explicit commit, no GitHub lookup (escape hatch)
--channel و--bn-api متعارضان؛ وبدونهما تُستخدم قناة stable. يستعلم --channel من GitHub الخاص بـ Vector35/binaryninja-api (أحدث إصدار stable/*، أو رأس فرع dev) لذا يحتاج إلى وصول للشبكة. إذا كان BN المثبّت لديك متأخرًا عن أحدث إصدار، مرّر --bn-api مع SHA الدقيق من api_REVISION.txt لذلك التثبيت.عند فتح جلسة Claude Code جديدة، أفضل مؤشرات التأهيل هي هذا README بالإضافة إلى الحالة الحالية على dev:
bridge/src/main/java/com/ghidra_svr/bridge/ProgramApplier.javabridge/src/test/java/com/ghidra_svr/bridge/ProgramTestBase.javabridge/src/test/java/com/ghidra_svr/bridge/*RoundTripTest.javagit log --oneline — يوضّح كل سطر موضوع ما تغيّر ولماذاProgramApplier مدخلات المعاملات is_local لأن تعيين فهارس سجلات BN إلى تخزين Ghidra يتطلب ترجمة جدول سجلات لكل معمارية. المعاملات تعمل؛ أما المتغيرات المحلية فلا تتزامن بعد.DatabaseExporter فضاء عناوين RAM واحدًا. قد تنتج فضاءات التراكب أو معماريات هارفارد عناوين غير صحيحة.DBChangeSet فارغًا لإبقاء عمليات الاستخراج تعمل. لذلك لا تستطيع آليات الدمج عند الاستخراج في Ghidra حل التعديلات المتزامنة تلقائيًا بين مستخدمي BN وGhidra — الكاتب الأخير يفوز.