
एक बहुत बहुत बहुत बहुत बहुत बहुत बहुत लंबा व्यवधान
एक बहुत बहुत बहुत बहुत बहुत बहुत बहुत लंबे इंटरप्ट के साथ सिस्टम मैनेजमेंट मोड का शोषण।
यह पता चला है कि आप SMM — हर x86 CPU की पृष्ठभूमि में अदृश्य रूप से चलने वाला सुरक्षित, अति-विशेषाधिकार प्राप्त निष्पादन वातावरण — को केवल एक अत्यंत लंबे समय तक चलने वाले मशीन निर्देश से तोड़ सकते हैं।
SMM के लिए आवश्यक है कि सभी कोर एक ही समय में या तो SMM में हों या SMM से बाहर हों। इसके बिना इसका सुरक्षा मॉडल काम नहीं करता — जब एक थ्रेड SMM में प्रवेश करता है, तो यह बाकी सभी को भी प्रवेश करने के लिए मजबूर करता है।
इसे तोड़ने के लिए, हमें बस किसी ऐसे व्यक्ति की आवश्यकता है जो यह नोटिस करने में बहुत व्यस्त हो कि उन्हें SMM में शामिल होना है।
यह कुछ इस तरह काम करता है:
core 0 - एक लंबा निर्देश शुरू करें
|
|
|
core 1 - core 0 को smm में आमंत्रित करें
|
|
|
core 1 - smm में प्रवेश करें
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - core 0 की प्रतीक्षा करें
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core 1 - हार मान लें
core 1 - गुप्त smm कार्य करें
core 1 - smm समाप्त करें
|
|
|
core 0 - smm में शामिल हों
इस बिंदु पर, core 1 SMM से बाहर है जबकि core 0 अंदर है, जिससे core 1 core 0 पर हमला कर सकता है। यहाँ पेच है: इसके काम करने के लिए हमें एक बहुत, बहुत, बहुत लंबे निर्देश की आवश्यकता है — किसी भी निर्देश से अधिक लंबा जो कभी लेने वाला था। आधुनिक CPU पर अधिकांश मशीन निर्देश तेज़ होते हैं: add 1 चक्र लेता है। core 1 को core 0 पर प्रतीक्षा छोड़ने के लिए, हमें core 0 पर एक ऐसे निर्देश की आवश्यकता है जो लगभग 4,000,000,000 चक्र ले — दीवार-घड़ी के समय में 1 सेकंड से अधिक।
x86 फर्मवेयर निम्नलिखित कोड चलाता है जब एक CPU कोर SMM में प्रवेश करता है:
for (Timer = StartSyncTimer ();
!IsSyncTimerTimeout (Timer, mTimeoutTicker) && SyncNeeded;
)
{
mSmmMpSyncData->AllApArrivedWithException = AllCpusInSmmExceptBlockedDisabled ();
if (mSmmMpSyncData->AllApArrivedWithException) {
break;
}
CpuPause ();
}
कोड सभी कोर के SMM में प्रवेश करने की प्रतीक्षा करता है, या 1 सेकंड तक, जो भी पहले हो। एक कोर को SMM कोड निष्पादित करने के लिए जबकि दूसरा कोर SMM के बाहर निष्पादित रहता है, हमें उस बाहरी कोर को पूरे सेकंड के लिए अबाधित रखना होगा — एक SMI एक निर्देश सीमा पर लिया जाता है, इसलिए दो निर्देशों के बीच कोई भी अंतर लंबित SMI को कोर को SMM में खींचने देता है। इसलिए देरी एक एकल निर्देश होनी चाहिए: एक अबाधित ऑप जो एक-सेकंड रेंडेज़वस से अधिक समय तक चलता है।
निषिद्ध 1-सेकंड निर्देश तक पहुंचने के कई तरीके हैं, और सटीक दृष्टिकोण प्लेटफ़ॉर्म-दर-प्लेटफ़ॉर्म भिन्न होगा। लेकिन, मोटे तौर पर: एक उच्च-विलंबता MMIO पता खोजें, और फिर CPU को उससे यथासंभव धीरे-धीरे पढ़ने के लिए मनाएं — एक अप्रलेखित क्षेत्र का दुरुपयोग करें जो पढ़ने का उत्तर धीरे-धीरे देता है, ISA द्वारा दी जाने वाली सबसे चौड़ी लोड का उपयोग करें ताकि एक ही निर्देश में अधिक से अधिक बाइट्स उस पार ले जा सकें, और अन्य कोर को उसी बस के लिए प्रतिस्पर्धा करने दें ताकि इसे और धीमा किया जा सके। एक पढ़ना, एक निर्देश, और CPU उसे लगभग एक सेकंड के लिए पकड़े रहने में फंस जाता है।
प्रदान किया गया प्रूफ-ऑफ-कॉन्सेप्ट एक Zen 3 Ryzen 7 5800H के लिए ट्यून किया गया है, जहाँ 0xfcc68860 पर धीमे MMIO से एक चौड़ी xmm लोड सभी-कोर रेंडेज़वस को तोड़ने के लिए पर्याप्त देर तक रुकती है:
mov $0xfcc68860, %rsi ; लक्षित MMIO पता
vmovdqu (%rsi), %xmm0 ; बहुत, बहुत लंबी लोड
PoC दो कोरों को एक-दूसरे के खिलाफ खड़ा करके इसका शोषण करता है। एक कोर को लंबे निर्देश द्वारा SMM के बाहर रखा जाता है — बहुत धीमी लोड पर एक तंग लूप:
/* पीड़ित कोर: ~1-सेकंड लोड पर स्पिन करें, SMI का उत्तर देने में बहुत व्यस्त */
for (;;)
asm volatile ("vmovdqu (%0), %%xmm0" :: "r"(mmio) : "xmm0");
इस बीच एक अन्य कोर प्रति-कोर SMI काउंटर तैयार करता है:
#define MSR_PERF_CTL0 0xc0010200 /* AMD कोर परफ इवेंट-सेलेक्ट MSR */
#define MSR_PERF_CTR0 0xc0010201 /* युग्मित 48-बिट काउंटर */
for (int cpu = 0; cpu < ACTIVE_CPUS; cpu++) {
msr_write(cpu, MSR_PERF_CTL0, 0x43002b); /* EN | OS | USR | इवेंट 0x2b */
msr_write(cpu, MSR_PERF_CTR0, 0); /* गिनती शून्य करें */
}
फिर SMI का तूफान दागता है:
asm volatile ("outb %%al, $0xb2" :: "a"(0)); /* पोर्ट 0xb2 किक करें -> #SMI */
और हर कोर की गिनती वापस पढ़ता है:
/* ...तूफान दागें, फिर हर कोर की गिनती वापस पढ़ें... */
uint64_t delta = smi_max - smi_min;
if (delta)
puts("!!! एक कोर SMM के बाहर चला");
यदि गिनती अलग हो जाती है, तो इसका मतलब है कि एक कोर SMM के बाहर चलता रहा जबकि अन्य अंदर खींच लिए गए — यह उन SMIs को चूक गया जिन्हें बाकी ने सेवा दी।
इसे बेहतर ढंग से चित्रित करने के लिए, हम प्रूफ-ऑफ-कॉन्सेप्ट को एक अनावश्यक रूप से चमकदार और पूरी तरह से बेकार GUI के पीछे चला सकते हैं, प्रत्येक कोर पर SMI काउंटर ट्रैक करते हुए, उन्हें पूर्ण लॉकस्टेप में निष्पादित होते देखने के लिए जब तक कि एक अत्यधिक विलंबित कोर उनकी आवश्यक सिंक्रनाइज़ेशन को नहीं तोड़ता:

SMM की एकमात्र गारंटी — कि जब यह चलता है तो कुछ और नहीं चलता — एक एकल बेतुके लंबे निर्देश के तहत टूट जाती है।
SMM की सुरक्षा एक सरल धारणा पर निर्भर करती है: जब यह चलता है, तो कुछ और नहीं चलता।
वहाँ हैं 100+ SMM TOCTOU CVEs बाहर वहाँ: एक SMM हैंडलर जाँचता है एक मान साझा मेमोरी में, फिर उसका उपयोग करता है। शोषण के लिए आपको बस जाँच और उपयोग के बीच उस मान को फिर से लिखना है, और आप SMM के अंदर हैं। लेकिन ये समस्याएँ जंगल में निष्क्रिय और बड़े पैमाने पर अनपैच्ड पड़ी हैं, एक धारणा के कारण: शोषण के लिए कुछ ऐसा चाहिए जो SMM निष्पादित होने के दौरान साझा मेमोरी को संशोधित करे, और SMM रेंडेज़वस के कारण कोई CPU कोर SMM के बाहर नहीं है जो हमला शुरू कर सके। अंदर जाने का एकमात्र रास्ता CPU की पीठ के पीछे लिखने वाला DMA-सक्षम परिधीय था — भौतिक पहुंच, एक दुर्भावनापूर्ण डिवाइस — इसलिए पूरी श्रेणी को हार्डवेयर समस्या के रूप में खारिज कर दिया गया है।
SMI डीसिंक्रनाइज़ेशन उस पूर्वापेक्षा को हटा देता है जिसने प्लेटफ़ॉर्म को सुरक्षित रखा था: एक बाहरी कोर, बिना किसी भौतिक पहुंच या हार्डवेयर के, अब SMM निष्पादित होने के दौरान चल सकता है — और निष्क्रिय CVEs सॉफ़्टवेयर से शोषण योग्य हो जाते हैं।
शायद कोई नहीं हैं, जो इस समस्या को पारंपरिक SMM मुद्दों से कुछ अधिक दिलचस्प बनाता है। टाइमआउट रखें, और रेंडेज़वस आसानी से टूट जाता है। टाइमआउट हटाएँ, और एक वैध रूप से फंसा कोर पहले SMI पर प्लेटफ़ॉर्म को लटका देता है। टाइमआउट बढ़ाएँ, और आप कई-कोर प्लेटफ़ॉर्म पर प्रदर्शन मार देते हैं जो हर SMM प्रवेश पर सभी कोर को शांत करने के लिए मजबूर हैं। यह स्पष्ट नहीं है कि आगे का सबसे अच्छा रास्ता क्या है, या आगे का कोई रास्ता है भी या नहीं।
तब तक, अनुशंसित समाधान यह है कि कोई लंबा निर्देश निष्पादित न करें।
प्रूफ-ऑफ-कॉन्सेप्ट में 0xfcc68860 पर डिफ़ॉल्ट vmovdqu इस मशीन पर एक धीमा स्थान है — एक Zen 3 Ryzen 7 5800H — और संभवतः कहीं और नहीं। अपने बॉक्स पर रेंडेज़वस तोड़ने के लिए, आपको लंबे निर्देश को फिर से ट्यून करना होगा ताकि रुकावट आपके SMM टाइमआउट से अधिक समय तक चले। ऐसा करने के कुछ सुझाव:
-r xmm → ymm → zmm तक कदम बढ़ाएँ जब तक रुकावट रेंडेज़वस टाइमआउट को पार न कर जाए।make # smiiiiiiiiiiiiiiii बनाता है
डिफ़ॉल्ट एक मशीन के लिए ट्यून किए गए हैं। Zen 3 Ryzen 7 5800H के अलावा किसी भी चीज़ पर, जब तक आप लंबे निर्देश को फिर से ट्यून नहीं करते, कोई विचलन की उम्मीद न करें — अपने प्लेटफ़ॉर्म पर पोर्ट करना देखें।
प्रत्येक कोर के SMI काउंटर में विचलन देखते हुए बहुत-बहुत-लंबे निर्देश को बार-बार दागने के लिए टूल चलाएँ:
sudo ./smiiiiiiiiiiiiiiii # डिफ़ॉल्ट: -r xmm at 0xfcc68860
फ़्लैग:
| फ़्लैग | डिफ़ॉल्ट | विवरण |
|---|---|---|
-r xmm|ymm|zmm | xmm | समयबद्ध MMIO पढ़ने के लिए वेक्टर रजिस्टर चौड़ाई (16/32/64 बाइट्स)। यदि कोई SMI गिनती डेल्टा नहीं देखा जाता है, तो टूल अगले आकार तक कदम बढ़ाने की सलाह देता है। |
-a <phys-addr> | 0xfcc68860 | MMIO टाइमिंग लूप के लिए लक्षित भौतिक पता (हेक्स 0x... या दशमलव)। |
-h, --help | — | उपयोग प्रिंट करें और बाहर निकलें। |
smiiiiiiiiiiiiiiii क्रिस्टोफर डोमास (@xoreaxeaxeax) का एक शोध प्रयास है।