
eBPF-आधारित Linux रूटकिट डिटेक्टर जो मल्टी-चैनल क्रॉस-व्यू विश्लेषण (sched_switch, NMI, /proc) का उपयोग करके DKOM, ट्रेसपॉइंट छेड़छाड़, और प्रक्रिया छिपाने का पता लगाता है, जिसमें हार्डवेयर-स्तरीय अखंडता सत्यापन शामिल है।
सिस्टम प्रोसेस इंटीग्रिटी और क्रॉस-व्यू एनालिसिस
"मैं गाने जा रही हूँ, तो चमको, SPiCa..."
SPiCa Rust में लिखा गया एक eBPF-आधारित Linux रूटकिट डिटेक्टर है। यह नाम Hatsune Miku गीत SPiCa और उसमें संदर्भित तारे — Spica (Alpha Virginis), जो कन्या राशि का सबसे चमकीला बिंदु है — से लिया गया है। नंगी आँखों से जो एक तारा दिखता है, वह वास्तव में एक स्पेक्ट्रोस्कोपिक बाइनरी है: दो तारे पारस्परिक कक्षा में, जो बिना उनके स्पेक्ट्रा मापे अलग-अलग वस्तुओं के रूप में भिन्न नहीं किए जा सकते। SPiCa उसी सिद्धांत को कर्नेल अवलोकन पर लागू करता है: कई स्वतंत्र चैनल भौतिक रूप से भिन्न तंत्रों से एक ही कर्नेल स्थिति को मापते हैं, और एक रूटकिट जो एक को दबाता है, वह दूसरों द्वारा उजागर हो जाता है।
अस्वीकरण: इस कोडबेस के महत्वपूर्ण भाग GLM सहायता से उत्पन्न या रिफैक्टर किए गए थे। कठोर परीक्षण और पुनरावृत्त डिज़ाइन लागू किया गया था, लेकिन उत्पादन उपयोग से पहले सुरक्षा और प्रदर्शन के लिए कोड की समीक्षा करें।
SPiCa को eBPF-सीमित प्रतिद्वंद्वी को हराने के लिए डिज़ाइन किया गया है — एक हमलावर जिसके पास उन्नत विशेषाधिकार (CAP_BPF या CAP_SYS_ADMIN) हैं और वह कर्नेल में एक विशेषाधिकार प्राप्त eBPF प्रोग्राम लोड करता है। यह प्रतिद्वंद्वी LKM रूटकिट की तुलना में मौलिक रूप से कमज़ोर है क्योंकि BPF वेरिफायर कठोर बाधाएं लगाता है:
| बाधा | प्रतिद्वंद्वी पर प्रभाव |
|---|---|
| बाउंडेड लूप (~1M निर्देश प्रति प्रोग्राम) | सभी कर्नेल मेमोरी को स्कैन नहीं कर सकता |
| पॉइंटर सुरक्षा (प्रत्यक्ष डीरेफरेंस के लिए प्रमाणित रूप से मान्य, बाउंडेड लक्ष्य आवश्यक) | मनमाने पॉइंटर्स का अनुसरण नहीं कर सकता |
| प्रतिबंधित हेल्पर्स (प्रति-प्रोग्राम-प्रकार अनुमति सूची) | kmalloc, vmalloc, या कोई कर्नेल आवंटन फ़ंक्शन नहीं बुला सकता |
कोई मनमाना कर्नेल लेखन नहीं (bpf_probe_write_kernel अत्यधिक प्रतिबंधित) | task_struct, ट्रेसपॉइंट funcs ऐरे, या BPF प्रोग्राम बाइटकोड को संशोधित नहीं कर सकता |
| मैप एक्सेस लोड किए गए संदर्भों तक सीमित | अन्य प्रोग्रामों से संबंधित मैप तक नहीं पहुंच सकता (जब तक कि ID द्वारा गणना न की जाए — §7 देखें) |
बूट के बाद LSM गेट द्वारा LKM को अवरुद्ध करने के साथ, यह सीमित प्रतिद्वंद्वी शेष यथार्थवादी खतरा है। SPiCa की एंटी-एवेज़न मशीनरी इस खतरे के लिए कैलिब्रेट की गई है — हर बचाव ईमानदार है कि वह क्या कवर करता है और क्या नहीं।
init_module के बिना मनमाना कर्नेल लेखन देता है। SPiCa आसान LKM वेक्टर को अवरुद्ध करके फर्श उठाता है, लेकिन प्रतिद्वंद्वी को सीमित नहीं करता।SPiCa एक गहराई में रक्षा स्टैक में अंतिम-उपाय परत है, न कि ऊपर की परतों का विकल्प।
SPiCa कर्नेल हुक से जुड़े चार eBPF प्रोग्राम चलाता है, साथ ही एक उपयोगकर्ता-स्थान डिटेक्शन इंजन जो उनके आउटपुट को सिस्टम के स्वयं के दृश्य (/proc) के साथ क्रॉस-सहसंबंधित करता है।
| चैनल | तंत्र | इसे क्या दबाता है | दमन की लागत |
|---|---|---|---|
/proc | procfs निर्देशिका गणना | DKOM (प्रक्रिया को कार्य सूची से अलिंक करें) | कम — अच्छी तरह से समझी जाने वाली रूटकिट तकनीक |
sched_switch | शेड्यूलर पर eBPF ट्रेसपॉइंट | funcs ऐरे से प्रोग्राम को डिटैच करें; संदर्भ फ़िल्टर करें | मध्यम — funcs ऐरे में कर्नेल लेखन की आवश्यकता |
| NMI | हार्डवेयर perf काउंटर इंटरप्ट → eBPF perf इवेंट | 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 स्नैपशॉट + चैनल टाइमस्टैम्प पर एक शुद्ध फ़ंक्शन है — कोई I/O नहीं, कोई दुष्प्रभाव नहीं, पूरी तरह से यूनिट-परीक्षण योग्य।
### The NMI redesign: from observation to integrity
मूल डिज़ाइन में, NMI एक दूसरा प्रक्रिया-अवलोकन चैनल था जो CPU का नमूना लेता था और रिपोर्ट करता था कि कौन सा कार्य चल रहा है। यह अनावश्यक था: sched\_switch पहले से ही शेड्यूलिंग का अवलोकन करता है, और NMI एक अलग तंत्र के माध्यम से समान डेटा का नमूना लेता था। अनावश्यकता की लागत ~1000+ रिंग-बफ़र ईवेंट/सेकंड/CPU प्रक्रिया डेटा की थी जो 99.999% समय पुष्टि करता था "हाँ, शेड्यूलर वही कर रहा है जो शेड्यूलर करता है।"
पुनर्डिज़ाइन की गई वास्तुकला में, **NMI को प्रक्रिया अवलोकन से ट्रेसपॉइंट अखंडता सत्यापन के लिए पुन: उपयोग किया गया है।** यह अब रिपोर्ट नहीं करता कि CPU पर कौन सी प्रक्रिया है। इसके बजाय, यह एक साझा `.bss` हार्टबीट पढ़कर सत्यापित करता है कि `sched\_switch` वास्तव में चल रहा है। यह:
1. NMI रिंग-बफ़र ट्रैफ़िक का ~99% समाप्त करता है (लगभग शून्य स्थिर-स्थिति ईवेंट)
2. सीधे ट्रेसपॉइंट अलगाव, दमन, और BTF/अटैच विफलताओं का पता लगाता है (मूल BTF बग — [§11](#11-the-btf-bug-incident) देखें)
3. एक हार्डवेयर इंटरप्ट से चलता है, ट्रेसपॉइंट डिस्पैच पथ के बाहर — `bpf_override_return`, kprobe इंटरसेप्शन, और funcs-array हेरफेर से प्रतिरक्षित
4. `.bss` ग्लोबल्स को BPF हेल्पर्स के बजाय प्रत्यक्ष मेमोरी एक्सेस के माध्यम से पढ़ता है — हेल्पर फ़ंक्शंस पर `fmod_ret` से प्रतिरक्षित
---
## 3. The sched\_switch Observation Channel
`sched_switch` ट्रेसपॉइंट से जुड़ा एक eBPF प्रोग्राम हर बार फायर करता है जब कर्नेल CPU पर एक प्रक्रिया शेड्यूल करता है। यह पारंपरिक (गैर-BTF) निश्चित-ऑफ़सेट रीड का उपयोग करके ट्रेसपॉइंट तर्कों से सीधे आने वाले कार्य के PID और comm को पढ़ता है:```
ctx.read_at::<u32>(56) → next_pid
ctx.read_at::<[u8;16]>(40) → next_comm
जानबूझकर BTF/CO-RE नहीं। ट्रेसपॉइंट आर्गुमेंट लेआउट कर्नेल संस्करणों में स्थिर है (यह ट्रेसपॉइंट ABI का हिस्सा है)। हार्डकोडेड ऑफ़सेट का उपयोग BTF-रिज़ॉल्व्ड स्ट्रक्ट नेविगेशन की कर्नेल-संस्करण भंगुरता से बचाता है। यह एक जानबूझकर डिज़ाइन विकल्प है जिसे §11 में दस्तावेज़ित किया गया है।
प्रत्येक आह्वान पर, प्रोग्राम:
next_pid और next_comm पढ़ता हैProcessInfo स्ट्रक्ट को BASE_KEY से XOR-ऑबस्क्यूरेट करता हैsc_sched रिंग बफ़र में सबमिट करता है.bss ग्लोबल SCHED_HEARTBEAT में bpf_ktime_get_ns() लिखता है — वह हार्टबीट जिसकी NMI इंटीग्रिटी चेकर निगरानी करता है