
محرك تحليل ساكن 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
التشغيل الأول مقابل إعادة التشغيل (مهم). التشغيل الأول يقوم بالجزء البطيء: يصفّي، ويستورد الملفات الثنائية المطابقة، ويشغّل التحليل التلقائي الكامل، ثم يضع علامة .analysisComplete في --projdir. أي تشغيل لاحق على نفس المشروع يتخطى الاستيراد/التحليل (-process -noanalysis) ويعيد تشغيل السكربت فقط على البرامج التي تم تحليلها بالفعل. لذا فإن تحليل System32 هو تكلفة لمرة واحدة، والتكرار على المخرجات رخيص. إذا توقف التحليل، لا تُكتب العلامة ويحذف المشروع الجزئي (.rep / .gpr) ويبدأ من جديد.
ملف JSON واحد: قائمة بالملفات الثنائية، كل منها يحتوي على مصفوفة Interfaces. توجد نتيجة تشغيل كاملة على System32 في هذا المستودع على المسار output/FullBatchRun.json، وهي المخرجات الخام غير المنقّحة، حتى ترى بالضبط ما تنتجه الأداة على نطاق واسع. لكل واجهة:
الوسوم؛ بوابة سلامة البيانات:
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 هو المجموع قبل السقف، ، هو مضاعِف الثقة (ينخفض إلى 0.5 عندما تكون التوقيعات غير مؤكدة)؛ فيكون Surface النهائي .
مثال ثانٍ يوضح التقييد وخصم انخفاض الثقة:
Low/3 | Gate:5 [Dynamic / epmapper:29, LocalCallOnly:-100, SecureOnly:-65, HasBouncer:-46, BouncerIsNotCaching:25] | Surface:50 [... -> raw:140 [capped to 100] * 0.5 -> 50] | (5 * 50) / 100 = 3
LocalCallOnly:-100 وحده يدفع البوابة تحت الصفر فيتم تقييدها عند الحد الأدنى 5؛ كانت التوقيعات غير مؤكدة لذا تم تخفيض Surface إلى النصف (* 0.5)؛ النتيجة المركّبة تصل إلى 3. مساحة إدخال خطيرة، لكن يتعذر الوصول إليها فعليًا -> تُخفَّض الأولوية بشكل صحيح.
Critical >= 75، وHigh >= 50، وModerate >= 25، وLow فيما عدا ذلك (الواجهة التي ليس لديها سطح مستعاد تكون Low بغض النظر عن البوابة). تقع العتبات على الدرجة المركّبة؛ النموذج الذي يقف خلفها موجود في docs/Surface_Scoring_Methadology.md.
جميع جداول الأوزان الثلاثة (أساسات النقل، ومُعدِّلات البوابة، وإشارات السطح) مشتقة باستخدام عملية التحليل الهرمي (Analytic Hierarchy Process)؛ مقارنات زوجية، وأوزان المتوسط الهندسي، ونسبة اتساق مُقاسة. الاشتقاق الكامل والمصفوفات وأرقام الاتساق والملاحظات المحسوبة يدويًا موجودة في docs/Surface_Scoring_Methadology.md.
حتى لا يكون الاستخراج مجرد تأكيد ذاتي، تمت مقارنة الواجهات التي تستعيدها هذه الأداة مع مُستخرِج IDL مستقل ومعروف لـ RPC (محلل RpcServer في NtObjectManager لجيمس فورشو) يعمل على نفس الملفات الثنائية. توجد نسخ مرجعية لـ lsass وsamsrv وwinlogon في validation/، والشرح الكامل في validation/VALIDATION.md. قارن أيًا منها مع الملف الثنائي المطابق في output/FullBatchRun.json: معرّفات UUID للواجهات، وأعداد الطرق/الأوبكودات، واتجاهات المعاملات لكل معامل تتطابق (على سبيل المثال، واجهة 12E65DD8-... في winlogon تظهر خمس طرق، Proc0-Proc4، في كليهما). الأداة المرجعية تتوقف عند استعادة الـ IDL؛ هذه الأداة تأخذ نفس السطح المستعاد وتضيف فوقه ترتيب الوصولية × الخطورة. لا توجد أي صلة بهذا المشروع، إنه يُستخدم فقط كفحص حقيقة مستقل.
ثابت فقط. لا يتم تنفيذ أي شيء. تُستنتج الوصولية من التسجيل، لا من وقت التشغيل.
درجات Needs-Review مؤقتة حتى يتم فحص عدد الطرق بصريًا.
هذه الأداة جزء من أبحاثي الجارية حول ALPC/RPC والدواخل الداخلية لنظام Windows. سأنشر المزيد من النتائج وسأطلق أدوات مساعدة في المستقبل القريب. إذا وجدت هذا مفيدًا، فكّر في متابعتي على GitHub أو على حساباتي الاجتماعية أدناه لتصلك إشعارات بالإصدارات القادمة.
أداة فرز / رسم خرائط ثابتة لأبحاث الثغرات على الأنظمة التي تملكها. إنها تُبلغ عن سطح الهجوم، لا عن الثغرات. أي شيء تعثر عليه لاحقًا في الواجهات التي تسلط الضوء عليها يجب أن يمر عبر الإفصاح المنسّق (MSRC) قبل نشر أي تفاصيل علنية.
MIT
| 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) |
| 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 المشفّرة |
[capped to 100]* 1.0(35 * 100) / 100 = 35 [Provisional]: الدرجة المركّبة = Gate x Surface / 100. يتم ضرب الوصولية والخطورة، لا جمعهما، لأن الخطورة لا تهم إلا إذا استطعت الوصول إليها: واجهة بالغة الخطورة لا يمكنك لمسها يجب ألا تطفو إلى القمة. علامة [Provisional] في النهاية تحذّر من أن اجتياز الذاكرة الديناميكي لم يطابق عدد الطرق المخزّن بشكل طفيف، ما يعني أن على إنسان التحقق من الحدود.