Files
dtic-DIIAA/tools/w-zombi/servidor.rb
T
Ricardo Monla 15725e0b3c 🚀 P02: Despliegue exitoso del sistema DASUTEN en pcv-dasu0
Infraestructura y Red:
- Tailscale v1.94.2 instalado en srv-dasu (IP: 100.116.210.36)
- Configurado como subnet router para 10.0.100.0/24
- Fix: Repositorios enterprise de Proxmox deshabilitados (.sources → .bak)

Despliegue del Sistema (pcv-dasu0):
- Transferencia SCP de runSysDasuten.zip via SSH puerto 7022
- Extracción en C:\Dasuten\runSysDasuten- Kermet.ini configurado: SERVER=sql-dasuten
- InstaladorComponentesKermet.exe (silent install)
- Fuentes TrueType registradas en Windows (Code39, I2OF5)
- Firewall TCP 1433 abierto en sql-dasuten
- Test-NetConnection: TcpTestSucceeded=True
- Login exitoso con usuario aalmiron 

ADN y Contexto:
- 05_ia.md: Regla 13 Auto-Contextualización Topológica
- 07_proyectos.md: Sección 'Topología y Red' obligatoria en manifiestos
- P2601_dasuten.md: Contexto de red documentado
- nodos/pcv-dasu0.md: Puerto SSH actualizado a 7022

Dashboard P2601:
- Hitos completados: 8 → 11 (P02.APP, NET01, TEST03)
- Progreso: 67% → 79%
- pcv-dasu0: Testing → Online
- Nuevo hito activo: UAT (verificación presencial)

Bitácoras y Documentación:
- Bitácora 04/03 y 05/03 completas
- Plan de despliegue en docs/plan/260305-1017
- Investigación de ingeniería inversa documentada
- ns8-candados: Instrucciones de uso para IA agregadas
- dtic-BITACORAs: CRUD completo, Dashboard P2601 integrado

Herramientas:
- w-zombi: Mejora anti-loop (max 5 ciclos)
- Payloads de despliegue SQL y sistema generados
2026-03-05 13:15:31 -03:00

148 lines
5.9 KiB
Ruby
Executable File

#!/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 "Max 5 ciclos. CTRL+C para detener." -ForegroundColor DarkGray
Write-Host ""
$maxCiclos = 5
for ($ciclo = 1; $ciclo -le $maxCiclos; $ciclo++) {
Write-Host "[ZOMBI] Ciclo $ciclo/$maxCiclos" -ForegroundColor DarkGray
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
}
}
} catch {
Write-Host "[ZOMBI] Sin conexion al servidor." -ForegroundColor Red
}
if ($ciclo -lt $maxCiclos) {
for ($i = 10; $i -gt 0; $i--) {
Write-Host -NoNewline "`r[ZOMBI] Siguiente ciclo en $i seg "
if ([console]::KeyAvailable) {
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
Write-Host "`n[ZOMBI] Detenido por usuario." -ForegroundColor Yellow
return
}
Start-Sleep -Seconds 1
}
Write-Host -NoNewline "`r `r"
}
}
Write-Host ""
Write-Host "======================================================" -ForegroundColor Yellow
Write-Host " ZOMBI: $maxCiclos ciclos completados. Auto-detenido." -ForegroundColor Yellow
Write-Host "======================================================" -ForegroundColor Yellow
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