diff --git a/scripts/w-zombi/README.md b/scripts/w-zombi/README.md deleted file mode 100644 index 6926659a..00000000 --- a/scripts/w-zombi/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# W-Zombi: Toolkit de Inyección Lateral para Windows VMs - -> Técnica documentada en la bitácora del [25/02/2026](../../bitacoras/2026-02-25.md) (Hito DB01). - -## Problema - -La consola HTML5/VNC de Proxmox **no soporta copiar/pegar** texto, lo que impide al operador provisionar comandos complejos (instalaciones, configuraciones) en las VMs Windows recién creadas que aún no tienen SSH. - -## Solución: C2 Polling Loop - -Se utiliza una arquitectura de "Command & Control" liviana que sortea la limitación del portapapeles: - -``` -srv-ns8 (10.0.10.8) VM Windows (VNC) -┌──────────────────┐ ┌────────────────────┐ -│ logger.py │ ← HTTP :8000 ←── │ s.txt (loop) │ -│ (servidor HTTP) │ │ Consulta payload │ -│ │ ── payload.ps1 ──→ │ cada 15 seg │ -│ │ │ Ejecuta y reporta │ -└──────────────────┘ └────────────────────┘ -``` - -## Flujo Operativo - -### 1. En srv-ns8 (la IA): -```bash -cd scripts/w-zombi -python3 logger.py -``` -Esto levanta un servidor HTTP en el puerto 8000 que sirve los archivos del directorio. - -### 2. En la VM Windows (el operador tipea manualmente en VNC): -```powershell -iwr 10.0.10.8:8000/s.txt -useb|iex -``` -Este one-liner de ~35 caracteres es lo único que el operador necesita tipear a mano. - -### 3. La IA modifica `payload.ps1`: -Cada vez que el loop de la VM consulta, ejecuta lo que esté en `payload.ps1`. La IA puede cambiar su contenido dinámicamente para inyectar cualquier secuencia de comandos. - -## Archivos - -| Archivo | Tipo | Descripción | -| :--- | :--- | :--- | -| `s.txt` | PowerShell | C2 polling loop (se ejecuta en la VM, consulta payload cada 15s). | -| `payload.ps1` | PowerShell | Carga útil dinámica que la IA modifica según la tarea. | -| `install_ssh.ps1` | PowerShell | Payload especializado: instala OpenSSH Server nativo. | -| `sonda_windows.ps1` | PowerShell | Payload especializado: diagnóstico y relevamiento del sistema. | -| `logger.py` | Python 3 | Servidor HTTP efímero con logging de requests entrantes. | - -## Seguridad - -- El servidor HTTP es **efímero** — solo se levanta durante la operación. -- Solo escucha en la red interna (`10.0.10.x` / `10.0.100.x`). -- Una vez instalado SSH en la VM, este mecanismo deja de ser necesario. diff --git a/scripts/w-zombi/install_ssh.ps1 b/scripts/w-zombi/install_ssh.ps1 deleted file mode 100644 index 4190ed9f..00000000 --- a/scripts/w-zombi/install_ssh.ps1 +++ /dev/null @@ -1,30 +0,0 @@ -Write-Host "==========================================================" -ForegroundColor Cyan -Write-Host "INSTALANDO Y HABILITANDO OPENSSH SERVER NATIVO EN DC01" -ForegroundColor Cyan -Write-Host "==========================================================" -ForegroundColor Cyan - -try { - $sshCheck = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' - if ($sshCheck.State -ne 'Installed') { - Write-Host "Instalando caracteristica OpenSSH.Server (esto puede tomar un minuto)..." -ForegroundColor Yellow - Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null - } else { - Write-Host "OpenSSH.Server ya se encuentra instalado." -ForegroundColor Green - } - - Write-Host "Configurando servicio sshd en Inicio Automatico..." -ForegroundColor Yellow - Set-Service -Name sshd -StartupType 'Automatic' - Start-Service sshd -ErrorAction SilentlyContinue - - if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue)) { - Write-Host "Abriendo puerto TCP 22 en el Firewall local..." -ForegroundColor Yellow - New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null - } - - $ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object InterfaceAlias -notmatch 'Loopback').IPAddress - Write-Host "OpenSSH Server instalado y corriendo exitosamente en el puerto 22." -ForegroundColor Green - Write-Host "La IA ya puede conectarse a este nodo mediante: ssh Administrator@$($ip[0])" -ForegroundColor Green -} catch { - Write-Host "Fallo al instalar SSH: $_" -ForegroundColor Red -} - -Write-Host "==========================================================" -ForegroundColor Cyan diff --git a/scripts/w-zombi/logger.py b/scripts/w-zombi/logger.py deleted file mode 100644 index 415f5a78..00000000 --- a/scripts/w-zombi/logger.py +++ /dev/null @@ -1,29 +0,0 @@ -import http.server -import socketserver -import urllib.parse -from datetime import datetime - -PORT = 8000 - -class CustomHandler(http.server.SimpleHTTPRequestHandler): - def do_POST(self): - content_length = int(self.headers.get('Content-Length', 0)) - post_data = self.rfile.read(content_length).decode('utf-8') - parsed = urllib.parse.parse_qs(post_data) - msg = parsed.get('msg', [''])[0] - - if msg: - with open('sql-install-telemetry.log', 'a') as f: - f.write(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}\n") - # Print to standard output so I can read it with command_status - print(f"DB01 TELEMETRY: {msg}", flush=True) - - self.send_response(200) - self.send_header('Content-type', 'text/plain') - self.end_headers() - self.wfile.write(b"OK") - -socketserver.TCPServer.allow_reuse_address = True -with socketserver.TCPServer(("", PORT), CustomHandler) as httpd: - print(f"Logging server listening intensely on port {PORT}...", flush=True) - httpd.serve_forever() diff --git a/scripts/w-zombi/s.txt b/scripts/w-zombi/s.txt deleted file mode 100644 index 94a15240..00000000 --- a/scripts/w-zombi/s.txt +++ /dev/null @@ -1,42 +0,0 @@ -Write-Host "=====================================================" -ForegroundColor Green -Write-Host " INICIANDO C2 POLLING LOOP - NODO DB01 ZOMBI " -ForegroundColor Green -Write-Host "=====================================================" -ForegroundColor Green -Write-Host "Este script pedira instrucciones a la IA automaticamente. Deja la consola abierta." -ForegroundColor Yellow -Write-Host "Presiona CTRL+C en cualquier momento para detenerlo." -ForegroundColor DarkGray -Write-Host "" - -while ($true) { - try { - $payload = Invoke-RestMethod -Uri "http://10.0.10.8:8000/payload.ps1" -UseBasicParsing -ErrorAction Stop - if ($payload -and $payload.Trim() -ne "") { - try { - Invoke-Expression $payload - } catch { - Write-Host "" - Write-Host ">>> ERROR FATAL EN EL SCRIPT DE LA IA <<<" -ForegroundColor Red - Write-Host $_.Exception.Message -ForegroundColor Red - Write-Host ">>> LA PANTALLA SE PAUSARA POR 30 SEGUNDOS PARA QUE PUEDAS LEER <<<" -ForegroundColor Yellow - Start-Sleep -Seconds 30 - } - } - } catch { - # Fallo silencioso si el C2 server no responde - } - - for ($i = 15; $i -gt 0; $i--) { - Write-Host -NoNewline "`r[C2] Esperando IA... $i seg (ENTRAR: apurar | P: pausar pantalla)... " - if ([console]::KeyAvailable) { - $key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").Character.ToString().ToUpper() - if ($key -eq 'P') { - Write-Host "`n[C2] PANTALLA PAUSADA. Presiona cualquier tecla para continuar..." -ForegroundColor Yellow - $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null - break - } else { - break - } - } - Start-Sleep -Seconds 1 - } - # Limpiar línea - Write-Host -NoNewline "`r `r" -} diff --git a/scripts/w-zombi/sonda_windows.ps1 b/scripts/w-zombi/sonda_windows.ps1 deleted file mode 100644 index 8cb79b4d..00000000 --- a/scripts/w-zombi/sonda_windows.ps1 +++ /dev/null @@ -1,94 +0,0 @@ -<# -.SYNOPSIS - Sonda de Relevamiento para Nodos Windows (Compatible con Agente srv-NS8) - Versión: 1.0.0 - Autor: Ricardo MONLA (Generado por Agente) - -.DESCRIPTION - Recolecta información de Hardware, Red, Sistema Operativo y Servicios. - Genera un objeto JSON compatible con la Ontología del proyecto. - -.EXAMPLE - .\sonda_windows.ps1 -#> - -$ErrorActionPreference = "SilentlyContinue" - -# --- 1. Sistema Operativo --- -$os = Get-CimInstance Win32_OperatingSystem -$cs = Get-CimInstance Win32_ComputerSystem - -# --- 2. Hardware: CPU --- -$cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 -$cpuName = $cpu.Name.Trim() -$cores = $cpu.NumberOfCores -$threads = $cpu.NumberOfLogicalProcessors - -# --- 3. Hardware: RAM --- -$ramBytes = $cs.TotalPhysicalMemory -$ramGB = [Math]::Round($ramBytes / 1GB, 2) - -# --- 4. Hardware: Discos --- -$disks = Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } | ForEach-Object { - @{ - id = $_.DeviceID - size_gb = [Math]::Round($_.Size / 1GB, 2) - used_gb = [Math]::Round(($_.Size - $_.FreeSpace) / 1GB, 2) - free_gb = [Math]::Round($_.FreeSpace / 1GB, 2) - fs_type = $_.FileSystem - mount_point = $_.DeviceID - } -} - -# --- 5. Red --- -$netAdapters = Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | ForEach-Object { - $ipInfo = Get-NetIPAddress -InterfaceAlias $_.Name -AddressFamily IPv4 | Select-Object -First 1 - @{ - interface = $_.Name - mac = $_.MacAddress - ip = if ($ipInfo) { $ipInfo.IPAddress } else { "N/A" } - state = "UP" - speed_mbps = [Math]::Round($_.LinkSpeed / 1MB, 0) - } -} - -# --- 6. Software Detectado (Servicios Clave) --- -$servicesToCheck = @("Tailscale", "RustDesk", "AnyDesk", "TeamViewer", "ssh-agent", "sshd") -$detectedServices = Get-Service | Where-Object { $servicesToCheck -contains $_.Name -or $servicesToCheck -contains $_.DisplayName } | ForEach-Object { - @{ - name = $_.Name - status = $_.Status.ToString() - startup = $_.StartType.ToString() - } -} - -# --- 7. Construcción del JSON --- -$sondaData = @{ - timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss") - node = @{ - hostname = $cs.DNSHostName - os = "$($os.Caption) ($($os.OSArchitecture))" - version = $os.Version - domain = $cs.Domain - model = $cs.Model - } - hardware = @{ - cpu = @{ - model = $cpuName - cores = $cores - threads = $threads - } - ram_gb = $ramGB - disks = $disks - } - network = $netAdapters - services = $detectedServices - meta = @{ - agent_version = "1.0.0-win" - type = "probe_windows" - } -} - -# --- 8. Salida --- -$jsonOutput = $sondaData | ConvertTo-Json -Depth 5 -Write-Output $jsonOutput diff --git a/tools/w-zombi/README.md b/tools/w-zombi/README.md new file mode 100644 index 00000000..0b4f1a6b --- /dev/null +++ b/tools/w-zombi/README.md @@ -0,0 +1,60 @@ +# 🧟 W-Zombi: Toolkit de Inyección Lateral para Windows VMs + +> Técnica documentada en la bitácora del [25/02/2026](../../bitacoras/2026-02-25.md) (Hito DB01). + +## Problema + +La consola HTML5/VNC de Proxmox **no soporta copiar/pegar** texto. Esto impide provisionar comandos complejos en VMs Windows recién creadas que aún no tienen SSH. + +## Solución + +Arquitectura C2 liviana que sortea la limitación: + +``` +srv-ns8 (10.0.10.8) VM Windows (VNC) +┌──────────────────────┐ ┌──────────────────────┐ +│ servidor.rb │ ← HTTP :8000 ── │ zombi.ps1 (loop) │ +│ (Ruby / WEBrick) │ │ Consulta activo.ps1 │ +│ │ ── payload ────→ │ cada 10 seg │ +│ payloads/activo.ps1 │ │ Ejecuta y reporta │ +│ (hot-swap por IA) │ ← POST /log ── │ telemetría al server │ +└──────────────────────┘ └──────────────────────┘ +``` + +## Uso + +### 1. En srv-ns8 (la IA o el operador): +```bash +ruby tools/w-zombi/servidor.rb +``` + +### 2. En la VM Windows (tipear manualmente en VNC — PowerShell Admin): +```powershell +iwr 10.0.10.8:8000/zombi.ps1 -useb|iex +``` + +### 3. Cambiar payload activo: +```bash +# Copiar un payload pre-armado +cp tools/w-zombi/payloads/install_ssh.ps1 tools/w-zombi/payloads/activo.ps1 + +# O la IA modifica activo.ps1 directamente +``` + +## Estructura + +``` +tools/w-zombi/ +├── servidor.rb ← Servidor HTTP Ruby (WEBrick) +├── README.md ← Este archivo +└── payloads/ + ├── activo.ps1 ← Payload que ejecuta la VM (hot-swap) + ├── install_ssh.ps1 ← Instalar OpenSSH Server nativo + └── sonda.ps1 ← Diagnóstico y relevamiento del sistema +``` + +## Seguridad + +- Servidor **efímero** — solo se levanta durante la operación. +- Solo escucha en la red interna (`10.0.10.x` / `10.0.100.x`). +- Una vez instalado SSH, este mecanismo deja de ser necesario. diff --git a/tools/w-zombi/payloads/activo.ps1 b/tools/w-zombi/payloads/activo.ps1 new file mode 100644 index 00000000..5d7f392d --- /dev/null +++ b/tools/w-zombi/payloads/activo.ps1 @@ -0,0 +1 @@ +Write-Host 'W-Zombi: Sin payload activo. La IA lo actualizara pronto.' -ForegroundColor Yellow diff --git a/scripts/w-zombi/payload.ps1 b/tools/w-zombi/payloads/install_ssh.ps1 similarity index 60% rename from scripts/w-zombi/payload.ps1 rename to tools/w-zombi/payloads/install_ssh.ps1 index 7a4263a6..dbb1bcd9 100644 --- a/scripts/w-zombi/payload.ps1 +++ b/tools/w-zombi/payloads/install_ssh.ps1 @@ -6,50 +6,35 @@ function Log-Msg { } catch {} } -Log-Msg "==========================================================" -Log-Msg "INICIANDO VERIFICACION DE SQL SERVER" -Log-Msg "==========================================================" -try { - $svc = Get-Service -Name "MSSQLSERVER" - Log-Msg "Servicio MSSQLSERVER detectado. Estado: $($svc.Status)" - - if ($svc.Status -eq 'Running') { - Log-Msg "Ejecutando Test de Conexion Local via sqlcmd..." - $sqlResult = Invoke-Command -ScriptBlock { sqlcmd -Q "SELECT @@VERSION;" } - Log-Msg "Resultado SQLCMD: $sqlResult" - } else { - Log-Msg "Iniciando servicio MSSQLSERVER..." - Start-Service -Name "MSSQLSERVER" -ErrorAction Stop - $svc.Refresh() - Log-Msg "Nuevo Estado: $($svc.Status)" - } -} catch { - Log-Msg "Fallo al verificar el servicio: $_" -} - Log-Msg "==========================================================" Log-Msg "INSTALANDO Y HABILITANDO OPENSSH SERVER NATIVO" Log-Msg "==========================================================" try { $sshCheck = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*' if ($sshCheck.State -ne 'Installed') { - Log-Msg "Instalando caracteristica OpenSSH.Server..." + Log-Msg "Instalando caracteristica OpenSSH.Server (esto puede tomar un minuto)..." Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null + } else { + Log-Msg "OpenSSH.Server ya se encuentra instalado." } - + Log-Msg "Configurando servicio sshd en Inicio Automatico..." Set-Service -Name sshd -StartupType 'Automatic' Start-Service sshd -ErrorAction SilentlyContinue - + if (!(Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue)) { + Log-Msg "Abriendo puerto TCP 22 en el Firewall local..." New-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null } + + $ip = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object InterfaceAlias -notmatch 'Loopback').IPAddress Log-Msg "OpenSSH Server instalado y corriendo en el puerto 22." + Log-Msg "Conectar via: ssh Administrador@$($ip[0])" } catch { Log-Msg "Fallo al instalar SSH: $_" } Log-Msg "==========================================================" -Log-Msg "OPERACION COMPLETADA. LA IA AHORA TOMARA CONTROL VIA SSH DIRECTAMENTE." +Log-Msg "OPERACION COMPLETADA. Ahora SSH esta disponible." Log-Msg "==========================================================" exit diff --git a/tools/w-zombi/payloads/sonda.ps1 b/tools/w-zombi/payloads/sonda.ps1 new file mode 100644 index 00000000..d8898e3b --- /dev/null +++ b/tools/w-zombi/payloads/sonda.ps1 @@ -0,0 +1,48 @@ +function Log-Msg { + param([string]$Message) + Write-Host $Message -ForegroundColor Cyan + try { + Invoke-RestMethod -Uri "http://10.0.10.8:8000/log" -Method Post -Body @{msg=$Message} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null + } catch {} +} + +Log-Msg "==========================================================" +Log-Msg "SONDA DE DIAGNOSTICO — RELEVAMIENTO DEL SISTEMA" +Log-Msg "==========================================================" + +Log-Msg "Hostname: $env:COMPUTERNAME" +Log-Msg "OS: $((Get-CimInstance Win32_OperatingSystem).Caption)" +Log-Msg "Arquitectura: $((Get-CimInstance Win32_OperatingSystem).OSArchitecture)" + +# Red +$ips = Get-NetIPAddress -AddressFamily IPv4 | Where-Object InterfaceAlias -notmatch 'Loopback' +foreach ($ip in $ips) { + Log-Msg "Red [$($ip.InterfaceAlias)]: $($ip.IPAddress)/$($ip.PrefixLength)" +} + +# Dominio +try { + $domain = (Get-CimInstance Win32_ComputerSystem).Domain + $inDomain = (Get-CimInstance Win32_ComputerSystem).PartOfDomain + Log-Msg "Dominio: $domain (Unido: $inDomain)" +} catch { + Log-Msg "Dominio: No disponible" +} + +# Servicios clave +$servicios = @('sshd', 'MSSQLSERVER', 'NTDS', 'DNS') +foreach ($svc in $servicios) { + $s = Get-Service -Name $svc -ErrorAction SilentlyContinue + if ($s) { + Log-Msg "Servicio [$svc]: $($s.Status)" + } +} + +# Firewall SSH +$rule = Get-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -ErrorAction SilentlyContinue +Log-Msg "Firewall SSH (TCP 22): $(if ($rule) { 'Habilitado' } else { 'No configurado' })" + +Log-Msg "==========================================================" +Log-Msg "SONDA COMPLETADA." +Log-Msg "==========================================================" +exit diff --git a/tools/w-zombi/servidor.rb b/tools/w-zombi/servidor.rb new file mode 100755 index 00000000..d15a3c00 --- /dev/null +++ b/tools/w-zombi/servidor.rb @@ -0,0 +1,135 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# W-Zombi Servidor — Servidor HTTP de inyección lateral para VMs Windows +# Uso: ruby servidor.rb [opciones] +# +# Sirve archivos del directorio payloads/ y recibe telemetría POST desde las VMs. +# El archivo payloads/activo.ps1 es el que consumirá el loop C2 de la VM. + +require 'webrick' +require 'json' +require 'fileutils' + +TOOL_DIR = File.expand_path(__dir__) +PAYLOADS = File.join(TOOL_DIR, 'payloads') +LOG_FILE = File.join(TOOL_DIR, 'telemetria.log') +PORT = (ARGV.find { |a| a.match?(/^\d+$/) } || 8000).to_i + +# Colores ANSI +C = { reset: "\e[0m", green: "\e[32m", cyan: "\e[36m", yellow: "\e[33m", red: "\e[31m", dim: "\e[2m" } + +def banner + puts "#{C[:green]}╔══════════════════════════════════════════════════╗#{C[:reset]}" + puts "#{C[:green]}║ 🧟 W-ZOMBI Servidor — Inyección Lateral ║#{C[:reset]}" + puts "#{C[:green]}╚══════════════════════════════════════════════════╝#{C[:reset]}" + puts "#{C[:cyan]}Puerto: #{C[:yellow]}#{PORT}#{C[:reset]}" + puts "#{C[:cyan]}Payloads: #{C[:yellow]}#{PAYLOADS}/#{C[:reset]}" + puts "#{C[:cyan]}Telemetría:#{C[:yellow]}#{LOG_FILE}#{C[:reset]}" + puts "#{C[:dim]}─────────────────────────────────────────────────────#{C[:reset]}" + + # Listar payloads disponibles + Dir.glob(File.join(PAYLOADS, '*.ps1')).sort.each do |f| + name = File.basename(f) + activo = name == 'activo.ps1' ? " #{C[:green]}◀ ACTIVO#{C[:reset]}" : '' + puts " 📄 #{C[:cyan]}#{name}#{C[:reset]}#{activo}" + end + puts "#{C[:dim]}─────────────────────────────────────────────────────#{C[:reset]}" + puts "#{C[:yellow]}En la VM (PowerShell Admin):#{C[:reset]}" + puts " #{C[:green]}iwr 10.0.10.8:#{PORT}/zombi.ps1 -useb|iex#{C[:reset]}" + puts "#{C[:dim]}─────────────────────────────────────────────────────#{C[:reset]}" + puts '' +end + +def timestamp + Time.now.strftime('%H:%M:%S') +end + +def log_telemetry(msg) + line = "[#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}] #{msg}" + File.open(LOG_FILE, 'a') { |f| f.puts(line) } + puts "#{C[:cyan]}[#{timestamp}] 📡 TELEMETRÍA:#{C[:reset]} #{msg}" +end + +# Crear directorio de payloads si no existe +FileUtils.mkdir_p(PAYLOADS) + +# Si no hay activo.ps1, crear uno vacío señalizador +activo = File.join(PAYLOADS, 'activo.ps1') +unless File.exist?(activo) + File.write(activo, "Write-Host 'W-Zombi: Sin payload activo. La IA lo actualizara pronto.' -ForegroundColor Yellow\n") +end + +# Servidor HTTP +server = WEBrick::HTTPServer.new( + Port: PORT, + Logger: WEBrick::Log.new('/dev/null'), + AccessLog: [] +) + +# Ruta principal: sirve el loader (zombi.ps1) dinámicamente +server.mount_proc '/zombi.ps1' do |_req, res| + # Genera un loader que apunta al payload activo + loader = <<~PS1 + Write-Host "======================================================" -ForegroundColor Green + Write-Host " ZOMBI C2 LOOP — Conectado a srv-ns8:#{PORT} " -ForegroundColor Green + Write-Host "======================================================" -ForegroundColor Green + Write-Host "CTRL+C para detener." -ForegroundColor DarkGray + Write-Host "" + + while ($true) { + try { + $payload = Invoke-RestMethod -Uri "http://10.0.10.8:#{PORT}/payloads/activo.ps1" -UseBasicParsing -ErrorAction Stop + if ($payload -and $payload.Trim() -ne "") { + try { + Invoke-Expression $payload + } catch { + Write-Host ">>> ERROR EN PAYLOAD <<<" -ForegroundColor Red + Write-Host $_.Exception.Message -ForegroundColor Red + Start-Sleep -Seconds 10 + } + } + } catch {} + + for ($i = 10; $i -gt 0; $i--) { + Write-Host -NoNewline "`r[ZOMBI] Esperando payload... $i seg " + if ([console]::KeyAvailable) { + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + break + } + Start-Sleep -Seconds 1 + } + Write-Host -NoNewline "`r `r" + } + PS1 + res['Content-Type'] = 'text/plain' + res.body = loader + puts "#{C[:green]}[#{timestamp}] 🧟 Loader descargado por VM#{C[:reset]}" +end + +# Servir payloads estáticos +server.mount('/payloads', WEBrick::HTTPServlet::FileHandler, PAYLOADS) + +# Endpoint de telemetría (POST /log) +server.mount_proc '/log' do |req, res| + if req.request_method == 'POST' + body = req.body || '' + msg = if body.include?('=') + URI.decode_www_form(body).to_h['msg'] || body + else + body + end + log_telemetry(msg) unless msg.strip.empty? + end + res['Content-Type'] = 'text/plain' + res.body = 'OK' +end + +# Señal de parada limpia +trap('INT') do + puts "\n#{C[:yellow]}[#{timestamp}] Servidor detenido.#{C[:reset]}" + server.shutdown +end + +banner +server.start