
अनुवाद लेख, CVE-2015-0057 भेद्यता का 32-bit और 64-bit सिस्टम पर शोषण। win32k!xxxEnableWndSBArrows use-after-free (CVE 2015-0057) बग का 32-bit और 64-bit दोनों पर शोषण (NCC के Aaron Adams)
लेखक: Aaron Adams
अनुवाद: 55-AA
अनुवादक का नोट: इस लेख के कुछ भागों का अनुवाद सारांश रूप में किया गया है। मूल पाठ के लिए कृपया मूल लेख देखें।
परिभाषाएँ:
इस वर्ष की शुरुआत में, मैं win32k.sys (CVE-2015-0057) में एक दिलचस्प भेद्यता के संपर्क में आया और इसे 32-बिट और 64-बिट सिस्टम पर स्थिर रूप से शोषण करने में सफल रहा, जो XP से Windows 8.1 तक (कुछ अपवादों के साथ) लागू होता है। यह लेख विस्तार से वर्णन करता है कि मैंने इन दोनों प्लेटफार्मों पर शोषण कैसे पूरा किया, अंत में कुछ अतिरिक्त चीजें भी संलग्न हैं। साथ ही, यह भी बताया गया है कि SMEP सक्षम Windows 8.1 पर कम अखंडता अनुमति वाले प्रक्रिया के तहत शोषण कैसे किया जाए।
यह लेख काफी लंबा है, मैंने इस भेद्यता का शोषण करने की जटिलता को छिपाने के बजाय यथासंभव अधिक विवरण प्रदान करने का प्रयास किया है, हालाँकि मैंने कुछ विवरणों को टाला भी है। उम्मीद है कि ये विवरण सभी के लिए सहायक होंगे।
10 फरवरी 2015 को, माइक्रोसॉफ्ट ने MS15-010 से संबंधित विवरण जारी किए। इस बग को सबसे पहले enSilo के Udi Yavo ने खोजा था। Udi ने ब्रेकिंग मैलवेयर ब्लॉग पर एक अच्छा विश्लेषण दिया था "one bit rule-bypassing windows 10 protections using single bit"। मैं इस लेख को ध्यान से पढ़ने की सलाह देता हूं ताकि इस बग को गहराई से समझा जा सके, हालाँकि मैं इस लेख में यथासंभव अधिक से अधिक विवरण प्रदान करूंगा, जिसमें उन बाधाओं का विवरण शामिल है जिन्हें भेद्यता को ट्रिगर करते समय दूर करना होगा। इस भेद्यता का शोषण करना बहुत दिलचस्प है, अधिकांश विवरण Udi के ब्लॉग से लिए गए हैं, और यहाँ उनका बयान है:
उचित प्रकटीकरण: हालांकि यह ब्लॉग तकनीकी है, हम किसी भी कोड या पूर्ण विवरण का खुलासा नहीं करेंगे ताकि किसी भी तकनीकी विशेषज्ञ द्वारा इस भेद्यता के शोषण को पुन: उत्पन्न नहीं किया जा सके।
इस भेद्यता का शोषण करने के अतिरिक्त पुरस्कार के रूप में, हमें एक पोकेमॉन का विकास मिला: तकनीकी पोकेमॉन। मुझे लगता है कि मुझे Udi को कुछ श्रेय देना चाहिए क्योंकि उन्होंने इस बग की खोज की और ब्लॉग पर संबंधित स्थिति और इस बग का शोषण करने के विवरण प्रदान किए, ये चीजें बहुत उपयोगी थीं।
पहले, मैंने कभी win32k.sys की भेद्यता का शोषण नहीं किया था, न ही मैं उपयोगकर्ता मोड कॉलबैक और कई संबंधित API से परिचित था, इसलिए मैं उन प्रसिद्ध सुरक्षा शोधकर्ताओं के प्रति आभार व्यक्त करता हूं जिन्होंने ऑनलाइन नए संसाधन उपलब्ध कराए, जैसे Skywing, Tarjei Mandt, Alex Ionescu और j00ru आदि। इन लोगों ने इतनी सारी तकनीकी जानकारी सार्वजनिक रूप से उपलब्ध कराई, इन सभी की प्रशंसा होनी चाहिए। मैंने मुख्य रूप से Tarjei Mandt के एक लेख Win32k.sys शोषण पेपर का संदर्भ लिया।
जब मैं इस भेद्यता का शोषण लिख रहा था, तब एक उत्कृष्ट रिवर्स इंजीनियर ने CVE-2015-1701 का स्थिर शोषण लागू किया था, जिसमें उपयोगकर्ता मोड कॉलबैक के उदाहरण कोड उपयोगी थे। उस लेखक को धन्यवाद।
यह ध्यान देने योग्य है कि मेरा नीचे का विश्लेषण Windows 7 पर किया गया था, क्योंकि ऐसा लगता है कि यह एकमात्र संस्करण है जहाँ win32k.sys के सभी संरचनाओं में संबंधित प्रतीक हैं। ये प्रतीक अधिकांशतः Win32k.sys के अन्य संस्करणों की संरचनाओं में उपलब्ध हैं। किसी कारण से, माइक्रोसॉफ्ट ने Windows 8 से इन प्रतीकों को हटा दिया।
अंत में, मैं यह कहना चाहूंगा कि मेरी शोषण विधि काफी जटिल है। यह पूरी तरह संभव है कि कोई आसान तरीका हो, बस मैंने इसे नहीं खोजा। मैं यह सुनना चाहूंगा कि किसी ने भिन्न विधि का उपयोग किया हो। किसी भी तरह से, मुझे आशा है कि यह सब win32k.sys की भेद्यताओं पर शोध के लिए उपयोगी है।
नीचे हम win32k!xxxEnableWndSBArrows के डिसअसेंबली कोड में इस बग को देखते हैं, जो एक सूक्ष्म बग है:
पैच न किए गए संस्करण में:
.text:FFFFF97FFF1B157D mov r8d, r13d
.text:FFFFF97FFF1B1580 mov rdx, r14
.text:FFFFF97FFF1B1583 call xxxDrawScrollBar ; उपयोगकर्ता मोड कॉलबैक ट्रिगर करता है
.text:FFFFF97FFF1B1588 jmp short loc_FFFFF97FFF1B1519
[...]
.text:FFFFF97FFF1B1519 mov eax, [rbx] ; tagSBINFO पॉइंटर को बिना जांचे संदर्भित करता है
.text:FFFFF97FFF1B151B mov ebp, 0FFFFFFFBh
.text:FFFFF97FFF1B1520 xor eax, esi
उपरोक्त कोड में, Win32K!xxxdrawscrollbar उपयुक्त परिस्थितियों में उपयोगकर्ता स्थान पर कॉलबैक कर सकता है, जहाँ tagSBINFO का पॉइंटर हमलावर द्वारा मुक्त किया जा सकता है, और जब कोड वापस ऊपर आता है, तो 0xFFFFF97FFF1B1519 पर कोड एक अमान्य पॉइंटर का संदर्भ लेगा।
पैच किए गए संस्करण में:
.text:FFFFF97FFF1D69C3 xor r8d, r8d
.text:FFFFF97FFF1D69C6 mov rdx, rbp
.text:FFFFF97FFF1D69C9 call xxxDrawScrollBar ; उपयोगकर्ता मोड कॉलबैक ट्रिगर करता है
.text:FFFFF97FFF1D69CE cmp rbx, [rdi+0B0h] ; जांचता है कि tagSBINFO पॉइंटर सही है
---.text:FFFFF97FFF1D69D5 jz short loc_FFFFF97FFF1D69E4; यदि सही है तो मूल प्रवाह जारी रखें
| .text:FFFFF97FFF1D69D7 mov rcx, rbp
| .text:FFFFF97FFF1D69DA call _ReleaseDC
| .text:FFFFF97FFF1D69DF jmp loc_FFFFF97FFF1D6958 ; फंक्शन से बाहर कूदें
|->.text:FFFFF97FFF1D69E4 mov eax, [rbx] ; सुरक्षित रूप से सही tagSBINFO पॉइंटर का उपयोग करें
.text:FFFFF97FFF1D69E6 xor eax, r14d
उपरोक्त पैच किए गए संस्करण में, हम देखते हैं कि tagSBINFO पॉइंटर का उपयोग करने से पहले शून्यता की जाँच की गई है। संबंधित संरचना की जानकारी बाद में दी जाएगी।
इस शोषण को लागू करते समय, हमने कई बार भ्रष्टाचार (corruption) किया। और उनमें से एक भ्रष्टाचार के दौरान हमने इस भेद्यता को ट्रिगर किया।
इस बग का तकनीकी मूल एक डेस्कटॉप हीप का UAF (use-after-free) है। शुरुआत में, यह मुझे भ्रमित कर रहा था, क्योंकि मैं win32k.sys के उपयोगकर्ता मोड कॉलबैक तंत्र से परिचित नहीं था, और यह भी स्पष्ट नहीं था कि यह कैसे काम करता है। इसलिए, मैंने सोचा कि यह एक लॉक रेस कंडीशन है जो UAF का कारण बनती है। वास्तव में, उस संरचना के लॉक का सही ढंग से उपयोग किया गया था, और इसका प्रवाह अपेक्षित था। संक्षेप में, समस्या का असली कारण है:
बस इतना ही, उपयोगकर्ता मोड कॉलबैक को छोड़कर, यह चरण काफी स्पष्ट है।
लेकिन, हम भ्रष्टाचार कैसे करते हैं और क्यों करते हैं? जैसा कि Udi के ब्लॉग में उल्लेख किया गया है, आप एक निश्चित स्थान पर 2 बिट सेट या क्लियर कर सकते हैं, और इस स्थान को सिस्टम कोड tagSBINFO संरचना में WSBflags फ़ील्ड मानता है। यह UAF का मानक उपयोग नहीं है, लेकिन लेख में एक संकेत दिया गया है कि यह कैसे काम करता है, जिसे मैं अगले खंडों में समझाऊंगा। पहले, आइए समझें कि हम इन बिट्स को कैसे नियंत्रित करते हैं।
tagSBINFO संरचना (32-बिट और 64-बिट में समान):
kd> dt -b !tagSBINFO
win32k!tagSBINFO
+0x000 WSBflags : Int4B
+0x004 Horz : tagSBDATA
+0x000 posMin : Int4B
+0x004 posMax : Int4B
+0x008 page : Int4B
+0x00c pos : Int4B
+0x014 Vert : tagSBDATA
+0x000 posMin : Int4B
+0x004 posMax : Int4B
+0x008 page : Int4B
+0x00c pos : Int4B
यह UAF भेद्यता win32k!xxxEnableWndSBArrows() फंक्शन में है, जो एक या दो (क्षैतिज या ऊर्ध्वाधर) स्क्रॉल बार नियंत्रकों के तीरों को सक्षम या अक्षम करने के लिए उपयोग किया जाता है। स्क्रॉल बार नियंत्रण एक विशेष विंडो है जिसका उपयोग स्क्रॉल बार को संचालित करने के लिए किया जाता है। इसे CreateWindow() फंक्शन के साथ बिल्ट-इन "SCROLLBAR" विंडो क्लास का उपयोग करके बनाया जा सकता है।
win32k!xxxEnableWndSBArrows() का फंक्शन प्रोटोटाइप:
BOOL xxxEnableWndSBArrows(PWND wnd, UINT WSBflags, UINT wArrows);
पैरामीटर WSBflags का अर्थ WinUser.h में परिभाषित के अनुसार है, जो बताता है कि कौन से स्क्रॉल बार को संचालित किया जाएगा:
#define SB_HORZ 0
#define SB_VERT 1
#define SB_CTL 2
#define SB_BOTH 3
पैरामीटर wArrows तीरों की स्थिति को दर्शाता है, उपलब्ध या अनुपलब्ध। सेट होने का मतलब है तीर अनुपलब्ध, अन्यथा उपलब्ध। पैरामीटर wArrows के निचले दो बिट क्षैतिज स्क्रॉल बार को दर्शाते हैं, अगले दो बिट ऊर्ध्वाधर स्क्रॉल बार को, और शेष बिट इस शोषण के उद्देश्य से अप्रासंगिक हैं।
नीचे का कोड win32k!xxxEnableWndSBArrows() फंक्शन से लिया गया है, जो SB_HORZ या SB_BOTH सेट होने पर क्षैतिज तीरों के संबंधित बिट्स को सेट या क्लियर करता है:

यह बग क्षैतिज और ऊर्ध्वाधर स्क्रॉल बार फ़्लैग्स सेट करते समय मौजूद है। क्षैतिज स्क्रॉल बार को रिफ्रेश करने के बाद, जैसे ही संबंधित स्क्रॉल बार डेस्कटॉप पर दिखाई देता है, win32k!xxxEnableWndSBArrows() फंक्शन win32k!xxxDrawScrollBar() को कॉल करता है, जैसा कि पिछले लेख में उल्लेख किया गया था कि यह एक संभावित उपयोगकर्ता मोड कॉलबैक को ट्रिगर कर सकता है।
इससे पहले कि हम उपयोगकर्ता मोड कॉलबैक पर चर्चा करें, आइए देखें कि win32k!xxxDrawScrollBar() को कॉल करने के बाद क्या होता है। यह वास्तव में क्षैतिज स्क्रॉल बार के समान तर्क है, केवल कुछ बिट्स का अंतर है। यदि हम ऊर्ध्वाधर स्क्रॉल बार को अक्षम करना चुनते हैं, और मान लें कि हमने UAF को ट्रिगर किया है, तो यह tagSBINFO हीप चंक के किसी स्थान पर 2 बिट लिखेगा। इसलिए, यदि मूल मान 0x2 है, तो यह 0xe हो जाता है। जैसा कि नीचे चित्र में दिखाया गया है।

बिट्स का यह परिवर्तन अंततः कोड निष्पादन की ओर ले जाने के लिए पर्याप्त है। मैंने गहराई से यह नहीं देखा कि बिट्स को क्लियर करके शोषण कैसे पूरा किया जाए, लेकिन यह संभव है।
उपरोक्त का मुख्य बिंदु यह है कि क्षैतिज और ऊर्ध्वाधर दोनों स्क्रॉल बार को संचालित करने के लिए, हमें किसी तरह से दोनों तत्वों वाला एक स्क्रॉल बार नियंत्रण बनाना होगा। यह CreateWindow() को WS_HSCROLL और WS_VSCROLL फ़्लैग्स के साथ कॉल करके किया जाता है। कोड इस प्रकार है:
g_hSBCtl = CreateWindowEx(
0, // कोई विस्तारित शैली नहीं
"SCROLLBAR", // क्लास
NULL, // नाम
SBS_HORZ | WS_HSCROLL | WS_VSCROLL, // ऊर्ध्वाधर + क्षैतिज
10, // x
10, // y
100, // चौड़ाई
100, // ऊँचाई
g_hSpray[UAFWND], // मॉडललेस पैरेंट विंडो
(HMENU)NULL,
NULL, // विंडो स्वामी
NULL // अतिरिक्त पैरामीटर
);
नीचे दिए गए कोड से यह दृश्यता सुनिश्चित की जा सकती है (आमतौर पर यह डिफ़ॉल्ट है, यहाँ स्पष्ट रूप से कॉल किया गया है):
result = ShowWindow(g_hSBCtl, SW_SHOW);
स्क्रॉल बार डिफ़ॉल्ट रूप से सक्षम हैं, और जब हम भेद्यता कोड को ट्रिगर करने का प्रयास करने के लिए तैयार होते हैं, तो हम स्क्रॉल बार को अक्षम कर सकते हैं ताकि हमें आवश्यक बिट्स को भ्रष्ट कर सकें:
result = EnableScrollBar(g_hSBCtl, SB_CTL | SB_BOTH, ESB_DISABLE_BOTH);
हालाँकि हमने ऊपर बग के विवरण और संबंधित कोड को ट्रिगर करने के तरीके का वर्णन किया है, फिर भी हम सबसे महत्वपूर्ण चरण को छोड़ रहे हैं, जो है win32k!xxxDrawScrollBar() द्वारा शुरू किए गए उपयोगकर्ता मोड कॉलबैक को इंटरसेप्ट करना, ताकि हम win32k!xxxEnableWndSBArrows() के निष्पादन जारी रखने से पहले हीप की सामग्री को बदल सकें। हमें वास्तव में इस बग को ट्रिगर करने की आवश्यकता है, लेकिन Win32k.sys और संबंधित API के बारे में कुछ भी जाने बिना, जैसे मैं शुरुआत में था, यह अपने आप में एक साहसिक कार्य है।
पिछले लेख में एक अच्छा कॉल स्टैक आरेख था, जो इस प्रक्रिया को गहराई से दर्शाता है, win32k!xxxDrawScrollBar() के माध्यम से ट्रिगर, फिर ClientLoadLibrary() को कॉल, और KeUserModeCallback() के माध्यम से भेजा जाता है। हमें वास्तव में KeUserModeCallback() के कॉलिंग पैटर्न को समझने की आवश्यकता है ताकि हम अपनी प्रक्रिया में हुक लगा सकें।
मैंने कुछ अच्छे पेपर पाए जिनमें कमोबेश उपयोगकर्ता मोड कॉलबैक का उल्लेख किया गया है। win32k से संबंधित भाग बहुत उपयोगी हैं:
आमतौर पर, प्रत्येक प्रक्रिया में उपयोगकर्ता मोड कॉलबैक फंक्शंस की एक पॉइंटर टेबल होती है, जिसे PEB->KernelCallBackTable इंगित करता है। जब कर्नेल उपयोगकर्ता मोड फंक्शन को कॉल करना चाहता है, तो यह फंक्शन इंडेक्स नंबर को KeUserModeCallBack() को पास करता है। उपरोक्त उदाहरण में, इंडेक्स नंबर उपयोगकर्ता-साइड __ClientLoadLibrary() फंक्शन को इंगित करता है।
KeUserModeCallBack() PEB->KernelCallBackTable में इंडेक्स के अनुसार संबंधित फंक्शन को ढूंढता है और उसे निष्पादित करता है, अंततः उपयोगकर्ता मोड में KiUserModeCallbackDispatch() को कॉल करता है।
निर्दिष्ट प्रवेश बिंदु को हुक करने के लिए, हमें PEB->KernelCallBackTable में __ClientLoadLibrary() का इंडेक्स ढूंढना होगा और इसे अपने स्वयं के फंक्शन से बदलना होगा। यह ध्यान देने योग्य है कि यह इंडेक्स ऑपरेटिंग सिस्टम संस्करण और हार्डवेयर प्लेटफॉर्म के अनुसार भिन्न होता है।
यदि हम PEB->KernelCallBackTable देखना चाहते हैं, तो WinDbg के माध्यम से इस तालिका का पता लगा सकते हैं। 32-बिट और 64-बिट प्लेटफार्मों की तुलना करने पर अधिक अंतर नहीं पाया गया।
kd> dt !_PEB @$peb
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 BitField : 0x8 ''
+0x003 ImageUsesLargePages : 0y0
[...]
+0x02c KernelCallbackTable : 0x76daf620 Void
kd> dds 0x76daf620
76daf620 76d96443 user32!__fnCOPYDATA
76daf624 76ddf0e4 user32!__fnCOPYGLOBALDATA
76daf628 76da736b user32!__fnDWORD
76daf62c 76d9d603 user32!__fnNCDESTROY
76daf630 76dc50f9 user32!__fnDWORDOPTINLPMSG
76daf634 76ddf1be user32!__fnINOUTDRAG
76daf638 76dc6cd0 user32!__fnGETTEXTLENGTHS
76daf63c 76ddf412 user32!__fnINCNTOUTSTRING
76daf640 76d9ce49 user32!__fnINCNTOUTSTRINGNULL
[...]
76daf724 76da3962 user32!__ClientLoadLibrary
kd> ?? (0x76daf724-0x76daf620)/4 int 0n65
उपरोक्त उदाहरण में, हम जानते हैं कि __ClientLoadLibrary का इंडेक्स 65 है, और यही वह स्थान है जहाँ हम हुक लगाएंगे। हुक लगाने के बाद, मैंने पाया कि __ClientLoadLibrary को win32k के संबंधित कोड द्वारा कई बार कॉल किया गया है! सबसे पहले, हमें यह करना होगा कि हम अपने इच्छित कॉल से पहले अपने हुक कोड को कैसे सूचित करें ताकि हम जान सकें कि हमने वास्तव में उस स्थान पर हुक लगाया है जहाँ संशोधन की आवश्यकता है। इसलिए, हुक कोड में एक ग्लोबल वेरिएबल का उपयोग किया गया है जिसे चिह्नित किया जाता है, और जब यह चिह्न सेट होता है तभी संबंधित कार्रवाई की जाती है।
अब दो बाधाएँ हैं:
इसलिए, हुक फंक्शन नीचे दिए गए रूप में दिखता है:
void ClientLoadLibraryHook(void * p)
{
CHAR Buf[PGSZ];
memset(Buf, 0, sizeof(Buf));
if (g_PwnFlag)
{
dprintf("[+] __ClientLoadLibrary hook called\n");
if (++g_HookCount == 2)
{
g_PwnFlag = 0; // केवल एक बार निष्पादित करें..
ReplaceScrollBarChunk(NULL);
}
}
fpClientLoadLibrary(&Buf); // मूल फंक्शन को कॉल करें
}
एक बार जब हम यह निर्धारित कर लेते हैं कि वर्तमान कॉल win32k!xxxDrawScrollBar() से आ रही है, तो हम इस बग को ट्रिगर करने का प्रयास कर सकते हैं। अब केवल ट्रिगर करने पर विचार करें, हमें बस DestroyWindow(g_hSBCtl) कॉल करना है। इससे विंडो की tagSBINFO संरचना मुक्त हो जाएगी, जबकि विंडो संरचना स्वयं तुरंत मुक्त नहीं होगी क्योंकि इसका रेफरेंस काउंट अभी भी मूल कॉल द्वारा उपयोग में है, लेकिन tagSBINFO में ऐसा कोई रेफरेंस काउंट तंत्र नहीं है, इसलिए यह तुरंत मुक्त हो जाता है।
इस समय, हमने बग को ट्रिगर कर दिया है। हालाँकि हमने tagSBINFO वाले हीप चंक को पुनः आवंटित नहीं किया है, फिर भी हम पहले से मुक्त हीप पर अक्षमता दर्शाने वाले दो बिट लिख सकते हैं। अगला कदम इस मुक्त चंक को अपनी इच्छित चीज़ से बदलना है, ताकि हम केवल कुछ बिट्स सेट करने से अधिक दिलचस्प कुछ कर सकें। इसके लिए, हमें डेस्कटॉप हीप के बारे में कुछ पृष्ठभूमि जानकारी की आवश्यकता है।
win32k.sys डेस्कटॉप हीप का उपयोग किसी दिए गए डेस्कटॉप से संबंधित GUI ऑब्जेक्ट्स को संग्रहीत करने के लिए करता है। इसमें विंडो ऑब्जेक्ट्स और उनसे संबंधित संरचनाएँ शामिल हैं, जैसे प्रॉपर्टी सूचियाँ, विंडो टेक्स्ट, स्क्रॉल बार। Tarjei का लेख इसका उल्लेख करता है, लेकिन विशेष रूप से ध्यान दें कि डेस्कटॉप हीप वास्तव में उपयोगकर्ता-मोड बैकएंड आवंटक का एक सरलीकृत संस्करण है, और RtlAllocateHeap() और RtlHeapFree() का उपयोग करके संचालित होता है। डेस्कटॉप हीप एक _HEAP संरचना द्वारा बनाए रखा जाता है, और चूंकि कोई फ्रंट-एंड आवंटक नहीं है, इसलिए कोई कम-फ़्रैगमेंटेशन हीप (LFH) या साइड-व्यू सूचियाँ आदि नहीं हैं।
प्रत्येक डेस्कटॉप बनाने पर उसके लिए एक संबंधित डेस्कटॉप हीप सेवा प्रदान करता है। इसका मतलब है कि हम एक नया डेस्कटॉप आवंटित कर सकते हैं ताकि हमें एक "स्वच्छ" डेस्कटॉप हीप मिल सके, जिस पर हमारे संचालन अधिक पूर्वानुमानित हों। हालाँकि, यह कम अखंडता अनुमति वाली प्रक्रियाओं के लिए अर्थहीन है, क्योंकि ऐसी प्रक्रियाएँ नया डेस्कटॉप बनाने की अनुमति नहीं देती हैं।
अब मुख्य समस्या आवंटन प्रक्रिया को ट्रैक करना है (बाद में मेटाडेटा आदि के बारे में अधिक विवरण शामिल किए जाएंगे)।
डेस्कटॉप हीप के आवंटन और मुक्ति को ट्रैक करने के लिए, मैंने WinDbg स्क्रिप्ट का उपयोग करने की आदत बनाई:
64-बिट हीप निगरानी
ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", @rcx, @edx, @r8; .echo ; gc";
ba e 1 nt!RtlAllocateHeap "r @$t2 = @r8; r @$t3 = @rcx; gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @rax; gc";
32-बिट हीप निगरानी
ba e 1 nt!RtlAllocateHeap "r @$t2 = poi(@esp+c); r @$t3 = poi(@esp+4); gu; .printf \"RtlAllocateHeap(%p, 0x%x):\", @$t3, @$t2; r @eax; gc";
ba e 1 nt!RtlFreeHeap ".printf\"RtlFreeHeap(%p, 0x%x, %p)\", poi(@esp+4), poi(@esp+8), poi(@esp+c); .echo ; gc"
इन डीबग स्क्रिप्ट के अलावा, चूंकि डेस्कटॉप हीप केवल उपयोगकर्ता-मोड बैकएंड आवंटक का एक सरलीकृत रूप है, हम वास्तव में WinDbg के अंतर्निर्मित !heap कमांड का भी उपयोग कर सकते हैं।
इस बग का शोषण करने के लिए, हमें हाल ही में मुक्त किए गए tagSBINFO हीप चंक को बदलने की आवश्यकता है, और हम यह भी जानते हैं कि इन विशिष्ट बग्स का उपयोग करके आसन्न डेटा को कैसे भ्रष्ट किया जाए। यह हमारे लिए मूल आवश्यकता है कि हम आवश्यक संरचना के पास पहले से कुछ हीप चंक आवंटित करें। किसी हीप चंक को कहाँ आवंटित किया जाएगा, इसका अनुमान लगाने के लिए, हमें पूरे हीप लेआउट को नियंत्रित करना होगा (या जितना संभव हो)। ऐसा करने के लिए, एक व्यावहारिक तरीका यह है कि जितना संभव हो उतने हीप चंक आवंटित करें ताकि पहले से मुक्त किए गए चंक भर जाएँ, और फिर नए आवंटित चंक क्रमागत हों। जब हमें एक छेद की आवश्यकता होती है, तो हम पूर्वानुमानित स्थान पर एक छेद बना सकते हैं (पहले से आवंटित चंक को मुक्त करके)।
यह भाग आवंटन को प्रभावित करने वाले कुछ कारकों की बुनियादी समझ है, ऊपर दिए गए WinDbg स्क्रिप्ट हमारी मदद कर सकते हैं। Tarjei ने अपने win32k PPT में डेस्कटॉप हीप पर आवंटित मुख्य ऑब्जेक्ट्स का उल्लेख किया है, और जो मैंने देखा, वह पूरी तरह से मेल खाता है। ये हैं:
डेस्कटॉप हीप काफी दिलचस्प है, अधिकांश आवंटन सीधे विंडो ऑब्जेक्ट से जुड़े होते हैं और tagWND संरचना के माध्यम से प्रबंधित होते हैं, जिसका अर्थ है कि किसी भी आकार का एक हीप चंक आवंटित करने के लिए (छोटे छेद को भरने के लिए), हमें पहले उससे संबंधित एक विंडो आवंटित करनी होगी। आप सोच सकते हैं कि विंडो संरचना हीप आवंटन का इंटरफ़ेस है। एक और दिलचस्प बात यह है कि विंडो ऑपरेशन के माध्यम से आवंटित कई हीप चंक को तब तक तुरंत मुक्त नहीं किया जा सकता जब तक कि विंडो स्वयं नष्ट न हो जाए, जो स्पष्ट रूप से हीप को प्रभावित करता है। अंत में, मान लें कि हम एक विंडो के माध्यम से आकार N का एक हीप चंक आवंटित करते हैं, जैसा कि ऊपर के उदाहरण में किया गया है, क्या हमें बहुत सारे आकार N के हीप चंक आवंटित करने की आवश्यकता है? यह निर्धारित किया जा सकता है कि आवंटित विंडो संरचनाओं का आकार चाहे कुछ भी हो, वे किसी सूची में संग्रहीत नहीं होते हैं। इसलिए, प्रत्येक विंडो आकार N के एक हीप आवंटन को नियंत्रित कर सकती है। यानी, यदि आपको बहुत सारे आकार N के हीप चंक आवंटित करने की आवश्यकता है, तो आपको पहले बहुत सारी विंडो बनानी होंगी, और विंडो का उपयोग हीप चंक आवंटन में सहायता के लिए करना होगा।
डेस्कटॉप हीप पर आवंटित तीन महत्वपूर्ण डेटा प्रकार भी हैं, जिनका उपयोग हम विंडो ऑब्जेक्ट के माध्यम से अप्रत्यक्ष रूप से हीप पर डेटा को नियंत्रित करने के लिए कर सकते हैं। हम इन डेटा प्रकारों का बड़े पैमाने पर उपयोग शोषण और हीप फेंगशुई बनाने के लिए करते हैं। ये तीन डेटा प्रकार हैं:
चित्र 2 इन डेटा प्रकारों के बीच संबंध दिखाता है:

हीप को आरंभ करने के लिए, मैंने बड़ी संख्या में tagWND संरचनाएँ बनाईं (विंडो ऑब्जेक्ट बनाकर)। इससे हीप पर कई बड़े छेद भर गए, और हमें अन्य आवश्यक हीप चंक आवंटित करने का इंटरफ़ेस भी मिला। Win8 और Win8.1 पर, एक नई विंडो आवंटित करने से स्वचालित रूप से एक tagPROPLIST संरचना आवंटित हो जाती है (पहले उल्लेखित WinDbg स्क्रिप्ट के माध्यम से देखा जा सकता है)। Win7 और इसके पुराने संस्करणों पर, हम स्वयं एक नया tagPROPLIST आवंटित करते हैं, जो छोटे छेद भरने के लिए उपयोग होता है।
यहाँ, हमारी स्प्रे की गई सभी विंडो ऑब्जेक्ट्स में विंडो टेक्स्ट स्ट्रिंग नहीं है, हालाँकि, यदि आवश्यक हो, तो हम इसका उपयोग मनमाने आकार के हीप चंक को आवंटित या मुक्त करने के लिए कर सकते हैं। एक बार बनने के बाद, आप मौजूदा प्रॉपर्टी सूची (property list) को नहीं हटा सकते जब तक कि विंडो नष्ट न हो, लेकिन हम नई प्रॉपर्टी को समायोजित करने के लिए इस सूची के पुन: आवंटन को नियंत्रित कर सकते हैं, इस तंत्र का उपयोग पहले से मौजूद स्थान पर छेद बनाने के लिए किया जा सकता है। और आपको बस एक नई प्रॉपर्टी सेट करने की आवश्यकता है जो पहले से मौजूद सूची में नहीं है (atomkey द्वारा अलग)।
दिलचस्प बात यह है कि डेस्कटॉप हीप उपयोगकर्ता स्थान पर मैप किया जाता है, हालाँकि यह केवल पढ़ने योग्य है। इसका मतलब है कि हम अपने फेंगशुई लेआउट को सत्यापित कर सकते हैं और सुनिश्चित कर सकते हैं कि यह सही ढंग से काम कर रहा है। पहले, हमें यह निर्धारित करना होगा कि डेस्कटॉप हीप उपयोगकर्ता मोड में किस स्थान पर मैप होता है। Tarjei के win32k पेपर में इसका उल्लेख किया गया है। TEB में एक अप्रकाशित संरचना Win32ClientInfo है जो इससे संबंधित है, जिसकी मोटे तौर पर परिभाषा इस प्रकार है:
typedef struct _CLIENTINFO {
ULONG_PTR CI_flags;
ULONG_PTR cSpins;
DWORD dwExpWinVer;
DWORD dwCompatFlags;
DWORD dwCompatFlags2;
DWORD dwTIFlags;
PDESKTOPINFO pDeskInfo;
ULONG_PTR ulClientDelta; // अपूर्ण. reactos देखें
} CLIENTINFO, *PCLIENTINFO;
जहाँ PDESKTOPINFO संरचना इस प्रकार परिभाषित है:
typedef struct _DESKTOPINFO {
PVOID pvDesktopBase;
PVOID pvDesktopLimit; // अपूर्ण. reactos देखें
} DESKTOPINFO, *PDESKTOPINFO;
पहला फ़ील्ड pvDesktopBase डेस्कटॉप हीप के कर्नेल-मोड पते को इंगित करता है, हम इसे नोट कर लेंगे। Win32ClientInfo का ulClientDelta फ़ील्ड कर्नेल-मोड और उपयोगकर्ता-मोड पतों के बीच का अंतर है, और इस जानकारी से हम वह प्राप्त कर सकते हैं जो हम चाहते हैं।
हालाँकि, हम स्वयं हीप संरचना को पार्स नहीं करना चाहते हैं, बल्कि हम एक user32 हैंडल, जैसे HWND का मान, को उपयोगकर्ता-मोड मैप किए गए पते में बदलने में सक्षम होना चाहते हैं, ताकि हम यह निर्धारित कर सकें कि यह अन्य हीप आवंटन से जुड़ा है या नहीं। इस हैंडल को खोजने के लिए, हमें एक gShared नामक संरचना ढूंढनी होगी, जो आमतौर पर uer32.dll में स्थित होती है, और Win7 और उसके बाद के संस्करणों में इसे निर्यात किया जाता है, इसलिए इसे आसानी से पाया जा सकता है।
अधिकांश सिस्टम पर यह संरचना इस प्रकार परिभाषित है:
kd> dt !tagSHAREDINFO
win32k!tagSHAREDINFO
+0x000 psi : Ptr32 tagSERVERINFO
+0x004 aheList : Ptr32 _HANDLEENTRY
+0x008 HeEntrySize : Uint4B
+0x00c pDispInfo : Ptr32 tagDISPLAYINFO
+0x010 ulSharedDelta : Uint4B
+0x014 awmControl : [31] _WNDMSG
+0x10c DefWindowMsgs : _WNDMSG
+0x114 DefWindowSpecMsgs : _WNDMSG
उपरोक्त संरचना में, aheList एक _HANDLEENTRY ऐरे को इंगित करता है, जहाँ प्रत्येक _HANDLEENTRY में कर्नेल-मोड पते की ओर इशारा करने वाला एक हैंडल होता है। हम "कर्नेल-मोड और उपयोगकर्ता-मोड पतों के बीच के अंतर" का उपयोग करके एक उपयोगी उपयोगकर्ता-मोड पता प्राप्त कर सकते हैं। दुर्भाग्य से, यह Win7 से पहले के संस्करणों पर संभव नहीं है, क्योंकि gSharedInfo निर्यात नहीं किया गया है। Tarjei के लेख में कहा गया है कि अप्रकाशित फंक्शन CsrClientConnectToServer का उपयोग gSharedInfo की एक प्रति प्राप्त करने के लिए किया जा सकता है, लेकिन मुझे कोई काम करने का उदाहरण नहीं मिला। परेशानी की बात यह है कि इस फंक्शन को लागू करने के लिए एक संरचना की आवश्यकता होती है, जिसकी लंबाई सिस्टम के अनुसार भिन्न होती है, इसलिए मेरे अनुभव के अनुसार, आप ReactOS में जो देखते हैं, उस पर पूरी तरह भरोसा नहीं कर सकते।
एक बार जब हम मैप किए गए स्थान की गणना कर लेते हैं, तो हम एक फंक्शन बना सकते हैं जो हमें बता सकता है कि विंडो ऑब्जेक्ट डेस्कटॉप हीप पर कहाँ स्थित है। फिर, यदि हम जानना चाहते हैं कि संबंधित प्रॉपर्टी सूची या टेक्स्ट स्ट्रिंग का हीप चंक कहाँ आवंटित किया गया था, तो हमें केवल उपयोगकर्ता-मोड संरचना को पार्स करना होगा।
अब, हम अंततः इस भेद्यता के शोषण के करीब पहुँच गए हैं। हमारे पास हीप चंक को नियंत्रित करने की विधि है, हीप चंक की स्थिति को सत्यापित करने की विधि है, और इस बग को ट्रिगर करने की विधि है, इसलिए अब हम अंततः चयनित tagPROPLIST प्रॉपर्टी सूची को मुक्त किए गए tagSBINFO हीप चंक से बदल सकते हैं। ध्यान दें, चूँकि tagPROPLIST केवल एक बड़ी सूची का शीर्ष है, इसलिए हम सूची के आकार को स्क्रॉल बार हीप चंक के आकार से मेल खा सकते हैं। tagPROPLIST के बाद का भाग मूल रूप से tagPROP संरचनाओं की एक ऐरे है, या प्रॉपर्टी सूची है; इसलिए, मैं ऐरे और सूची शब्दों के बीच अंतर नहीं करूंगा। tagPROPLIST संरचना 64-बिट सिस्टम पर इस प्रकार है:
kd> dt -b !tagPROPLIST
win32k!tagPROPLIST
+0x000 cEntries : Uint4B
+0x004 iFirstFree : Uint4B
+0x008 aprop : tagPROP
+0x000 hData : Ptr64
+0x008 atomKey : Uint2B
+0x00a fs : Uint2B
जैसा कि पहले उल्लेख किया गया है, प्रत्येक विंडो ऑब्जेक्ट में एक संबंधित प्रॉपर्टी सूची होती है। यह सूची SetProp() फंक्शन के माध्यम से बनाई जाती है। इसका उपयोग atomKey के मिलान से मौजूदा प्रॉपर्टी ढूंढने के लिए किया जाता है, और यदि प्रॉपर्टी मौजूद नहीं है, तो प्रॉपर्टी सूची में एक नई प्रॉपर्टी आइटम बनाई जाती है। यदि कोई प्रॉपर्टी सूची ही नहीं है, तो एक प्रॉपर्टी सूची बनाई जाती है और tagWND संरचना से जुड़ी होती है।
यदि हमने पहले से एक बग वाले tagWND को स्प्रे किया है और संबंधित tagPROPLIST आइटम बनाए हैं, तो अंतिम लेआउट चित्र 3 में दिखाया गया है:

एक बार यह सेट हो जाने के बाद, हम वह स्क्रॉल बार नियंत्रण आवंटित कर सकते हैं जिसका हम शोषण करना चाहते हैं। इससे चित्र 4 में दिखाया गया परिणाम होगा:

फिर हम स्क्रॉल बार को संचालित करके उपयोगकर्ता मोड कॉलबैक के हुक को ट्रिगर करते हैं, हुक फंक्शन में, हम विंडो को नष्ट करने का प्रयास करके tagSBINFO संरचना को मुक्त करते हैं। इससे चित्र 5 की स्थिति उत्पन्न होती है:

64-बिट पर, tagSBINFO संरचना 0x28 बाइट है, tagPROPLIST एरे आइटम 0x18 बाइट है, जिसमें 0x10 बाइट डिफ़ॉल्ट tagPROP है। इसलिए, दो एरे आइटम वाली एक प्रॉपर्टी सूची 0x28 बाइट (0x8 + 0x10 + 0x10) होगी, जो सही बैठती है। मान लें कि हमने मेमोरी को स्प्रे कर दिया है ताकि छेद भर सकें। हमें बस एक ऐसी विंडो की आवश्यकता है जिसमें प्रॉपर्टी सूची हो, और उस विंडो द्वारा tagSBINFO संरचना (जैसा कि पिछले चित्र में दिखाया गया है) को मुक्त करने के तुरंत बाद उसमें एक नया प्रॉपर्टी आइटम जोड़ना होगा। यह प्रक्रिया पहले 0x18 बाइट के tagPROPLIST हीप चंक को मुक्त करती है, चूँकि हीप को स्प्रे किया गया है, आसपास कोई मुक्त हीप चंक नहीं है, इसलिए कोई हीप मर्जिंग नहीं होती, और नए 0x28 बाइट के लिए पर्याप्त बड़ी जगह नहीं होती। इस प्रकार, अभी-अभी मुक्त किया गया tagSBINFO का स्थान (जिसका आकार बिल्कुल 0x28 बाइट है) का उपयोग किया जाता है, जैसा कि चित्र 6 में दिखाया गया है:

जब हम अपने हुक किए गए कॉलबैक फंक्शन से लौटते हैं, तो UAF ट्रिगर होगा, और कुछ बिट्स tagPROPLIST के cEntries फ़ील्ड पर लिखे जाएँगे। मूल cEntries मान 0x2 था, जो दर्शाता है कि हमने दो प्रॉपर्टी सूची आइटम बनाए थे। ओवरफ़्लो के बाद यह 0xe हो गया, जिसमें तीसरे और चौथे बिट (1 से शुरू) को 1 पर सेट कर दिया गया।
इस समय, हमने एक नया हीप ओवरफ़्लो पूरा किया है, और साथ ही इस प्रॉपर्टी सूची की आइटम संख्या को बढ़ाकर 0xc से अधिक कर दिया है। इसके बाद हम आसन्न हीप चंक को ओवरफ़्लो करेंगे, जिसे हम चरण 2 भ्रष्टाचार कहते हैं।
Udi के ब्लॉग में अब तक यही समझाया गया है। इससे पहले इसे "विशिष्ट हीप ओवरफ़्लो" कहा जाता था, हालाँकि, मेरे अनुभव में, इस बिंदु से मनमाना पता पढ़ना/लिखना या कोड निष्पादन प्राप्त करना कठिन है। हम 64-बिट पर tagPROPLIST संरचना को फिर से देखते हैं:
kd> dt -b !tagPROPLIST
win32k!tagPROPLIST
+0x000 cEntries : Uint4B
+0x004 iFirstFree : Uint4B
+0x008 aprop : tagPROP
+0x000 hData : Ptr64
+0x008 atomKey : Uint2B
+0x00a fs : Uint2B
चरण 1 के भ्रष्टाचार ने हमें एक भ्रष्ट tagPROPLIST ऐरे दिया है, जिससे हम एरे आइटम tagPROP को बढ़ा सकते हैं। tagPROPLIST में केवल दो फ़ील्ड हैं:
जब सूची में एक नया आइटम डाला जाता है, तो एक फंक्शन पहले प्रत्येक आइटम को स्कैन करता है जब तक कि उपयुक्त iFirstFree मान न मिल जाए। यदि नहीं मिलता है, तो देखें कि क्या iFirstFree का मान cEntries से बड़ा है। यदि atomKey के अनुरूप आइटम सूची में नहीं है, तो जाँचें कि क्या iFirstFree != cEntries। यदि बराबर नहीं है, तो iFirstFree इंडेक्स पर एक आइटम डालें; यदि बराबर है, तो एक नया प्रॉपर्टी सूची आवंटित करें जो डाले जाने वाले आइटम को समायोजित कर सके, और मौजूदा आइटम को कॉपी करें, और नया आइटम डालें।
atomKey फ़ील्ड LPCTSTR lpString से मेल खाती है। जैसा कि MSDN में SetProp() दस्तावेज़ में वर्णित है, कॉलर एक स्ट्रिंग पॉइंटर या 16-बिट atom मान पास कर सकता है। जब स्ट्रिंग पॉइंटर पास किया जाता है, तो प्रॉपर्टी सूची में संग्रहीत करने से पहले इसे स्वचालित रूप से atom मान में बदल दिया जाता है। चूँकि हम SetProp() फंक्शन में कोई भी atom मान पास कर सकते हैं, इससे हमारे पास इन दो बाइट्स को नियंत्रित करने की क्षमता है, लेकिन कुछ बाधाएँ हैं। यह है कि हमारा atomKey भ्रष्टाचार डेटा दोहराया नहीं जाना चाहिए, अन्यथा जब एक नया प्रॉपर्टी आइटम सेट किया जाता है, तो यह उसी atom मान वाले मौजूदा प्रॉपर्टी आइटम को बदल देगा। इसके अलावा, fs फ़ील्ड हमारे नियंत्रण में नहीं है, इसका मान 0 होता है जब atomKey का मान < 0xBFFF होता है, जो पूर्णांक atom मान से मेल खाता है। fs फ़ील्ड का मान 2 होता है जब atomKey का मान >= 0xC000 होता है।
एक और ध्यान देने वाली बात यह है कि tagPROP केवल 0xc बाइट का होता है। 64-बिट सिस्टम पर यह संरचना 0x10 बाइट पर संरेखित होती है, इसलिए एक tagPROP आइटम डालने पर अतिरिक्त 4 बाइट ऐसे होते हैं जिन्हें भ्रष्ट नहीं किया जा सकता। अंतिम महत्वपूर्ण बिंदु यह है कि एक tagPROPLIST हीप चंक के शुरुआती 8 बाइट सूची आइटम के आकार को परिभाषित करते हैं, जिसका अर्थ है कि प्रत्येक नया डाला गया tagPROP आइटम हमेशा 8 बाइट संरेखित स्थान पर लिखा जाएगा।
प्रत्येक डाले गए tagPROP के लिए, 64-बिट सिस्टम पर स्थिति इस प्रकार है:
* Offset 0x0: 8 बाइट पूरी तरह से नियंत्रण योग्य डेटा (hData)
* Offset 0x8: 2 बाइट अधिकांशतः नियंत्रण योग्य डेटा (atomKey)
* Offset 0xa: 2 बाइट अनियंत्रित डेटा (fs)
* Offset 0xc: 4 बाइट संशोधित नहीं किया जा सकने वाला डेटा (padding)
यह स्थिति 2 बिट से कहीं बेहतर है, लेकिन फिर भी पूर्ण नहीं है। जब तक हम शुरुआती 8 बाइट से कुछ ओवरराइट नहीं करते, जो पूरी तरह से नियंत्रण योग्य hData फ़ील्ड से आता है, यह बहुत सीमित है। यदि हमें आसन्न संरचना में गहरे फ़ील्ड लिखने की आवश्यकता है, तो कुछ मानों के अनियंत्रित भ्रष्टाचार से बचा नहीं जा सकता। मैंने डेस्कटॉप हीप पर विभिन्न ऑब्जेक्ट्स की खोज में कुछ समय बिताया, उपरोक्त भ्रष्टाचार सीमाओं को ध्यान में रखते हुए, मनमाना पता पढ़ने/लिखने को प्राप्त करने के लिए इस सीमा को पार करने का एकमात्र तरीका जो मैं सोच सकता हूँ, वह है tagWND के strName फ़ील्ड को भ्रष्ट करना, जो एक _LARGE_UNICODE_STRING संरचना है:
kd> dt !_LARGE_UNICODE_STRING
win32k!_LARGE_UNICODE_STRING
+0x000 Length : Uint4B
+0x004 MaximumLength : Pos 0, 31 Bits
+0x004 bAnsi : Pos 31, 1 Bit
+0x008 Buffer : Ptr64 Uint2B
यदि इस संरचना के Buffer फ़ील्ड को भ्रष्ट किया जा सके, तो हम विंडो टेक्स्ट को संचालित करके किसी दिए गए पते से MaximumLength बाइट पढ़ या लिख सकते हैं। यही मैं करने जा रहा हूँ। आपने पिछले खंडों में इस संरचना पर ध्यान दिया होगा, यह बताता है कि डेस्कटॉप हीप पर मनमाने आकार और मान का हीप चंक कैसे बनाया जाए, इसलिए वही स्थिति यहाँ लागू होती है।
अब तक, हम जानते हैं कि tagPROPLIST सूची आइटम का उपयोग करके डेटा को कैसे भ्रष्ट किया जाए, कौन से भाग हम नियंत्रित कर सकते हैं, और अधिक महत्वपूर्ण बात यह है कि हम किन सीमाओं का सामना करेंगे, जो 32-बिट और 64-बिट में भिन्न हैं। हमने 64-बिट पर जो किया वह 32-बिट पर काम नहीं करेगा। जल्द ही, हम चरण 2 के भ्रष्टाचार (यानी tagPROP संरचना के माध्यम से डेटा लिखना) से एक और भ्रष्टाचार "ऑपरेशन प्रिमिटिव" की ओर बढ़ेंगे, जिसके माध्यम से हम पूरी तरह से नियंत्रित डेटा लिख सकते हैं, जिसे मैं चरण 3 भ्रष्टाचार कहता हूँ।
योजना आसन्न tagWND के strName फ़ील्ड को भ्रष्ट करने की है। हम पहले से जानते हैं कि यह एक _LARGE_UNICODE_STRING संरचना है, लेकिन आइए tagWND संरचना के अधिक विवरण देखें, यह कुछ इस प्रकार दिखता है:kd> dt !tagWND win32k!tagWND +0x000 head : THRDESKHEAD +0x028 state : Uint4B +0x028 bHasMeun : Pos 0, 1 Bit +0x028 bHasVerticalScrollbar : Pos 1, 1 Bit +0x028 bHasHorizontalScrollbar : Pos 2, 1 Bit [SNIPPED FLAGS] +0x028 bDestroyed : Pos 31, 1 Bit +0x02c state2 : Uint4B [SNIPPED FLAGS] +0x02c bWMCreateMsgProcessed : Pos 31, 1 Bit +0x030 ExStyle : Uint4B +0x030 bWS_EX_DLGMODALFRAME : Pos 0, 1 Bit +0x030 bUnused1 : Pos 1, 1 Bit +0x030 bWS_EX_NOPARENTNOTIFY : Pos 2, 1 Bit [SNIPPED FLAGS] +0x030 bUIStateFocusRectHidden : Pos 31, 1 Bit +0x034 style : Uint4B +0x034 bReserved1 : Pos 0, 16 Bits [SNIPPED FLAGS] +0x034 bWS_POPUP : Pos 31, 1 Bit +0x038 hModule : Ptr64 Void +0x040 hMod16 : Uint2B +0x042 fnid : Uint2B +0x048 spwndNext : Ptr64 tagWND +0x050 spwndPrev : Ptr64 tagWND +0x058 spwndParent : Ptr64 tagWND +0x060 spwndChild : Ptr64 tagWND +0x068 spwndOwner : Ptr64 tagWND +0x070 rcWindow : tagRECT +0x080 rcClient : tagRECT +0x090 lpfnWndProc : Ptr64 int64 +0x098 pcls : Ptr64 tagCLS +0x0a0 hrgnUpdate : Ptr64 HRGN_ +0x0a8 ppropList : Ptr64 tagPROPLIST +0x0b0 pSBInfo : Ptr64 tagSBINFO +0x0b8 spmenuSys : Ptr64 tagMENU +0x0c0 spmenu : Ptr64 tagMENU +0x0c8 hrgnClip : Ptr64 HRGN__ +0x0d0 hrgnNewFrame : Ptr64 HRGN__ +0x0d8 strName : LARGE_UNICODE_STRING +0x0e8 cbwndExtra : Int4B +0x0f0 spwndLastActive : Ptr64 tagWND +0x0f8 hImc : Ptr64 HIMC_ +0x100 dwUserData : Uint8B +0x108 pActCtx : Ptr64 _ACTIVATION_CONTEXT +0x110 pTransform : Ptr64 _D3DMATRIX +0x118 spwndClipboardListenerNext : Ptr64 tagWND +0x120 ExStyle2 : Uint4B +0x120 bClipboardListener : Pos 0, 1 Bit [SNIPPED FLAGS] +0x120 bChildNoActivate : Pos 11, 1 Bit
ऊपर 64-बिट का स्ट्रक्चर है, जिसमें हम देख सकते हैं कि जिस _LARGE_UNICODE_STRING स्ट्रक्चर को हम ओवरराइट करना चाहते हैं, उसका ऑफसेट 0xd8 है। आप इस स्ट्रक्चर की शुरुआत में एक महत्वपूर्ण फील्ड भी देखेंगे। मैं वास्तव में इसके साथ खिलवाड़ करना चाहता था, लेकिन _THRDESKHEAD में बहुत सारे पॉइंटर्स हैं, जिनके लिए हमें सतर्क रहना होगा, और दुर्भाग्य से, हम यह नियंत्रित नहीं कर सकते कि हम कहाँ लिखते हैं, इसकी सीमाएँ हम पहले ही चर्चा कर चुके हैं।
_THRDESKHEAD स्ट्रक्चर की परिभाषा:
kd> dt !_THRDESKHEAD
win32k!_THRDESKHEAD
+0x000 h : Ptr64 Void
+0x008 cLockObj : Uint4B
+0x010 pti : Ptr64 tagTHREADINFO
+0x018 rpdesk : Ptr64 tagDESKTOP
+0x020 pSelf : Ptr64 UChar
_THRDESKHEAD की समस्या न केवल हमें भ्रमित करती है, बल्कि हमें उस एलाइनमेंट की बाधा पर भी पुनर्विचार करने के लिए मजबूर करती है। नए tagPROP लिस्ट आइटम, चाहे किसी भी ऑफसेट पर हों, हमारा राइट ऑपरेशन सीधे _LARGE_UNICODE_STRING की शुरुआत को ओवरराइट करेगा:
win32k!_LARGE_UNICODE_STRING
+0x000 Length <-- hData (पूरी तरह से नियंत्रणीय) यहाँ ओवरराइट करता है
+0x004 MaximumLength <-- और यहाँ
+0x004 bAnsi <-- और यहाँ
+0x008 Buffer <-- atomKey और fs (आंशिक रूप से नियंत्रणीय) यहाँ ओवरराइट करता है
यह स्पष्ट है कि हम Buffer पॉइंटर को ओवरराइट करना चाहते हैं ताकि हम मनमाना एड्रेस मेमोरी तक पहुँच सकें, हालांकि, भले ही हम इस स्ट्रक्चर के अन्य फील्ड को सुरक्षित रूप से हमला कर सकें, हम उस पॉइंटर को नियंत्रित नहीं कर सकते जिसकी हमें आवश्यकता है।
हम मनमाना डेटा को दूषित नहीं कर सकते, इस समस्या का समाधान अब tagPROPLIST के दूषण की समस्या नहीं है, बल्कि यह एक पूरी तरह से अलग दूषण तंत्र बन गया है।
Windows XP के बाद के संस्करणों में, यूज़र मोड बैकएंड एलोकेटर (जैसे कर्नेल डेस्कटॉप हीप) के हीप ब्लॉक हेडर (यानी _HEAP_ENTRY) को हीप पर संग्रहीत किया जाता है, और वह ब्लॉक की वास्तविक सामग्री से पहले स्थित होता है। डेस्कटॉप हीप स्वयं _HEAP स्ट्रक्चर के माध्यम से इसका प्रबंधन करता है, जो हमें इस हीप ब्लॉक का उपयोग करते समय कुछ स्वतंत्रता देता है।
_HEAP_ENTRY स्ट्रक्चर की परिभाषा इस प्रकार है:
kd> dt !_HEAP_ENTRY
ntdll!_HEAP_ENTRY
+0x000 PreviousBlockPrivateData : Ptr64 Void
+0x008 Size : Uint2B
+0x00a Flags : UChar
+0x00b SmallTagIndex : UChar
+0x00c PreviousSize : Uint2B
+0x00e SegmentOffset : UChar
+0x00f UnusedBytes : UChar
हीप ब्लॉक हेडर कुल 0x10 बाइट का होता है, पहले 8 बाइट PreviousBlockPrivateData होते हैं, जो पिछले वास्तविक ब्लॉक डेटा को समायोजित करने के लिए उपयोग किए जाते हैं, जब अनुरोधित आकार सामान्य 0x10 (8 बाइट से कम होने पर 8 बाइट के अनुसार संरेखित) से अधिक होता है। इसका Leviathan blog entry में संक्षेप में वर्णन किया गया है, इसके अलावा यूज़र मोड हीप पर प्रारंभिक लेखों में भी वर्णन है। Size और PreviousSize वर्तमान ब्लॉक आकार और पिछले ब्लॉक के आकार को दर्शाते हैं, इकाई 0x10 बाइट है। Flags यह दर्शाता है कि ब्लॉक खाली है या नहीं, आदि। यदि _HEAP में _HEAP_ENTRY का सुरक्षा मोड सक्षम है, तो SmallTagIndex ब्लॉक में डेटा का XOR चेकसम शामिल करेगा।
हालांकि एलाइनमेंट की बाधा हमारे खिलाफ है, यह वास्तव में मौजूद है। यदि आप tagPROPLIST को कॉल करते हैं, तो यह हमेशा कम से कम 0x18 बाइट होता है, फिर 0x10 बाइट का tagPROP जोड़ा जाता है। दो लिस्ट आइटम वाले 0x28 बाइट के tagPROPLIST को 0x20 बाइट के हीप ब्लॉक में रखा जाएगा, और PreviousBlockPrivateData द्वारा दर्शाए गए अतिरिक्त बाइट आसन्न हीप ब्लॉक का उपयोग करते हैं। इसका मतलब है कि जब हम तीसरा लिस्ट आइटम जोड़ते हैं, तो आसन्न हीप ब्लॉक दूषित हो जाता है, और नियंत्रणीय 8 बाइट का hData _HEAP_ENTRY के शीर्ष को ओवरराइट करेगा।
हम इसका उपयोग करना चाहते हैं ताकि हम किसी तरह से Buffer के शीर्ष स्थान पर मनमाना डेटा लिख सकें। सबसे पहले, हम हीप लेआउट को बदलते हैं ताकि हम अपने दूषित tagPROPLIST हीप ब्लॉक के करीब पहुँच सकें, नियंत्रण प्रक्रिया में, हमारे पास एक छोटा हीप ब्लॉक होता है जिसमें एक विंडो से संबंधित टेक्स्ट स्ट्रिंग होती है, हम इसे "ओवरराइट हीप ब्लॉक" कहते हैं। इस "ओवरराइट हीप ब्लॉक" के बगल में, हम एक tagWND रखते हैं ताकि हम इस tagWND को दूषित कर सकें। नीचे चित्र 7 इस प्रक्रिया को दिखाता है। ध्यान दें, हमने स्थान बचाने के लिए पहले से स्प्रे किए गए हीप ब्लॉक को छोड़ दिया है, इसलिए इन्हें अब निहित माना जाना चाहिए।

इसके बाद, हम tagPROPLIST की सूची में तीसरा tagPROP सम्मिलित करते हैं, जो _HEAP_ENTRY के अंतिम 8 बाइट और "ओवरराइट हीप ब्लॉक" के पहले 8 बाइट को ओवरराइट करेगा। इस तरह हम "ओवरराइट हीप ब्लॉक" के _HEAP_ENTRY को संशोधित कर सकते हैं, जिससे इसका आकार इसके वास्तविक आकार से बड़ा हो जाए और यह आसन्न tagWND स्ट्रक्चर को समाहित करने के लिए पर्याप्त हो।
अब हम अभी दूषित "ओवरराइट हीप ब्लॉक" को मुक्त करते हैं, ताकि हीप मैनेजर इसे फ्री लिस्ट में रख सके, जो ब्लॉक आकार के अनुरूप होता है (प्रत्येक फ्री लिस्ट एक निश्चित आकार से मेल खाती है) जो "ओवरराइट हीप ब्लॉक" के वास्तविक आकार से बड़ा होता है। फिर हम विंडो टेक्स्ट (जिसे हम पूरी तरह से नियंत्रित कर सकते हैं) को संशोधित करके इस ब्लॉक का पुन: उपयोग करते हैं। हालांकि इसमें एक छोटी सी समस्या है जिसे हल करने की आवश्यकता है। जब "ओवरराइट हीप ब्लॉक" मुक्त किया जाता है, तो हीप मैनेजर पिछले आसन्न ब्लॉक को खोजने का प्रयास करता है, जो दूषित Size फील्ड पर निर्भर करता है। हीप मैनेजर देखता है कि यह आसन्न ब्लॉक खाली है या नहीं, ताकि इसे मर्ज किया जा सके। हम किसी भी तरह से इसे नियंत्रित करना चाहते हैं, और एक उपयोग में होने का फ्लैग सेट करते हैं। अपने हीप लेआउट को थोड़ा संशोधित करके हम इसे प्राप्त कर सकते हैं। इस बिंदु पर, हम एक नकली हीप ब्लॉक रखते हैं जिसका हीप हेडर "उपयोग में" फ्लैग सेट किया गया है, और इसका PreviousSize मान दूषित Size फील्ड के मान पर सेट किया गया है, जिसे हम किसी अन्य विंडो के विंडो टेक्स्ट को आवंटित करके आसानी से प्राप्त कर सकते हैं। नया हीप लेआउट इस प्रकार है (चित्र 8):

अब हम "ओवरराइट हीप ब्लॉक" को मुक्त कर सकते हैं, इससे संबंधित विंडो के टेक्स्ट स्ट्रिंग को अपडेट करके, ताकि इसकी लंबाई मूल 0x10 बाइट से अधिक हो। इस तरह, दूषित "ओवरराइट हीप ब्लॉक" पहले मुक्त किया जाता है, और फ्री लिस्ट में रखा जाता है, हालांकि इसका आकार दूषित होता है, इसका घोषित आकार वास्तविक से बड़ा होता है। इस आकार को हम अपनी वास्तविक आवश्यकता के अनुसार समायोजित कर सकते हैं। इस तरह हमारा स्ट्रिंग डेटा इस "ओवरराइट हीप ब्लॉक" में लिखा जाता है, और फिर हम इस "ओवरराइट हीप ब्लॉक" का उपयोग करके आसन्न tagWND को मनमाना डेटा के साथ दूषित करते हैं। निम्नानुसार (चित्र 9):

यह चरण 3 का दूषण है। अब हम किसी भी वांछित डेटा के साथ strName.Buffer पॉइंटर को ओवरराइट कर सकते हैं। हालांकि, tagWND के अन्य डेटा को दूषित करने में अभी भी कुछ परेशानी है, लेकिन यह कोई समस्या नहीं है, क्योंकि डेस्कटॉप हीप को यूज़र स्पेस में मैप किया जाता है! इसलिए सब कुछ दूषित करने से पहले, हम tagWND की सभी सामग्री को पढ़ लेते हैं, strName स्ट्रक्चर की सामग्री को अपनी इच्छानुसार संशोधित करते हैं, और विंडो टेक्स्ट को संशोधित करके सभी डेटा भेजते हैं।
strName के माध्यम से हमें न केवल मनमाना रीड/राइट का "ऑपरेशन प्रिमिटिव" मिलता है, बल्कि हम strName को बार-बार संशोधित भी कर सकते हैं, जो विंडो टेक्स्ट संशोधन तंत्र द्वारा अनुमत है। जब तक लिखी गई स्ट्रिंग की लंबाई MaximumLength के मान से अधिक नहीं है, तब तक उसी हीप ब्लॉक का उपयोग जारी रखा जा सकता है। इसलिए, हर बार जब हम किसी स्थान से मान पढ़ने के लिए strName का पता बदलना चाहते हैं, तो हम एक नई स्ट्रिंग संलग्न करते हैं और अपने डेटा को "ओवरराइट हीप ब्लॉक" में अपडेट करते हैं। यह पुन: उपयोग चित्र 10 में दिखाया गया है। ध्यान दें, मैंने फिर से आकृति के दृश्य को बढ़ाया है ताकि प्रत्येक दूषण को विस्तृत रूप से दिखाया जा सके।

इसका मतलब है कि हमें अंततः केवल दो अतिरिक्त चीजों को दूषित करने की आवश्यकता है (मूल tagPROPLIST लिस्ट आइटम के अलावा):
अब, यदि हम मेमोरी के किसी स्थान से कुछ बाइट पढ़ना चाहते हैं, तो हम InternalGetWindowText() फ़ंक्शन के माध्यम से विंडो टेक्स्ट क्वेरी करते हैं, जहाँ दूषित strName की प्रविष्टि है। हम Length फील्ड द्वारा घोषित बाइट्स की संख्या पढ़ सकते हैं। इसी तरह, यदि हम मेमोरी में किसी मनमाना स्थान पर लिखना चाहते हैं, तो हम NtUserDefSetText() फ़ंक्शन का उपयोग करके दूषित विंडो टेक्स्ट को अपडेट करते हैं, लेकिन लिखी गई मात्रा MaximumLength फील्ड द्वारा घोषित मान (जिसे हम सेट भी कर सकते हैं) से अधिक नहीं होनी चाहिए। इस तरह मौजूदा बफर का पुन: उपयोग किया जाता है, और यह हमारे इच्छित मेमोरी एड्रेस पर इंगित करता है।
हालांकि Windows Vista से शुरू करके, यूज़र मोड बैकएंड एलोकेटर ने हीप एनकोडिंग (heap encoding) का उपयोग किया है, लेकिन डेस्कटॉप हीप ने इसे Windows 8 तक कभी सक्षम नहीं किया। इसलिए Windows 8 के बाद के सिस्टम पर, जब हम "ओवरराइट हीप ब्लॉक" को ओवरराइट करते हैं तो एक बाधा उत्पन्न होती है। हालांकि, वास्तव में, _HEAP स्ट्रक्चर में जो कुकी होती है, वह पूरे हीप हेडर को एनकोड करने के लिए उपयोग की जाती है, इसलिए हम यूज़र स्पेस में मैप किए गए डेस्कटॉप हीप से इस कुकी को पढ़ सकते हैं, और फिर इसका उपयोग "ओवरराइट हीप ब्लॉक" के हेडर को एनकोड करने के लिए कर सकते हैं, एनकोडिंग विधि एलोकेटर कोड के रिवर्स इंजीनियरिंग द्वारा प्राप्त की जाती है, और इसके संचालन का अनुकरण करके, एलोकेटर इस ऑपरेशन को स्वीकार कर सकता है।
सबसे पहले ध्यान देने योग्य बात यह है कि 32-बिट सिस्टम पर tagPROP स्ट्रक्चर 8 बाइट का होता है, न कि 64-बिट सिस्टम पर 0xc बाइट, और हमारे द्वारा नियंत्रित hData फील्ड केवल 4 बाइट है, न कि 64-बिट सिस्टम पर 8 बाइट। साथ ही कोई अतिरिक्त पैडिंग बाइट नहीं है, 64-बिट सिस्टम पर 8 बाइट का पैडिंग है, इसलिए पूरा स्ट्रक्चर ठीक 8 बाइट का है। इसका मतलब है कि हम आसन्न हीप ब्लॉक हेडर को पूरी तरह से दूषित नहीं कर सकते, यदि हम केवल डेटा को आंशिक रूप से नियंत्रित कर सकते हैं। विंडोज के कुछ संस्करणों पर यह संभव है, क्योंकि हम सबसे महत्वपूर्ण फील्ड को नियंत्रित कर सकते हैं, लेकिन Windows 8 और 8.1 पर हीप हेडर एनकोडेड होता है, और हम अंततः fs फील्ड के माध्यम से हीप हेडर के एक हिस्से को असुरक्षित रूप से ओवरराइट कर सकते हैं। 32-बिट सिस्टम पर _HEAP_ENTRY हेडर समान दिखता है, लेकिन इसमें PreviousBlockPrivateData फील्ड नहीं है।
हम अभी भी tagWND के सभी हिस्सों को दूषित नहीं कर सकते, क्योंकि ट्रंकेटेड पॉइंटर्स से बचा नहीं जा सकता। और मुझे अभी भी इसके लिए कोई ऑब्जेक्ट नहीं मिला है, यह देखते हुए कि _LARGE_UNICODE_STRING 64-बिट सिस्टम पर अच्छी तरह से काम करता है, मैंने सोचा कि 32-बिट सिस्टम पर भी इसका उपयोग करूं।
मेरा विचार यह है कि यदि हम इंडेक्स मान बढ़ाकर tagPROPLIST स्ट्रक्चर के iFirstFree फील्ड (प्रॉपर्टी लिस्ट में पहले मुक्त प्रॉपर्टी आइटम का इंडेक्स) को दूषित कर सकते हैं, तो यह हीप पर दूर के स्थान पर इंगित कर सकता है। उदाहरण के लिए, हम इसे tagWND.strName के शीर्ष पर इंगित करा सकते हैं। चित्र 11 इस विचार को दर्शाता है:

प्रक्रिया को स्पष्ट करने के लिए, अब हम दो tagPROPLIST स्ट्रक्चर का उपयोग करते हैं, UAF के लिए "प्रॉपर्टी लिस्ट A", और दूसरा "प्रॉपर्टी लिस्ट B"। हमें यह जानने की आवश्यकता है कि "प्रॉपर्टी लिस्ट A" में डाले गए tagPROP के कौन से हिस्से "प्रॉपर्टी लिस्ट B" के iFirstFree फील्ड को ओवरराइट करेंगे। हमें यह भी ध्यान रखना होगा कि हम एक बार में केवल 8 बाइट लिख सकते हैं, इसलिए हमें "प्रॉपर्टी लिस्ट A" में कम से कम एक अतिरिक्त tagPROP डालना होगा, पहला आसन्न हीप हेडर को दूषित करने के लिए, दूसरा "प्रॉपर्टी लिस्ट B" के tagPROPLIST फील्ड को हिट करने के लिए। ये विभिन्न ऑपरेटिंग सिस्टम और विभिन्न हीप ब्लॉक आकारों के अनुसार बदल सकते हैं, और मेरे शोषण में विभिन्न हीप लेआउट के अनुकूल होना चाहिए। चित्र 12 दिखाता है कि हम कैसे दूषित करते हैं। ध्यान दें, आकृति में पहले tagPROPLIST को अलग-अलग फील्ड में विभाजित नहीं किया गया है, इसलिए tagPROP[0] निहित है। हालांकि, दूसरे tagPROPLIST में, इसके आंतरिक सदस्यों को हमारी दूषण प्रक्रिया को दिखाने के लिए विभाजित किया गया है। यही कारण है कि tagPROP[0] दिखाया गया है:

सबसे पहले हम ध्यान देते हैं कि यदि हम प्रत्येक tagPROP पर 8 बाइट लिखते हैं, तो इसका मतलब है कि हम iFirstFree पर केवल आंशिक रूप से नियंत्रण कर सकते हैं (चूंकि यह atomKey और fs फील्ड से आता है), यही सबसे अधिक हमारी चिंता का विषय है। क्योंकि हम atomKey के मान के माध्यम से कम से कम दो महत्वपूर्ण बाइट को पूरी तरह से नियंत्रित कर सकते हैं, जब यह मान काफी छोटा होता है, तो fs फील्ड 0 हो जाएगा। इसलिए हम hData के मान का उपयोग cEntries को उचित मान पर ओवरराइट करने के लिए करते हैं, और atomKey का उपयोग iFirstFree को tagWND पर इंगित करने के लिए करते हैं, जहाँ strName.Buffer पॉइंटर है जिसे हम ओवरराइट करना चाहते हैं। यदि हम सीधे Length और MaximumLength के मान को ओवरराइट नहीं कर सकते हैं, तो हम लक्ष्य विंडो को एक स्ट्रिंग पूर्व-आवंटित कर सकते हैं ताकि यह सुनिश्चित हो सके कि इसकी लंबाई पहले से ही एक निश्चित मान पर सेट है।
आइए देखें कि 32-बिट tagWND स्ट्रक्चर से हम क्या प्राप्त कर सकते हैं। ध्यान दें, इस बार मैंने -b पैरामीटर का उपयोग किया है ताकि हम strName में Buffer के ऑफसेट की आसानी से गणना कर सकें।
kd> dt -b !tagWND
win32k!tagWND
+0x000 head : _THRDESKHEAD
+0x000 h : Ptr32
+0x004 cLockObj : Uint4B
+0x008 pti : Ptr32
+0x00c rpdesk : Ptr32
+0x010 pSelf : Ptr32
+0x014 state : Uint4B
+0x014 bHasMeun : Pos 0, 1 Bit
[SNIPPED FLAGS]
+0x014 bDestroyed : Pos 31, 1 Bit
+0x018 state2 : Uint4B
[SNIPPED FLAGS]
+0x018 bWMCreateMsgProcessed : Pos 31, 1 Bit
+0x01c ExStyle : Uint4B
+0x01c bWS_EX_DLGMODALFRAME : Pos 0, 1 Bit
[SNIPPED FLAGS]
+0x01c bUIStateFocusRectHidden : Pos 31, 1 Bit
+0x020 style : Uint4B
+0x020 bReserved1 : Pos 0, 16 Bits
[SNIPPED FLAGS]
+0x020 bWS_POPUP : Pos 31, 1 Bit
+0x024 hModule : Ptr32
+0x028 hMod16 : Uint2B
+0x02a fnid : Uint2B
+0x02c spwndNext : Ptr32
+0x030 spwndPrev : Ptr32
+0x034 spwndParent : Ptr32
+0x038 spwndChild : Ptr32
+0x03c spwndOwner : Ptr32
+0x040 rcWindow : tagRECT
+0x000 left : Int4B
+0x004 top : Int4B
+0x008 right : Int4B
+0x00c bottom : Int4B
+0x050 rcClient : tagRECT
+0x000 left : Int4B
+0x004 top : Int4B
+0x008 right : Int4B
+0x00c bottom : Int4B
+0x060 lpfnWndProc : Ptr32
+0x064 pcls : Ptr32
+0x068 hrgnUpdate : Ptr32
+0x06c ppropList : Ptr32
+0x070 pSBInfo : Ptr32
+0x074 spmenuSys : Ptr32
+0x078 spmenu : Ptr32
+0x07c hrgnClip : Ptr32
+0x080 hrgnNewFrame : Ptr32
+0x084 strName : _LARGE_UNICODE_STRING
+0x000 Length : Uint4B
+0x004 MaximumLength : Pos 0, 31 Bits
+0x004 bAnsi : Pos 31, 1 Bit
+0x008 Buffer : Ptr32
+0x090 cbwndExtra : Int4B
+0x094 spwndLastActive : Ptr32
+0x098 hImc : Ptr32
+0x09c dwUserData : Uint4B
+0x0a0 pActCtx : Ptr32
+0x0a4 pTransform : Ptr32
+0x0a8 spwndClipboardListenerNext : Ptr32
+0x0ac ExStyle2 : Uint4B
+0x0ac bClipboardListener : Pos 0, 1 Bit
[SNIPPED FLAGS]
+0x0ac bChildNoActivate : Pos 11, 1 Bit
strName का ऑफसेट 0x84 है, Buffer का ऑफसेट 0x8c है। हम जानते हैं कि हमारे पास tagPROP लिस्ट आइटम का इंडेक्स है, और हम जानते हैं कि हम 8 बाइट लिख सकते हैं। इसलिए हम आसानी से जान सकते हैं कि iFirstFree, MaximumLength द्वारा इंगित विंडो के 0x88 ऑफसेट एड्रेस पर इंडेक्स करता है या नहीं। चूंकि हम Buffer के केवल दो बाइट को नियंत्रित कर सकते हैं, इसलिए राइट ऑपरेशन संभव नहीं है, यह देखते हुए कि हमारा लक्ष्य इसे अपने मनमाना रीड/राइट के "ऑपरेशन प्रिमिटिव" के रूप में उपयोग करना है, यह परिणाम स्वीकार्य नहीं है। यदि हम एक इंडेक्स लिखते हैं जो 0x90 पर इंगित करता है, तो हम cbwndExtra को ओवरराइट कर देंगे, जो हमारे उद्देश्य के लिए नहीं है।
पहले हीप फेंगशुई करते समय हमने जो कुछ नियंत्रित किया था, उस पर पुनर्विचार करते हुए, देखते हैं कि tagWND में कोई दिलचस्प ऑफसेट है जिसे हम नियंत्रित कर सकते हैं। tagWND में ऑफसेट 0x70 पर pSBInfo फील्ड है। यह ऑफसेट 8 से विभाज्य है, इसलिए हम नकली tagPROP में hData के डेटा के हिस्से से इस पॉइंटर को ओवरराइट कर सकते हैं।
क्या हम pSBInfo को सीधे उसी tagWND स्ट्रक्चर में strName पर इंगित करा सकते हैं? शायद हम स्क्रॉल बार API का उपयोग करके strName को दूषित कर सकते हैं ताकि हमारे उद्देश्य को प्राप्त किया जा सके।
pSBInfo एक tagSBINFO स्ट्रक्चर पर इंगित करता है, जिसका उल्लेख शुरुआती UAF प्रक्रिया में किया गया था।
kd> dt -b !tagSBINFO
win32k!tagSBINFO
+0x000 WSBflags : Int4B
+0x004 Horz : tagSBDATA
+0x000 posMin : Int4B
+0x004 posMax : Int4B
+0x008 page : Int4B
+0x00c pos : Int4B
+0x014 Vert : tagSBDATA
+0x000 posMin : Int4B
+0x004 posMax : Int4B
+0x008 page : Int4B
+0x00c pos : Int4B
हमें याद है, WSBflags हमें अधिक नियंत्रण नहीं दे सकता, लेकिन हम कम से कम जानते हैं कि जब स्क्रॉल बार सक्षम होता है तो यह 1 सेट होता है, और जब स्क्रॉल बार अक्षम होता है तो इसे 0 पर सेट किया जाता है। इस फ्लैग फील्ड को मनमाना मान पर सेट नहीं किया जा सकता है, प्रासंगिक फंक्शनलिटी के रिवर्स इंजीनियरिंग के माध्यम से, हम पाते हैं कि यदि स्क्रॉल बार की स्थिति नहीं बदली जाती है, तो यह फील्ड अपरिवर्तित रहता है। tagSBDATA स्ट्रक्चर में मान अधिक दिलचस्प लगते हैं। यदि हम SetScrollInfo() के दस्तावेज़ पढ़ते हैं, तो हम इन मानों के अर्थ को अच्छी तरह से समझ सकते हैं। ऐसा लगता है कि हम SCROLLINFO स्ट्रक्चर के माध्यम से SetScrollInfo() को पैरामीटर सेट कर सकते हैं। जब तक हम जिस विंडो को दूषित करना चाहते हैं, उसके पास एक स्क्रॉल बार कंट्रोल है, हम सीधे pSBInfo पॉइंटर पर काम कर सकते हैं (यह संबंधित विंडो कंट्रोल को एक विशेष विंडो संदेश भेजेगा)। जाहिर है कि हम बिना शर्त posMin और posMax के मान को नियंत्रित कर सकते हैं। page और pos फील्ड के साथ थोड़ी परेशानी है, क्योंकि वे एक निश्चित सीमा के भीतर प्रतिबंधित हैं, अब हम इनसे बचने की कोशिश कर रहे हैं। SCROLLINFO स्ट्रक्चर को SIF_RANGE फ्लैग सेट करें, यह घोषित करने के लिए कि हम अधिकतम और न्यूनतम मान कहाँ सेट करना चाहते हैं।
हम मनमाना डेटा के साथ Buffer को ओवरराइट करना चाहते हैं, इसका मतलब है कि हम posMin को इस पर ओवरराइट करना चाहते हैं, इसलिए हम pSBInfo को strName.MaximumLength पर इंगित करा सकते हैं। जब तक हम स्क्रॉल बार को सक्षम या अक्षम नहीं करते हैं, WSBflags फील्ड को ओवरराइट नहीं किया जाएगा, यह strName.MaximumLength की अखंडता सुनिश्चित करता है। इसका मतलब है कि हम posMin (SCROLLINFO के nMin के माध्यम से) को कैसे भी सेट करें, यह Buffer को ओवरराइट करेगा, और posMax को cbwndExtra पर लिखा जाएगा। यह कोई बड़ी समस्या नहीं है; 64-बिट सिस्टम पर, हम इस मान को पहले से पढ़ सकते हैं और बाद में इसे पुनर्स्थापित कर सकते हैं। ओवरफ्लो का सामान्य विचार चित्र 13 में दिखाया गया है:

अब हम चित्र 14 के साथ 32-बिट सिस्टम पर हमले की प्रक्रिया का वर्णन करते हैं। किसी भी UAF से दूर के एड्रेस को दूषित करने से पहले, आइए पहले आरेख में संबंधित हीप ब्लॉक और हीप लेआउट को देखें। अब जब हम अधिक विवरण जानते हैं, तो आगे क्या करना है यह स्पष्ट हो जाता है।

इसके बाद, "प्रॉपर्टी लिस्ट A" में दो प्रॉपर्टी आइटम डालें, यह "प्रॉपर्टी लिस्ट A" के पास के डेटा को दूषित करेगा, पिछले UAF दूषण के कारण, और साथ ही "प्रॉपर्टी लिस्ट A" के iFirstFree को pSBInfo पर इंगित करेगा। ध्यान दें, यह आस-पास के pSBInfo मान को भी दूषित करेगा, लेकिन हम इसे पहले से पढ़ सकते हैं, ताकि दूषित होने के बाद इसे पुनर्स्थापित किया जा सके।

हम "प्रॉपर्टी लिस्ट B" में एक नया tagPROP डालते हैं, जिसका atom पहचानकर्ता सूची में मौजूद से अलग है, ताकि यह tagPROP अगले खाली इंडेक्स आइटम पर डाला जाए। इस प्रकार pSBInfo दूषित हो जाता है, जो उसी tagWND में strName.MaximumLength पर इंगित करता है।

अंत में, स्क्रॉल बार को रिफ्रेश करें ताकि strName.Buffer फील्ड दूषित हो जाए (चित्र 17 के अनुसार):

यह जान लें कि 64-बिट मामले के विपरीत, हम strName के लंबाई मान को दूषित नहीं कर सकते। हम एक उपयुक्त लंबाई का विंडो टेक्स्ट स्ट्रिंग पूर्व-आवंटित कर सकते हैं ताकि इसका मान पहले से ही उपयोग में हो। बाद में, चाहे हम कर्नेल एड्रेस से कुछ डेटा पढ़ना या लिखना चाहते हैं, हमें केवल SetScrollInfo() को कॉल करके लक्ष्य विंडो पर Buffer के मान को अपडेट करना होगा, और फिर विंडो टेक्स्ट API का उपयोग करके ऑपरेशन करना होगा।
अब हमारे पास 32-बिट सिस्टम पर एक पुन: प्रयोज्य मनमाना एड्रेस रीड/राइट का "ऑपरेशन प्रिमिटिव" है!
अब से सब कुछ मान लिया गया है कि हमारे पास एक मनमाना रीड/राइट का "ऑपरेशन प्रिमिटिव" (primitive) है। इसलिए, जब मैं कहता हूँ कि किसी मान को लीक/पढ़ना या ओवरराइट करना, तो इसका मतलब है कि पिछले दूषण चरणों में स्थापित इस "ऑपरेशन प्रिमिटिव" को निष्पादित करना। यह "ऑपरेशन प्रिमिटिव" दोनों प्लेटफार्मों पर मोटे तौर पर समान है। बस इतना करना बाकी है कि एक फंक्शन पॉइंटर को ओवरराइट करना है और इसे कहीं ShellCode लोडेड पर इंगित करना है। सामान्य तरीका nt!HalDispatchTable के दूसरे आइटम को ओवरराइट करना है, जो HalQuerySystemInformation() फंक्शन से मेल खाता है। फिर यूज़र मोड में NtQueryInternalProfile() फंक्शन को कॉल करके इसे ट्रिगर करना है।
हमें कर्नेल मॉड्यूल के लोड बेस को जानने की आवश्यकता है, ताकि nt!HalDispatchTable के कर्नेल एड्रेस की गणना की जा सके। इसके लिए, हम यूज़र मोड में NtQuerySystemInformation() को कॉल करके मॉड्यूल जानकारी प्राप्त कर सकते हैं, इन जानकारियों में मॉड्यूल बेस शामिल है।
// एनुमरेशन वैल्यू 11 SystemModuleInformation को दर्शाता है, यह अनडॉक्यूमेंटेड है...
rc = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)11, pModuleInfo, 0x100000, NULL);
उसके बाद, हम ntoskrnl.exe को यूज़र मोड में लोड करते हैं ताकि nt!HalDispatchTable का ऑफसेट खोज सकें, इस प्रकार हमें इसका कर्नेल स्पेस एड्रेस मिल जाता है। फिर "रीड ऑपरेशन प्रिमिटिव" का उपयोग करके HaliQuerySystemInformation() का कर्नेल एड्रेस पढ़ें (यह फंक्शन अनएक्सपोर्टेड है) ताकि इसे संशोधित किया जा सके, फिर "राइट ऑपरेशन प्रिमिटिव" का उपयोग करके इस फंक्शन के पॉइंटर को दूषित करें, ताकि यह shellcode के एड्रेस (जो कर्नेल एड्रेस स्पेस या यूज़र एड्रेस स्पेस हो सकता है, बाद में विस्तृत) पर इंगित करे। पढ़े और लिखे गए बाइट्स की संख्या 32-बिट और 64-बिट सिस्टम पर समान है।
Windows 8 और 8.1 ने SMEP के लिए समर्थन प्रस्तुत किया है, और कुछ सुरक्षा उत्पाद Windows 7 पर भी इसे सक्षम कर सकते हैं, इसलिए हम मानते हैं कि यह निश्चित रूप से मौजूद है। SMEP हमें कर्नेल विशेषाधिकारों के साथ यूज़र स्पेस में कोड निष्पादित करने से रोकता है, जिससे nt!HalDispatchTable के लिस्ट आइटम को संशोधित करके इसे यूज़र स्पेस एड्रेस पर इंगित करना अनुपलब्ध हो जाता है। इसलिए, हम चाहते हैं कि यह कर्नेल स्पेस के किसी नियंत्रणीय स्थान पर इंगित करे, जहाँ पर कोड cr4 रजिस्टर के मान को संशोधित करके SMEP को अक्षम कर सके, ताकि हम यूज़र एड्रेस स्पेस पर जा सकें। MWR के लेख में 64-बिट के लिए एक दिलचस्प ट्रिक प्रस्तुत की गई है, जिसमें पेज टेबल एंट्री को स्वयं मैप करके, किसी भी वर्चुअल एड्रेस के लिए एक संबंधित कर्नेल-मोड वैध एड्रेस प्राप्त किया जाता है। फिर "राइट ऑपरेशन प्रिमिटिव" के माध्यम से सीधे पेज टेबल एंट्री को संशोधित करके इसके मास्क बिट्स को संशोधित किया जाता है। मैंने इस ट्रिक को 32-बिट सिस्टम पर पोर्ट किया है, लेकिन PAE सक्षम और अक्षम सिस्टम में कुछ अंतर हैं।
इसे प्राप्त करने का स्पष्ट तरीका एक यूज़र-मोड एड्रेस को कर्नेल स्पेस में मैप करना है, फिर "राइट ऑपरेशन प्रिमिटिव" का उपयोग करके पेज टेबल एंट्री को सिस्टम अनुमति के साथ सेट करना है, न कि यूज़र अनुमति के साथ। यह हमारा पहला कदम है। जब मैंने इसे Windows 8 पर लागू किया तो मुझे एक दिलचस्प समस्या का सामना करना पड़ा। Windows 8 और उसके बाद का डेस्कटॉप मैनेजर (dwm.exe) समय-समय पर डेस्कटॉप पर विंडो को स्कैन करता है और उनके नाम क्वेरी करता है, मैंने इसके कारण की जांच नहीं की है। यह ऑपरेशन विंडो को कोई संदेश नहीं भेजता है, लेकिन इसका एक संबंधित विंडो हैंडलिंग फंक्शन है, और फंक्शन GetInternalWindowText() को कॉल करता है। इसलिए, समस्या यह है कि विंडो स्ट्रक्चर के strName फील्ड का उपयोग करके shellcode वाले मेमोरी के पेज टेबल एंट्री को संशोधित करना है, यह मेमोरी हमारी अपनी प्रक्रिया स्पेस के पेज टेबल का हिस्सा है। जब dwm.exe कर्नेल से विंडो का नाम प्राप्त करता है, तो संशोधित पेज टेबल एंट्री के कारण कर्नेल strName.Buffer की शून्यता की जाँच करता है, और इस प्रकार अप्रत्यक्ष रूप से उस एड्रेस को संदर्भित करता है, जो अमान्य होने पर सिस्टम क्रैश का कारण बनेगा।