
प्रूफ-ऑफ-कॉन्सेप्ट शोषण CVE-2023-21768 के लिए, एक विंडोज एंसिलरी फंक्शन ड्राइवर (AFD.sys) मनमाना कर्नेल लिखने की कमजोरी जो I/O रिंग के माध्यम से स्थानीय विशेषाधिकार वृद्धि को सक्षम करती है।
CVE-2023-21768 के विस्तृत विवरण के अनुसार, जो Microsoft Security Response Center (MSRC) द्वारा प्रकाशित किया गया है, यह भेद्यता Ancillary Function Driver (AFD) में मौजूद है, जिसका सिस्टम फ़ाइल नाम afd.sys है। AFD मॉड्यूल WinSock API का कर्नेल एंट्री पॉइंट है। इस विश्लेषण में मैं इसका उपयोग विंडोज 11 पर विशेषाधिकार वृद्धि (LPE) के लिए करूंगा।
दो संस्करण afd.sys डाउनलोड करें Winbindex से, एक संस्करण पैच से पहले का नवीनतम, और एक पैच के बाद का संस्करण। फिर इन दो संस्करणों की तुलना करने के लिए Bindiff का उपयोग करें।

दो संस्करणों की सामान्य तुलना से पता चलता है कि केवल एक फ़ंक्शन में अंतर है, वह है AfdNotifyRemoveIoCompletion। इस फ़ंक्शन के दो संस्करणों के बीच अंतर को अधिक विस्तार से देखें।

दोनों संस्करणों के बीच बहुत अधिक अंतर नहीं है। पोस्ट-पैच संस्करण में, पैरामीटर सेट करने और ProbeForWrite फ़ंक्शन को कॉल करने के लिए अतिरिक्त असेंबली निर्देश जोड़े गए हैं। Microsoft के दस्तावेज़ के अनुसार, यह फ़ंक्शन यह जांचने के लिए उपयोग किया जाता है कि कोई पता वास्तव में यूज़र-मोड का है, उसमें लिखने की अनुमति है, और सही ढंग से संरेखित (aligned) है या नहीं। इस कोड का अधिक विस्तृत विश्लेषण:
afd.sys version 10.0.22621.608

-post-patch afd.sys version 10.0.22621.1105

दोनों r15_1 के मान की जाँच करते हैं, यदि यह 0 के बराबर है तो var_304 के मान को struct_1 के एक फ़ील्ड पर निर्दिष्ट पॉइंटर पर लिखते हैं। यदि यह 0 नहीं है, तो ProbeForWrite को कॉल किया जाएगा ताकि यह सुनिश्चित किया जा सके कि पॉइंटर एक वैध पते की ओर इशारा करता है। pre-patch संस्करण में, उसके बाद ही var_304 का मान पॉइंटर पर लिखा जाता है, यह जाँच गायब थी। इस पैच से, हम अनुमान लगा सकते हैं कि हम arg3_1->field_18 के नियंत्रित मान के साथ इस कोड को कॉल कर सकते हैं। यदि हम field_18 पर एक कर्नेल पता सेट कर सकते हैं, तो हम var_304 को कर्नेल मेमोरी क्षेत्र के पते पर लिख सकते हैं।
=> bug type: arbitrary kernel Write-Where
अब बग को ट्रिगर करने का तरीका खोजना होगा। फ़ंक्शन AfdNotifyRemoveIoCompletion को सीधे AfdNotifySock फ़ंक्शन में कॉल किया जाता है।

इसी तरह, AfdNotifySock का क्रॉस रेफरेंस ढूंढने पर हम देखते हैं कि इसे किसी अन्य फ़ंक्शन से सीधे कॉल नहीं किया जाता है, लेकिन फ़ंक्शन का पता .rdata में एक पते पर संग्रहीत है।

यह पता AfdIrpCallDispatch से ठीक पहले स्थित है।

बग को ट्रिगर करने के लिए, मैं DeviceIoControl को IOCTL_AFD_NOTIFY_SOCK के साथ कॉल करूंगा और AfdNotifySock को कॉल किया जाएगा।
BOOL DeviceIoControl(
[in] HANDLE hDevice,
[in] DWORD dwIoControlCode,
[in, optional] LPVOID lpInBuffer,
[in] DWORD nInBufferSize,
[out, optional] LPVOID lpOutBuffer,
[in] DWORD nOutBufferSize,
[out, optional] LPDWORD lpBytesReturned,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
प्रत्येक ड्राइवर के लिए, कर्नेल में एक DRIVER_OBJECT ऑब्जेक्ट बनाया जाता है, इसे इस प्रकार परिभाषित किया गया है:
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
अंतिम घटक MajorFunction एक सरणी है जिसमें ड्राइवर के डिस्पैच फ़ंक्शन होते हैं, जो कर्नेल और यूज़रमोड के बीच संचार को संभालते हैं। DeviceIoControl कॉल के अनुरूप डिस्पैच फ़ंक्शन MajorFunction[IRP_MJ_DEVICE_CONTROL] पर संग्रहीत होता है।
#define IRP_MJ_DEVICE_CONTROL 0x0e
afd.sys के DriverEntry फ़ंक्शन से, हम देख सकते हैं कि ड्राइवर ने डिवाइस ऑब्जेक्ट "\Device\Afd" बनाया है:

यह MajorFunction[IRP_MJ_DEVICE_CONTROL] = AfdDispatchDeviceControl असाइन करता है, इसलिए जब कर्नेल के साथ संचार करने के लिए DeviceIoControl कॉल किया जाता है, तो यह इस फ़ंक्शन को कॉल करेगा।

AFD में दो डिस्पैच टेबल हैं: AfdIrpCallDispatch और AfdImmediateCallDispatch।

यह देखना आसान है कि AfdDispatchDeviceIoControl IoControlCode के माध्यम से सबस्क्रिप्ट की गणना करता है और IoControlCode द्वारा सत्यापित करने के लिए AfdIoctlTable से सबस्क्रिप्ट के अनुरूप मान प्राप्त करता है।

AfdImmediateCallDispatch के प्रारंभ पते और AfdNotifySock को संग्रहीत करने वाले पते के बीच की दूरी से, हम इंडेक्स 73 की गणना करते हैं, जिसका नियंत्रण कोड 0x12127 है।

int main() {
WSADATA WSAData;
SOCKET s;
SOCKADDR_IN sa;
int ierr;
WSAStartup(0x2, &WSAData);
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
memset(&sa, 0, sizeof(sa));
sa.sin_port = htons(135);
sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
sa.sin_family = AF_INET;
ierr = connect(s, (const struct sockaddr*)&sa, sizeof(sa));
char outBuf[100];
DWORD bytesRet;
DWORD inbuf1[100];
memset(inbuf1, 0, sizeof(inbuf1));
DeviceIoControl((HANDLE)s, 0x12127, (LPVOID)inbuf1, 0x30, outBuf, 0, &bytesRet, NULL);
return 0;
}
it works!

जैसा कि शुरुआत में बताया गया था, भेद्यता तब होती है जब हम एक struct के माध्यम से एक अमान्य (unvalidated) पॉइंटर पास कर सकते हैं। यह struct सीधे usermode से DeviceIoControl के lpInBuffer के माध्यम से पास किया जाता है। फिर इसे चौथे पैरामीटर के रूप में AfdNotifySock में और तीसरे पैरामीटर के रूप में AfdNotifyRemoveIoCompletion में पास किया जाता है।