
كاشف روتكيت لينكس يعتمد على eBPF باستخدام تحليل متعدد القنوات عبر الرؤى (sched_switch, NMI, /proc) لكشف DKOM والتلاعب بنقاط التتبع وإخفاء العمليات مع التحقق من التكامل على مستوى العتاد.
تحليل سلامة العمليات النظامية ورؤية متقاطعة
"سأغني، لذا أشرق بشدة، SPiCa..."
SPiCa هو كاشف روتكيت لنظام لينكس يعتمد على eBPF ومكتوب بلغة رست. الاسم مستوحى من أغنية Hatsune Miku SPiCa والنجم الذي تشير إليه — سبيكا (ألفا العذراء)، ألمع نقطة في برج العذراء. ما يبدو كنجم واحد بالعين المجردة هو في الواقع ثنائي طيفي: نجمان في مدار متبادل، لا يمكن تمييزهما كجسمين منفصلين دون قياس أطيافهما. يطبق SPiCa نفس المبدأ على مراقبة النواة: عدة قنوات مستقلة تقيس نفس حالة النواة من آليات متميزة فيزيائيًا، والروتكيت الذي يكتم إحداها يفضح بواسطة الأخرى.
إخلاء مسؤولية: تم إنشاء أو إعادة هيكلة أجزاء كبيرة من قاعدة الشيفرات هذه بمساعدة GLM. تم تطبيق اختبارات دقيقة وتصميم تكراري، ولكن يُرجى مراجعة الشيفرة من حيث الأمان والأداء قبل الاستخدام الإنتاجي.
صُمم SPiCa لهزيمة الخصم المقيد بـ eBPF — مهاجم يتمتع بصلاحيات مرتفعة (CAP_BPF أو CAP_SYS_ADMIN) ويقوم بتحميل برنامج eBPF ذو صلاحيات في النواة. هذا الخصم أضعف جوهريًا من روتكيت LKM لأن مدقق BPF يفرض قيودًا صارمة:
| القيد | التأثير على الخصم |
|---|---|
| الحلقات المحدودة (~1 مليون تعليمة لكل برنامج) | لا يمكنه مسح كل ذاكرة النواة |
| سلامة المؤشرات (الإشارة المباشرة تتطلب هدفًا صحيحًا ومحدودًا) | لا يمكنه متابعة مؤشرات عشوائية |
| الوظائف المساعدة المقيدة (قائمة مسموح بها حسب نوع البرنامج) | لا يمكنه استدعاء kmalloc أو vmalloc أو أي وظيفة تخصيص نواة |
لا كتابة عشوائية للنواة (bpf_probe_write_kernel مقيد بشدة) | لا يمكنه تعديل task_struct أو مصفوفات وظائف نقاط التتبع أو شيفرة برنامج BPF |
| الوصول إلى الخرائط مقيد بالمراجع المحملة | لا يمكنه الوصول إلى خرائط تنتمي لبرامج أخرى (إلا إذا تم تعدادها بواسطة ID — انظر §7) |
مع حظر LKMs بواسطة بوابة LSM بعد الإقلاع، يبقى هذا الخصم المقيد هو التهديد الواقعي المتبقي. آليات SPiCa المضادة للتهرب معايرة لهذا التهديد — كل دفاع صريح بشأن ما يغطيه وما لا يغطيه.
init_module. SPiCa يرفع المستوى الأدنى بحجب ناقل LKM الأسهل ولكنه لا يحد من الخصم عند المستوى الأعلى.SPiCa هو طبقة الملاذ الأخير في مجموعة دفاع في العمق، وليس بديلاً عن الطبقات التي تعلوه.
يشغل SPiCa أربعة برامج eBPF مرتبطة بخطافات النواة، بالإضافة إلى محرك كشف في مساحة المستخدم يربط ناتجها عرضيًا مع رؤية النظام لنفسه (/proc).
| القناة | الآلية | ما يكتمها | تكلفة الكتم |
|---|---|---|---|
/proc | تعداد دليل procfs | DKOM (فصل العملية عن قائمة المهام) | منخفضة — تقنية روتكيت معروفة جيدًا |
sched\_switch | نقطة تتبع eBPF على المجدول | فصل البرنامج عن مصفوفة الوظائف؛ تصفية السياق | متوسطة — تتطلب كتابة نواة على مصفوفة الوظائف |
| NMI | مقاطعة عداد الأداء العتادي → حدث أداء eBPF | ربط معالج IDT/NMI | شبه مؤكد يسبب ذعر النواة — مقاطعة عتادية غير قابلة للإخفاء |
الخاصية البنيوية الرئيسية: لا يمكن للروتكيت كتم جميع القنوات الثلاث في وقت واحد دون أن يصبح الكتم نفسه قابلاً للكشف أو مزعزعًا للاستقرار. كتم NMI يتطلب تعديل IDT (جدول واصف المقاطعات)، مما يسبب ذعرًا في معظم النوى. هذه هي "القنبلة الحية" — الطريق الوحيد للعمى الكامل للمهاجم هو طريق من المحتمل أن ينهار النظام.
sched sees it, /proc doesn't → DKOM NMI finds sched's heartbeat stopped → TAMPER sched goes globally silent, /proc != empty → SILENT
كل فئة كشف هي حكم تفاضلي: تباين بين قناتين أو أكثر. محرك الكشف هو دالة خالصة فوق سجل النظام + لقطة /proc + طوابع زمنية للقنوات — لا إدخال/إخراج، لا آثار جانبية، قابلة للاختبار بالكامل بالوحدات.
### إعادة تصميم NMI: من المراقبة إلى التكامل
في التصميم الأصلي، كانت NMI قناة مراقبة عمليات ثانية تقوم بأخذ عينات من وحدة المعالجة المركزية والإبلاغ عن المهمة الجاري تشغيلها. كان هذا زائدًا عن الحاجة: `sched_switch` تراقب بالفعل جدولة العمليات، وكانت NMI تأخذ نفس البيانات عبر آلية مختلفة. كلف هذا التكرار ما يقرب من 1000+ حدث/ثانية/CPU في الحلقة العازلة من بيانات العملية والتي تؤكد في 99.999% من الوقت "نعم، المجدول يفعل ما يفعله المجدول."
في البنية المعاد تصميمها، **تم إعادة استخدام NMI من مراقبة العمليات إلى التحقق من سلامة نقاط التتبع (tracepoint).** لم تعد تُبلغ عن العملية التي تعمل على وحدة المعالجة المركزية. بدلاً من ذلك، تتحقق من أن `sched_switch` تعمل بالفعل عن طريق قراءة إشارة نبض مشتركة في قسم `.bss`. هذا:
1. يلغي حوالي 99% من حركة مرور الحلقة العازلة الخاصة بـ NMI (أحداث شبه صفرية في الحالة المستقرة)
2. يكتشف بشكل مباشر انفصال نقاط التتبع، وقمعها، وفشل إرفاق BTF (خلل BTF الأصلي — انظر [§11](#11-the-btf-bug-incident))
3. يعمل من مقاطعة أجهزة، خارج مسار إرسال نقاط التتبع — محصن ضد `bpf_override_return`، واعتراض kprobe، والتلاعب بمصفوفة الدوال
4. يقرأ المتغيرات العامة في `.bss` عبر الوصول المباشر للذاكرة، وليس مساعدات BPF — محصن ضد `fmod_ret` على الدوال المساعدة
---
## 3. قناة مراقبة sched\_switch
برنامج eBPF ملحق بنقطة التتبع `sched_switch` يتم تشغيله في كل مرة يقوم فيها النواة بجدولة عملية على وحدة معالجة مركزية. يقرأ معرف العملية (PID) واسم الأمر (comm) الخاصين بالمهمة الواردة مباشرة من وسيطات نقطة التتبع باستخدام قراءات تقليدية (غير BTF) ذات إزاحة ثابتة:```
ctx.read_at::<u32>(56) → next_pid
ctx.read_at::<[u8;16]>(40) → next_comm
متعمد عدم استخدام BTF/CO-RE. تخطيط وسيطات نقاط التتبع ثابت عبر إصدارات النواة (فهي جزء من واجهة ABI لنقاط التتبع). استخدام الإزاحات المبرمجة يجنب هشاشة إصدارات النواة التي قد تسببها تنقل بنى struct عبر BTF. هذا اختيار تصميمي متعمد موثق في §11.
في كل استدعاء، يقوم البرنامج بما يلي:
next_pid و next_comm من سياق نقطة التتبعProcessInfo باستخدام XOR مع BASE_KEYsc_schedbpf_ktime_get_ns() إلى المتغير العام .bss SCHED_HEARTBEAT — النبضة التي يراقبها مدقق سلامة NMIيتجنب البرنامج عمداً استخدام bpf_get_current_pid_tgid() في هذا السياق. في وقت sched_switch، تكون "المهمة الحالية" هي المهمة الخارجة، وليست الواردة. توفر وسيطات نقطة التتبع هوية العملية الصحيحة (الواردة).
يستخدم محرك الكشف قاعدة زمنية أحادية رتيبة: نانوثانية منذ بدء تشغيل عملية SPiCa (Instant::now() منذ دخول run_detection()). عندما يصل حدث جدولة، يخزن معالج الحدث now المقدم من المتصل (نانوثانية محلية للعملية)، وليس قيمة bpf_ktime_get_ns() من eBPF المضمنة في الحدث. هذا يتجنب عدم تطابق القاعدة الزمنية الذي قد يحدث إذا اختلط زمن إقلاع النواة بالزمن المحلي للعملية — وهو خطأ كان موجوداً في الإصدارات السابقة وتسبب في فشل جميع مسندات البقاء بصمت.