CVE-2026-50416: विंडोज़ 11 KASLR बायपास
Windows 11 Insider बिल्ड 10.0.28020.2149 पर, Win32k डेस्कटॉप हीप का यूज़र मोड मैपिंग ऑफसेट 0x100 पर एक कच्चा कर्नेल सेशन पूल पॉइंटर उजागर करता है।
रीड स्वयं लगभग आक्रामक रूप से छोटा है:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
मेरे परीक्षण सेशन में, इसने लौटाया:
0xffffc600dcc00040
मान एक ही डेस्कटॉप पर विभिन्न प्रक्रियाओं में समान रहा और रीबूट के बाद बदल गया। किसी अन्य डेस्कटॉप पर लॉन्च की गई प्रक्रिया को एक अलग मान प्राप्त हुआ क्योंकि उसका डेस्कटॉप हीप अलग था। इस एक QWORD से, PoC ने कर्नेल डेस्कटॉप हीप बेस को पुनर्प्राप्त किया और फिर लाइव विंडो ऑब्जेक्ट्स के कर्नेल पते प्राप्त करने के लिए user32!gSharedInfo का उपयोग किया।
वही रीड Low integrity, AppContainer, शून्य क्षमताओं वाले LPAC कॉन्फ़िगरेशन, और शून्य क्षमताओं वाले Low integrity AppContainer चाइल्ड से काम करता था।
डेस्कटॉप हीप को साझा किया जाना चाहिए। कर्नेल पॉइंटर नहीं है।
Win32k विंडो, मेनू, क्लास, हुक और संबंधित मेटाडेटा जैसे USER ऑब्जेक्ट्स को डेस्कटॉप हीप्स में संग्रहीत करता है। प्रत्येक डेस्कटॉप का अपना हीप होता है। उस हीप का एक हिस्सा डेस्कटॉप से जुड़ी प्रक्रियाओं में मैप किया जाता है ताकि यूज़र मोड हर फ़ील्ड के लिए कर्नेल से पूछे बिना साझा GUI स्थिति पढ़ सके।
परीक्षण किए गए x64 बिल्ड पर, यूज़र मोड मैपिंग वर्तमान थ्रेड के TEB क्लाइंट डेटा के माध्यम से पहुंचा जा सकता है:
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
ऑफसेट बिल्ड-विशिष्ट हैं, लेकिन मार्ग सरल है:
GS:[0x30]
-> TEB
-> ClientInfo at TEB + 0x800
-> ClientInfo[5]
-> यूज़र मोड डेस्कटॉप हीप मैपिंग
PoC लौटाए गए पते पर VirtualQuery कॉल करता है और मैप किए गए क्षेत्र और उसकी सुरक्षा को रिकॉर्ड करता है। अभी तक कुछ भी गलत नहीं हुआ है। रीड-ओनली डेस्कटॉप हीप मैपिंग सामान्य Win32k व्यवहार है।
समस्या इसके 256 बाइट्स बाद शुरू होती है।
मुख्य PoC मैप किए गए हीप से एक QWORD पढ़ता है:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
मान परीक्षण प्रणाली पर कर्नेल वर्चुअल एड्रेस से अपेक्षित बुनियादी जांचों को पार कर गया:
स्थिरता परीक्षण STATIC, BUTTON और EDIT विंडो बनाता है, निर्माण से पहले मान पढ़ता है, विंडो के मौजूद रहते हुए इसे फिर से पढ़ता है, उन्हें नष्ट करता है, और तीसरी बार पढ़ता है।
ULONG64 before = *(ULONG64 *)(desktop_heap + 0x100);
HWND w1 = CreateWindowExA(0, "STATIC", "A", WS_OVERLAPPEDWINDOW,
0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w2 = CreateWindowExA(0, "BUTTON", "B", WS_OVERLAPPEDWINDOW,
0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
HWND w3 = CreateWindowExA(0, "EDIT", "C", WS_OVERLAPPEDWINDOW,
0, 0, 100, 100, NULL, NULL, GetModuleHandleA(NULL), NULL);
ULONG64 after_create = *(ULONG64 *)(desktop_heap + 0x100);
DestroyWindow(w1);
DestroyWindow(w2);
DestroyWindow(w3);
ULONG64 after_destroy = *(ULONG64 *)(desktop_heap + 0x100);
तीनों रीड्स ने समान मान लौटाया। विंडो आवंटन गतिविधि ने इसे स्थानांतरित नहीं किया। यह व्यवहार अल्पकालिक ऑब्जेक्ट पॉइंटर के बजाय डेस्कटॉप हीप मेटाडेटा में एक फ़ील्ड के अनुरूप है।
क्रॉस-प्रोसेस गुण उतना ही महत्वपूर्ण है। एक ही डेस्कटॉप से जुड़ी दो प्रक्रियाएं समान लीक मान देखती हैं क्योंकि वे एक ही डेस्कटॉप हीप को देख रही हैं। रीबूट के बाद, KASLR सेशन को एक नया पता देता है। किसी अन्य डेस्कटॉप पर रखा गया चाइल्ड एक और पॉइंटर देखता है क्योंकि उस डेस्कटॉप के पास एक और हीप है।
यह लीक को एक उपयोगी पहचान देता है:
समान बूट + समान डेस्कटॉप -> समान पॉइंटर
समान बूट + अलग डेस्कटॉप -> अलग पॉइंटर
नया बूट -> अलग पॉइंटर
परीक्षण किए गए बिल्ड पर, लीक पॉइंटर PoC द्वारा उपयोग किए गए कर्नेल डेस्कटॉप हीप बेस से 0x40 बाइट्स ऊपर स्थित है:
ULONG64 kernel_desktop_heap_base = leaked - 0x40;
रिकॉर्ड किए गए सेशन मान का उपयोग करके:
लीक पॉइंटर = 0xffffc600dcc00040
कर्नेल डेस्कटॉप हीप बेस = 0xffffc600dcc00000
यह संबंध बिल्ड-विशिष्ट है। परीक्षण के दौरान उपयोग किए गए बिल्ड के लिए, यह अगले चरण के लिए आवश्यक कर्नेल-साइड एंकर देता है।
एक पॉइंटर पहले से ही उपयोगी है। किसी चुने हुए ऑब्जेक्ट के लिए एक पता बहुत अधिक उपयोगी है।
user32.dll gSharedInfo निर्यात करता है, जो USER हैंडल एंट्री सूची और प्रत्येक एंट्री का आकार उजागर करता है:
typedef struct {
PVOID psi;
PVOID aheList;
ULONG HeEntrySize;
} SHAREDINFO;
SHAREDINFO *shared = (SHAREDINFO *)GetProcAddress(
GetModuleHandleA("user32.dll"),
"gSharedInfo"
);
एक HWND में USER हैंडल तालिका में एक इंडेक्स होता है। PoC हैंडल के निचले 16 बिट्स लेता है, मिलान एंट्री तक जाता है, और वहां संग्रहीत डेस्कटॉप हीप ऑफसेट पढ़ता है।
ULONG index = (ULONG)(ULONG_PTR)hwnd & 0xffff;
BYTE *entry = (BYTE *)shared->aheList + index * shared->HeEntrySize;
ULONG64 heap_offset = *(ULONG64 *)entry;
समान ऑफसेट दोनों मैपिंग में ऑब्जेक्ट का नाम देता है:
BYTE *user_window = desktop_heap + heap_offset;
ULONG64 kernel_window = kernel_desktop_heap_base + heap_offset;
तो पूरी गणना है:
कर्नेल डेस्कटॉप हीप बेस = desktop_heap[0x100] - 0x40
हैंडल इंडेक्स = HWND & 0xffff
हीप ऑफसेट = aheList[हैंडल इंडेक्स].offset
कर्नेल विंडो पता = कर्नेल डेस्कटॉप हीप बेस + हीप ऑफसेट
PoC छह विंडो क्लास बनाता है और प्रत्येक के लिए गणना करता है:
STATICBUTTONEDITLISTBOXSCROLLBARCOMBOBOXप्रत्येक ऑब्जेक्ट के लिए, यह HWND, हैंडल इंडेक्स, यूज़र मोड ऑब्जेक्ट पता, हीप ऑफसेट और कर्नेल पता प्रिंट करता है।
HWND
-> निचला 16-बिट हैंडल इंडेक्स
-> gSharedInfo हैंडल एंट्री
-> डेस्कटॉप हीप ऑफसेट
-> कर्नेल डेस्कटॉप हीप बेस + ऑफसेट
-> उस विंडो ऑब्जेक्ट का कर्नेल पता
यह वह हिस्सा है जो प्रकटीकरण को एक ढीले कर्नेल पॉइंटर से परीक्षण किए गए डेस्कटॉप हीप पर चयनित USER ऑब्जेक्ट्स के लिए एक पता ओरेकल में बदल देता है।
डेस्कटॉप हीप एक साझा मैपिंग के माध्यम से आता है। इंटीग्रिटी स्तर और AppContainer प्रतिबंध प्रत्येक प्रक्रिया के लिए उस मैपिंग की सामग्री को फिर से नहीं लिखते हैं। यदि प्रक्रिया को डेस्कटॉप हीप प्राप्त होता है, तो उसे इसके साथ 0x100 पर QWORD भी प्राप्त होता है।
सैंडबॉक्स PoC कई संदर्भों में चाइल्ड लॉन्च करता है और प्रत्येक चाइल्ड को अपने स्वयं के TEB और अपनी स्वयं की डेस्कटॉप हीप मैपिंग से मान पढ़ने देता है।
| संदर्भ | कॉन्फ़िगरेशन | परिणाम |
|---|---|---|
| Medium integrity | मानक उपयोगकर्ता प्रक्रिया | लीक हुआ |
| Low integrity | टोकन इंटीग्रिटी Low पर कम | लीक हुआ |
| AppContainer | शून्य अनुरोधित क्षमताएं | लीक हुआ |
| LPAC कॉन्फ़िगरेशन | सभी एप्लिकेशन पैकेज ऑप्ट-आउट नीति, शून्य अनुरोधित क्षमताएं | लीक हुआ |
| Low integrity AppContainer | Low IL प्लस AppContainer, शून्य अनुरोधित क्षमताएं | लीक हुआ |
| वैकल्पिक डेस्कटॉप | चाइल्ड एक नए डेस्कटॉप को सौंपा गया | एक अलग मान लीक हुआ |
पहले पांच चाइल्ड डिफ़ॉल्ट डेस्कटॉप से जुड़े थे और समान पता लौटाया। वैकल्पिक डेस्कटॉप चाइल्ड ने एक और पता लौटाया क्योंकि उसे एक और डेस्कटॉप हीप प्राप्त हुआ।
चाइल्ड आउटपुट में एक कॉम्पैक्ट प्रारूप है ताकि पैरेंट परिणामों की तुलना कर सके:
RESULT|LowIL+AppContainer|LEAKED|0xffffc600dcc00040|1|1234
सख्त हेल्पर टोकन स्थिति और क्षमता गणना भी रिकॉर्ड करता है:
RESULT|LPAC_LowIL_NoCaps|LEAKED|0xffffc600dcc00040|IL=Low|AC=1|caps=0|PID=1234
महत्वपूर्ण विवरण यह नहीं है कि चाइल्ड एक विशेष Win32k API कॉल कर सकता है। उसे इसकी आवश्यकता नहीं है। एक बार मैपिंग मौजूद होने पर, लीक एक सामान्य यूज़र मोड मेमोरी रीड है।
एक अलग हेल्पर CreateWindow कॉल किए बिना रीड करता है।