
محرك تحليل ساكن zero-symbol يستخرج سطح هجوم Windows RPC ويرتبه رياضيًا باستخدام نموذج مخاطر قائم على AHP.
ثابت مقابل ديناميكي، اقرأ هذا أولاً. من الجدير توضيح الخط الفاصل بين التحليل الديناميكي و الثابت منذ البداية: هذه الأداة تعتمد في ترتيبها على التحليل الثابت فقط. فهي تقرأ بنى MIDL / NDR المترجمة مباشرةً من الملف التنفيذي ولا تنفّذ أي شيء.
فرز ثابت لسطح هجوم RPC في Windows. وجّهه إلى مجلد يحتوي على ملفات PE؛ سيعثر على كل ملف يُسجّل خادم RPC، ويستعيد توقيعات طرق NDR لكل واجهة، وارتباطات النقل، وأعلام التسجيل مباشرةً من بنى MIDL المترجمة، ثم يرتّب كل واجهة وفق الوصولية × الخطورة مع إرفاق إيصال حسابي كامل بكل درجة لتتمكن من التحقق من الحساب يدويًا.
الأدوات الحالية لـ RPC تستطيع بسهولة استعادة توقيعات طرق الواجهة وأعلام تسجيلها. لكن ما لا تفعله أيٌّ منها هو الجمع بين الاثنين والإجابة عن السؤال الوحيد الذي يحدد حقًا أين تقضي وقتك: بما أنني أستطيع الوصول إلى هذه الواجهة، وبما أن طرقها تقبل ما تقبله كمدخلات، ما مدى الاستعجال في فحصها مقارنةً بكل شيء آخر على الجهاز؟ هذه الفجوة هي ما تسده هذه الأداة.
يصفّي المجلد المستهدف بحيث لا يقوم Ghidra بتحليل تلقائي إلا للملفات الثنائية التي تسجّل خادم RPC فعلاً (فهي تستورد rpcrt4.dll وتستدعي أحد واجهات برمجة التطبيقات RpcServerRegisterIf\*). في مجلد System32، هذا هو الفرق بين بعد ظهر واحد وأسبوع كامل.
يستخرج، لكل واجهة: UUID، وسلسلة RPC_SERVER_INTERFACE / MIDL_SERVER_INFO، وDispatchTableCount المعتمد، ورقم العملية (opnum) لكل طريقة + اتجاهات المعاملات + أكواد NDR المشفّرة، وأعلام التسجيل (R9)، ووجود استدعاء الأمان ("bouncer")، وواصف الأمان (بأفضل جهد ممكن)، وارتباطات نقطة النهاية / النقل.
يرتّب كل واجهة سليمة على محورين مستقلين ويضربهما في نتيجة مركّبة واحدة من 0 إلى 100، مقسّمة إلى فئات حرجة / عالية / متوسطة / منخفضة.
يشرح نفسه بنفسه: كل درجة تأتي مع سلسلة إيصال تُدرج كل مكوّن والعملية الحسابية التي أنتجت الرقم النهائي.
target dir --(pefile filter) --> only RPC-registering PEs
--(Ghidra headless auto-analysis) --> analyzed program DB
--(extract_rpc_interfaces.py script) --> interfaces + NDR + flags + endpoints
--(two-axis AHP ranking engine) --> ranked interfaces + receipts
--> single JSON report
الاعتماد الصفري على الرموز: أحد الفروق التقنية الجوهرية لهذا المحرك هو أنه يعمل بالكامل دون رموز تصحيح (debug symbols). فمن خلال اجتياز جداول الإرسال (dispatch tables) برمجيًا وتحليل البايت كود الخام لـ NDR (تمثيل بيانات الشبكة) وبنى MIDL المترجمة مباشرةً من الذاكرة، تتجاوز الأداة الحاجة إلى ملفات .pdb من Microsoft أو استعلامات live endpoint mapper. وهذا يضمن أن المحرك يعمل فورًا على ملفات System32 الإنتاجية المجرّدة تمامًا كما تُشحن.
Ghidra 11.x (يستخدم أداة support/analyzeHeadless المرفقة). يتطلب JDK 17+ في PATH.
Python 3.8+ على جانب التشغيل، مع pefile.
يعمل سكربت الاستخراج تحت Jython 2.7 المرفق مع Ghidra - لا توجد استيرادات تابعة لجهات خارجية، ولا شيء يحتاج إلى تثبيت هناك.
الأهداف: ملفات PE لأنظمة Windows x64. التحليل نفسه مستقل عن نظام التشغيل (Ghidra متعدد المنصات)، لذا لا يتعين عليك تشغيله على Windows.
git clone https://github.com/talha-nazeef-ahmed/RPC-Triage
cd RPC-Triage/
python -m pip install -r requirements.txt # just pefile
requirements.txt: pefile>=2023.2.7
كل شيء يعمل عبر orchestrator.py: فهو يقوم بالتصفية والاستيراد والتحليل وتشغيل المُستخرِج نيابةً عنك.
python orchestrator.py \
-t \"C:\Windows\System32\" \
-g \"C:\ghidra_12.1.2_PUBLIC\" \
-s \".\extract_rpc_interfaces.py\" \
-o \".\out\report.json\" \
--stagedir \".\out\staged\" \
--projdir \".\out\ghidra_proj\" \
--projname RPC_Atlas
| flag | meaning |
|---|---|
-t / --target | مجلد الملفات الثنائية المراد فحصها |
-g / --ghidra | مجلد تثبيت Ghidra (الذي يحتوي على support/analyzeHeadless) |
-s / --script | مسار إلى extract_rpc_interfaces.py |
-o / --output | مسار ملف تقرير JSON المطلوب كتابته |
--stagedir | المجلد الذي تُنسخ إليه ملفات RPC الثنائية المُصفّاة (يُحتفظ به) |
--projdir | مجلد مشروع Ghidra الدائم |
--projname | اسم مشروع Ghidra (مثل RPC_Atlas) |
التشغيل الأول مقابل إعادة التشغيل (مهم). التشغيل الأول يقوم بالجزء البطيء: يصفّي، ويستورد الملفات الثنائية المطابقة، ويشغّل التحليل التلقائي الكامل، ثم يضع علامة .analysisComplete في --projdir. أي تشغيل لاحق على نفس المشروع يتخطى الاستيراد/التحليل (-process -noanalysis) ويعيد تشغيل السكربت فقط على البرامج التي تم تحليلها بالفعل. لذا فإن تحليل System32 هو تكلفة لمرة واحدة، والتكرار على المخرجات رخيص. إذا توقف التحليل، لا تُكتب العلامة ويحذف المشروع الجزئي (.rep / .gpr) ويبدأ من جديد.
ملف JSON واحد: قائمة بالملفات الثنائية، كل منها يحتوي على مصفوفة Interfaces. توجد نتيجة تشغيل كاملة على System32 في هذا المستودع على المسار output/FullBatchRun.json، وهي المخرجات الخام غير المنقّحة، حتى ترى بالضبط ما تنتجه الأداة على نطاق واسع. لكل واجهة:
| field | meaning |
|---|---|
CallSite | عنوان استدعاء RpcServerRegisterIf\* |
Tag / TagDesc | فئة جودة البيانات (انظر أدناه) |
Rank | \"{Tier}/{Composite}\"، مثل Critical/91 |
RankDetail | إيصال الدرجات الكامل (انظر أدناه) |
UUID | معرّف الواجهة UUID |
InterfaceAddress / DispatchAddress | عناوين البنى المستعادة |
FunctionsCount | عدد الطرق المخزّن المعتمد؛ قد يحمل (FLAG: Walked X != Stored Y) |
Endpoints | ارتباطات النقل / نقطة النهاية |
Security | HasBouncer, SecurityDescriptor, SecureOnly, LocalCallOnly |
Methods | قائمة المعاملات لكل رقم عملية (opnum) مع أكواد NDR المشفّرة |
الوسوم؛ بوابة سلامة البيانات:
Clean: تم استرجاعه بشكل سليم؛ وتم ترتيبه بشكل طبيعي.
Needs-Review: تم ترتيبه، لكن اجتياز جدول الإرسال تجاوز العدد المخزّن (عادةً كتلة thunk زائدة). الدرجة حقيقية لكنها تحمل [Provisional]؛ تحقق من عدد الطرق قبل الاستشهاد بها.
Diagnostics / Diagnostics (2): الصف هو نتاج استخراج (سلسلة ASCII تم التقاطها خطأً كـ UUID و/أو مؤشر MIDL تالف). غير مُرتّب (Rank: N/A). هذه يتم الإبقاء عليها عمدًا: فهي تُبلغ عن سلامة الأداة، وليست سطح هجوم.
الملاحظة FLAG: Walked X != Stored Y التالية: إن DispatchTableCount المخزّن هو المعتمد وهو ما تعتمد عليه كل درجة. أما العدد المَمرور (walked) فهو فحص تبادلي مستقل لقابلية التنفيذ؛ عندما يختلف الرقمان (عادةً بفارق 2x واضح) تُوسَم الواجهة بـ Needs-Review لتعرف أنها تستحق نظرة فاحصة. وهي لا تغيّر أي درجة بصمت أبدًا.
Moderate/35 | Gate:35 [ncacn_np:41, MultiEndpointBonus:15, HasBouncer:-46, BouncerIsNotCaching:25] | Surface:100 [HasBogusStruct:1x(opnums 5):[in]:61, HasCallerSizedBuffer:5x(opnums 0,6,11):[in]:49, InPtrs:6:18, Count:12:6 -> raw:134 [capped to 100] * 1.0 -> 100] | (35 * 100) / 100 = 35 [Provisional]
اقرأه من اليسار إلى اليمين:
Moderate/35: الفئة والدرجة المركّبة.
Gate:35 [...]: محور الوصولية. يبدأ من أساس النقل (ncacn_np:41، أي pipe مسمّى)، ثم يدرج كل مُعدِّل تسجيل مع مساهمته الموقّعة: MultiEndpointBonus:15 (مُسجَّل على عدة وسائل نقل)، وHasBouncer:-46 (وجود استدعاء أمان يخفض الوصولية)، وBouncerIsNotCaching:+25. يُجمَع ثم يُقيَّد في النطاق [5,100] -> 35.
Surface:100 [...]: محور الخطورة. كل إشارة اشتعلت تكون بصيغة Name:count x(opnums):direction:weight، مثل HasBogusStruct التي اشتعلت على معامل واحد (opnum 5) ووزنها 61. ثم مساهمة معتمدة على العدد: InPtrs:6:18 = 6 مؤشرات داخلة يتحكم بها المتصل تساهم بـ +18، وCount:12:6 = 12 طريقة أضافت +6. raw:134 هو المجموع قبل السقف، [capped to 100]، * 1.0 هو مضاعِف الثقة (ينخفض إلى 0.5 عندما تكون التوقيعات غير مؤكدة)؛ فيكون Surface النهائي 100.
(35 * 100) / 100 = 35 [Provisional]: الدرجة المركّبة = Gate x Surface / 100. يتم ضرب الوصولية والخطورة، لا جمعهما، لأن الخطورة لا تهم إلا إذا استطعت الوصول إليها: واجهة بالغة الخطورة لا يمكنك لمسها يجب ألا تطفو إلى القمة. علامة [Provisional] في النهاية تحذّر من أن اجتياز الذاكرة الديناميكي لم يطابق عدد الطرق المخزّن بشكل طفيف، ما يعني أن على إنسان التحقق من الحدود.
مثال ثانٍ يوضح التقييد وخصم انخفاض الثقة: