
ड्राइवर रिवर्स और एक्सप्लॉइटेशन
⚠️ चेतावनी : यह प्रोजेक्ट पूर्णतः शैक्षिक और प्रदर्शनात्मक है। इसका दुर्भावनापूर्ण संदर्भ में उपयोग करने का कोई इरादा नहीं है। उद्देश्य reverse engineering की पद्धति और Windows ड्राइवर के शोषण के चरणों को सीखना है।
मैं यहाँ उस प्रक्रिया को समझा रहा हूँ जो मैंने d1rk(SaadAhla) https://github.com/SaadAhla द्वारा प्रस्तावित अभ्यास को हल करने के लिए अपनाई थी, जिसमें एक वैध, हस्ताक्षरित ड्राइवर पर reverse engineering और शोषण करना शामिल है जो blocklists (HVCI, LOLBIN...) में नहीं है। एक C प्रोग्राम उपलब्ध है जो इस Kernel-mode ड्राइवर के माध्यम से सिस्टम पर किसी भी सक्रिय प्रोसेस को समाप्त कर सकता है; मैं इसके संचालन का विवरण थोड़ा नीचे दे रहा हूँ।

📃 उपयोग : DriverKiller.exe <प्रोसेस_नाम.exe> [-d]
विकल्प -d : शोषण के बाद सिस्टम से सेवा और ड्राइवर को हटाने की अनुमति देता है।
लक्ष्य मशीन पर testsigning मोड सक्षम होना चाहिए क्योंकि ड्राइवर का प्रमाणपत्र समाप्त हो चुका है।
भाग 1 - रिवर्स इंजीनियरिंग :
अभ्यास में एक .sys फ़ाइल दी गई है, जिसका नाम उसके SHA-256 हैश के साथ रखा गया है।
पहला चरण इस फ़ाइल को IDA के साथ खोलना है।
IDA निःशुल्क उपलब्ध है। लाइसेंस उत्पन्न करने और सॉफ़्टवेयर डाउनलोड करने के लिए बस Hex-Rays की वेबसाइट पर जाना होगा।
हम ड्राइवर की IAT (Import Address Table) को सूचीबद्ध करके शुरू करते हैं और उस API कॉल की खोज करते हैं जिसमें हमारी रुचि है : ZwTerminateProcess।
ZwTerminateProcess पर डबल-क्लिक करने पर, IDA हमें इस फ़ंक्शन के संकलित कोड पर पुनर्निर्देशित करता है। एंट्री का चयन करके और cross-references प्रदर्शित करके, हमें ड्राइवर के उन फ़ंक्शनों की सूची मिलती है जो इसे कॉल करते हैं।
हम देखते हैं कि sub_12EF4 फ़ंक्शन, ऑफ़सेट 1CE पर, ZwTerminateProcess का उपयोग करता है। डबल-क्लिक के बाद, IDA इसका संकलित कोड प्रदर्शित करता है।
डीकंपाइल किया गया कोड ZwOpenProcess (जो लक्ष्य प्रोसेस के लिए एक हैंडल खोलता है) और ZwTerminateProcess (जो इस हैंडल के माध्यम से प्रोसेस को समाप्त करता है) के कॉल प्रकट करता है।
ZwOpenProcess के दस्तावेज़ (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess) को देखने पर, हम पाते हैं कि ClientID पैरामीटर एक पॉइंटर है जो लक्षित प्रोसेस के PID को इंगित करता है।
ऊपर वाली पंक्ति में, ClientId.UniqueProcess को v22 चर के साथ प्रारंभ किया गया है। इसे ठीक ऊपर परिभाषित किया गया है :
v22 = (void )((_QWORD *)i + 10);
इस असाइनमेंट को समझने के लिए, हमें i चर और +10 फ़ील्ड की पहचान करनी होगी।
इस फ़ंक्शन में थोड़ा ऊपर, हम SYSTEM_PROCESS_INFORMATION पैरामीटर के साथ ZwQuerySystemInformation का कॉल देखते हैं। हम यह भी समझते हैं कि i, v6 चर के साथ इस संरचना की प्रविष्टियों पर पुनरावृत्ति करता है।
ZwQuerySystemInformation के दस्तावेज़ के अनुसार : (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation), यह फ़ंक्शन एक सरणी लौटाता है जिसमें सिस्टम पर प्रत्येक सक्रिय प्रोसेस के लिए एक प्रविष्टि होती है।
SYSTEM_PROCESS_INFORMATION संरचना का वर्णन यहाँ किया गया है : https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;
स्मरण : Windows x64 पर कुछ प्रकारों के आकार
typedef struct _UNICODE_STRING {
USHORT Length; -> 2
USHORT MaximumLength; -> + 2 = 4
PWSTR Buffer; -> + 8 = 12 (12 n'est pas un multiple de 8 donc padding de 4 ajouté en amont de Buffer) = 16
} UNICODE_STRING;
UniqueProcessId के ऑफ़सेट की गणना :
ULONG NextEntryOffset; -> 4
ULONG NumberOfThreads; -> + 4 = 8
BYTE Reserved1[48]; -> + 48 = 56
UNICODE_STRING ImageName; -> + 16 = 72
KPRIORITY BasePriority; -> + 4 = 76 (76 n'est pas un multiple de 8 donc padding de 4 ajouté) = 80
HANDLE UniqueProcessId; -> + 8 = 88
UniqueProcessId सदस्य इस प्रकार ऑफ़सेट 0x50 (80 दशमलव) पर है।
हमारे चर v22 के असाइनमेंट को देखते हुए, हम पाते हैं कि i को QWORD (8 बाइट्स) पॉइंटर में कास्ट किया गया है
v22 = (void )((_QWORD *)i + 10);अतः
v22 = i का पता + 10 * 8 = 80 बाइट्स। इसलिए इस चर में SYSTEM_PROCESS_INFORMATION संरचना से प्राप्त PID ही होता है।
यह जानने के लिए कि ZwTerminateProcess को कौन सा PID दिया जाएगा, हमें इस असाइनमेंट के आसपास की शर्त का विश्लेषण करना होगा।
हम देखते हैं कि पहले प्रोसेस की इमेज का नाम प्राप्त किया जाता है :
v9 = (wchar_t )((_QWORD *)i + 8);क्योंकि
v9 = i का पता + 8 × 8 = 64 बाइट्स। यह ImageName सदस्य के Buffer के अनुरूप है, क्योंकि यह सदस्य ऑफ़सेट 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64 पर स्थित है।
नीचे दिए गए संचालनों और लूपों को देखते हुए, हम यह परिकल्पना कर सकते हैं कि तर्क के रूप में पारित प्रोसेस के नाम (a2) और सिस्टम पर सक्रिय प्रोसेसेस v9/String के बीच तुलना की जाती है।
sub_1C078(String, v9, (int)v13); v17 = strupr(a2); v18 = strupr(String);
इस प्रकार a2 पैरामीटर ही वह है जिसमें ZwTerminateProcess के माध्यम से समाप्त किए जाने वाले प्रोसेस का नाम होना चाहिए।
हम ध्यान देते हैं कि a2 फ़ंक्शन sub_12EF4 का एक पैरामीटर है। आगे बढ़ने के लिए, हमें इस फ़ंक्शन के संदर्भों की जाँच करनी होगी (बेहतर पठनीयता के लिए मैंने इसे ZwTerminateProcessCaller नाम दिया है)।
हम देखते हैं कि ZwTerminateProcessCaller को फ़ंक्शन sub_13624 द्वारा ऑफ़सेट 61A पर कॉल किया जाता है।
