[P2604] Fase 15: Optimización IA e integración de herramienta candados. Sincronización de bitácoras y actualización de planes de proyecto.
This commit is contained in:
@@ -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.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative '../lib/wzombi/config'
|
||||
require_relative '../lib/wzombi/server'
|
||||
require_relative '../lib/wzombi/command_store'
|
||||
|
||||
base = File.expand_path("..", __dir__)
|
||||
config = WZombi::Config.new(base_path: base)
|
||||
|
||||
def help
|
||||
puts <<~HELP
|
||||
Uso:
|
||||
w-zombi start
|
||||
w-zombi cmd "<comando>"
|
||||
w-zombi ayuda | -h | -help
|
||||
|
||||
Ejemplo ADN:
|
||||
adn tools w-zombi cmd "ipconfig"
|
||||
HELP
|
||||
end
|
||||
|
||||
cmd = ARGV[0]
|
||||
|
||||
case cmd
|
||||
when "start"
|
||||
WZombi::Server.new(config).start
|
||||
|
||||
when "cmd"
|
||||
command = ARGV[1]
|
||||
if command.nil?
|
||||
puts "Falta comando"
|
||||
exit
|
||||
end
|
||||
store = WZombi::CommandStore.new(config.cmd_path)
|
||||
store.update(command)
|
||||
puts "Comando actualizado: #{command}"
|
||||
|
||||
when "ayuda", "-h", "-help", nil
|
||||
help
|
||||
|
||||
else
|
||||
puts "Comando desconocido"
|
||||
help
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
{"id":"1773805876","cmd":"whoami","status":"pending"}
|
||||
@@ -0,0 +1 @@
|
||||
NO_CMD
|
||||
@@ -0,0 +1 @@
|
||||
{"id":0,"cmd":""}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
require 'json'
|
||||
|
||||
module WZombi
|
||||
class CommandStore
|
||||
def initialize(path)
|
||||
@path = path
|
||||
init_file
|
||||
end
|
||||
|
||||
def init_file
|
||||
File.write(@path, { id: 0, cmd: "" }.to_json) unless File.exist?(@path)
|
||||
end
|
||||
|
||||
def current
|
||||
JSON.parse(File.read(@path))
|
||||
end
|
||||
|
||||
def update(cmd)
|
||||
data = current
|
||||
data["id"] += 1
|
||||
data["cmd"] = cmd
|
||||
File.write(@path, data.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module WZombi
|
||||
class Config
|
||||
attr_reader :puerto, :base_path
|
||||
|
||||
def initialize(base_path:)
|
||||
@base_path = base_path
|
||||
@puerto = ENV.fetch("WZ_PORT", 8000).to_i
|
||||
end
|
||||
|
||||
def log_path
|
||||
File.join(base_path, "data/telemetria.log")
|
||||
end
|
||||
|
||||
def cmd_path
|
||||
File.join(base_path, "data/cmd.json")
|
||||
end
|
||||
|
||||
def payloads_path
|
||||
File.join(base_path, "payloads")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module WZombi
|
||||
module Engine
|
||||
class PS1Builder
|
||||
def self.build(host:)
|
||||
<<~PS1
|
||||
# W-ZOMBI ENGINE
|
||||
$WZ_HOST = "#{host}"
|
||||
$LAST_CMD_ID = 0
|
||||
|
||||
function Get-Cmd {
|
||||
try {
|
||||
return Invoke-RestMethod -Uri "http://$WZ_HOST/cmd"
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function Send-Log {
|
||||
param([string]$msg)
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://$WZ_HOST/log" -Method Post -Body $msg -ContentType "text/plain"
|
||||
} catch {}
|
||||
}
|
||||
|
||||
while ($true) {
|
||||
$cmdData = Get-Cmd
|
||||
if ($cmdData.id -ne $LAST_CMD_ID) {
|
||||
$LAST_CMD_ID = $cmdData.id
|
||||
$result = Invoke-Expression $cmdData.cmd 2>&1 | Out-String
|
||||
Send-Log $result
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
PS1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
module WZombi
|
||||
class Logger
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def log(msg)
|
||||
line = "[#{Time.now.strftime('%H:%M:%S')}] #{msg}"
|
||||
File.open(@path, "a") { |f| f.puts(line) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
require 'json'
|
||||
|
||||
module WZombi
|
||||
module Routes
|
||||
class Cmd
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(_req, res)
|
||||
res['Content-Type'] = 'application/json'
|
||||
res.body = File.read(@config.cmd_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
module WZombi
|
||||
module Routes
|
||||
class Log
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(req, res)
|
||||
File.open(@config.log_path, "a") { |f| f.puts(req.body) }
|
||||
res.body = "OK"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
require_relative '../engine/ps1_builder'
|
||||
|
||||
module WZombi
|
||||
module Routes
|
||||
class Zombi
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(_req, res)
|
||||
# Priorizamos host desde variable de entorno, sino usamos la IP Tailscale conocida
|
||||
host = ENV.fetch("WZ_HOST", "100.111.195.4:#{@config.puerto}")
|
||||
res.body = Engine::PS1Builder.build(host: host)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
require 'webrick'
|
||||
require_relative 'routes/zombi'
|
||||
require_relative 'routes/cmd'
|
||||
require_relative 'routes/log'
|
||||
|
||||
module WZombi
|
||||
class Server
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def start
|
||||
server = WEBrick::HTTPServer.new(Port: @config.puerto)
|
||||
|
||||
server.mount_proc('/zombi.ps1', &Routes::Zombi.new(@config).method(:call))
|
||||
server.mount_proc('/cmd', &Routes::Cmd.new(@config).method(:call))
|
||||
server.mount_proc('/log', &Routes::Log.new(@config).method(:call))
|
||||
|
||||
trap("INT") { server.shutdown }
|
||||
|
||||
puts "[W-ZOMBI] Servidor corriendo en puerto #{@config.puerto}"
|
||||
server.start
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
OpenSSH-Win64.zip
|
||||
@@ -0,0 +1,65 @@
|
||||
$pub_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCxCHrqnaVrkihb/0yBAxzcb9knhCAWSFFC9J6rEKHE4Yu5DW3XZ/aoWKFq1NEZS6yNQqBqFGvAe32RBwnX7RiJEkhYgS/xr2SJorIxTWWJUOmV5i7OopQQQoZ7YUoY56tdVrYaulJCDyJkvnmGZwGdUS782BEb9nWDwXd5t1qDZJtCWCV/xK34zRuTy7DqHPU3sx+PUwj0KWy8rQBeRK2KNvHCpcVTQvx4u1QS9Cbbqsic+BrZfaU1LBa70GdJL8GY9p1ZIlSqqPMyzcpILh7CwrnyNr7hj+kaKVIdzeo+0WIf97XsjD0dNNt5VPP3kBrnTA62EQ6eraZl7V01Hx6gK178K9C3fcgPHsxZASf8/Vo+zOeg3ChCYQFPF9YzSyQpAJE2yT7ptuzXulhifpRBIs9d8HS5+6mWYgaLPqwDpZRvYB5NsX96m2OX3LBIeeJm4ZEc4531jsvOJNPiok9SVg083aAUbflFKtWV5M01X54rT4+uU6Uy4LRQGXPr5Mor+BBZyZ91bTtX8nGOVZrQ3Cxm4jc0Q7Aj902SX4yqrImLuoM7XIbVTHGQt+gsgVSxEbb4KaF4jOvRZKYrDcSFrJN9KWcx5SLCDZmN4TlGqgbVPrfxHzF7BqLrzq+bcG+BVm3DP3ROBwAjbXPcztiaDHpPjWDiZByy5nHnzqmmCw== rmonla@srv-ns8`n"
|
||||
|
||||
$config = @"
|
||||
Port 7022
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
StrictModes yes
|
||||
|
||||
# Logging
|
||||
#SyslogFacility LOCAL0
|
||||
LogLevel VERBOSE
|
||||
|
||||
Subsystem sftp sftp-server.exe
|
||||
|
||||
Match Group administrators
|
||||
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
|
||||
"@
|
||||
|
||||
$ssh_dir = "C:\ProgramData\ssh"
|
||||
$admin_keys = "$ssh_dir\administrators_authorized_keys"
|
||||
$sshd_config = "$ssh_dir\sshd_config"
|
||||
|
||||
# Ensure dir exists
|
||||
if (-not (Test-Path $ssh_dir)) {
|
||||
New-Item -ItemType Directory -Force -Path $ssh_dir | Out-Null
|
||||
}
|
||||
|
||||
# Write files as UTF-8 without BOM (Safe cross-platform)
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($False)
|
||||
[System.IO.File]::WriteAllText($admin_keys, $pub_key, $utf8NoBom)
|
||||
[System.IO.File]::WriteAllText($sshd_config, $config, $utf8NoBom)
|
||||
|
||||
# ACL helper function
|
||||
function Set-SecureAcl($path) {
|
||||
if (Test-Path $path) {
|
||||
$acl = Get-Acl $path
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$acl.Access | ForEach-Object { $acl.RemoveAccessRule($_) | Out-Null }
|
||||
|
||||
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\SYSTEM", "FullControl", "Allow")
|
||||
# S-1-5-32-544 is Builtin\Administrators
|
||||
$adminSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
|
||||
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminSid, "FullControl", "Allow")
|
||||
|
||||
$acl.AddAccessRule($systemRule)
|
||||
$acl.AddAccessRule($adminRule)
|
||||
$acl.SetOwner($adminSid)
|
||||
|
||||
Set-Acl $path $acl
|
||||
}
|
||||
}
|
||||
|
||||
# Apply correct Windows OpenSSH permissions to the key file specifically
|
||||
# `sshd_config` needs to be readable but `administrators_authorized_keys` MUST be strict
|
||||
Set-SecureAcl $admin_keys
|
||||
|
||||
# Start service automatically & Restart
|
||||
Set-Service sshd -StartupType Automatic
|
||||
Restart-Service sshd
|
||||
|
||||
# Firewall Rule
|
||||
New-NetFirewallRule -Name "OpenSSH-7022" -DisplayName "OpenSSH Server (sshd) 7022" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 7022 -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
Write-Output "SSH fully configured for port 7022 with RSA Public Key."
|
||||
@@ -0,0 +1,41 @@
|
||||
$AuthorizedKeysPath = "$env:ProgramData\ssh\administrators_authorized_keys"
|
||||
$Keys = @"
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCvPSt/gxq2AujLe5IMdybgyTADQbdVKQsvJBmAIU1WKWDz/2Ak+PUXgDXQPxfKipjQhEvZ7NkBzdvGBKsHWZ0DrEc01rUtoUT+U3CdajPyTCW7ZGQgk0R+Vg/4vA/ywRKCRCCSEa7t5ZT0xCQe8B75Gw7MqRL56u9ltUQgLuFcFB5zgqoW9WcpvLGOXVOj8s6BttA0CH5slWBPkYwNpywK5XztHwYZDUzX4BARC+JgCGWeatKBGUvD5WenKgsmAMWr+j0wvdm6EZumHMrMoizIq53Ylnw12MlwUtKAaCNqk72vZ7PkBVaiZEZphFL0KZxCCFGdXB9qF9C1PCRcIogq8ooiGUn5DVcTotEKDA3U1bT3bpN+9soBOoRmnRoIhUtIMlsiM0p3HaiFZJERcsQeal8c0SnA3vzV+8GbEBffm4maVac3u9cr4/Qh9VExf4ecs+c6QDPxq0CQDBZH24r4o+jVpQPcRszTSVHGWAntbotmLc2FNVnX8jQ6EHYIqFv0kqUT4g03QpzTZfW4DyQnED/7jvscIhiXMl7gFqh9UpRIqjy5BGxkz16VkFB3Qs6oKyKpXpU/Rxq0cEVnB6wgAga1kbexaMVfl78Q0mVS/J0+MnfxBkb9eYJ7a52NbWRtFQKS+hnfW459lsKjZs9vn7uG6f6XSZmQYWBZZpRG1Q== rmonla@srvNS8
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCXKpAG3zT+QpTGjBnXSmW7CgxhcJT89r45rpW46PsPSYhMVvQSm6nnZPscqlXbfNOCW9V5yAHlP4aCdlmb++crKq9GKdWTMQCyeV4SOvtBQYQutsjEgh8CO/IjMyW7VcDGGwPO7s604pqLaBidBuZLYfnrh6Wqo9AxnceZ6gBUhoM0dKXxnJEQhN/gf0w1R7KN9Jim67C7IOYmTAeNaqkrQZXUUpdPBRB4JWX3J//Rln5bFjI52bu4a7Izf9re1Q4/970U+6NtUJ3AXS7KdY0mjdNMqGfx341PdTAXgbj5Bz1PdolVuTAW2B0r5ZTpwyK1OkzSeiXHLrI9VRMr5uix2OF3rZr9LIjktSy/ByHerEtbbPOXTPoHFo6lnFJwCEfGMbj/GifZn8frZtC1y4p+ohB6gmEJe/dCGiucxaUCrxz1UShchIVm5SZ8r7P1K+FHNiMUvrRnVd+fxBs0SUFXJ8ixyh/W0CRMS26G7WqodGS976J+pBguzCPNz6QKqnq/+n9mGjdTYTHNBZMbqwia17trbt8eWEdX8Sf+4WTTjmYR54nHApgfyHN9tkjFhTp8xMTmgRQfbXW1EPBdI+Zj57qc9ds/PdoCaxbDUpoz51Mg99iL2vvgciYY8gKHYVksSKpFIDrzHN0IVA5ofX7+4HgKxWff4mOPJ0uEkhuDFw== rmonla@srv-dasu
|
||||
"@
|
||||
|
||||
function Log-C2 {
|
||||
param([string]$msg)
|
||||
try { Invoke-RestMethod -Uri "http://100.111.195.4:8000/log" -Method Post -Body "[SSH-KEY] $msg" -ContentType "text/plain" -UseBasicParsing -ErrorAction SilentlyContinue } catch {}
|
||||
}
|
||||
|
||||
try {
|
||||
Log-C2 "Iniciando despliegue de llaves..."
|
||||
if (!(Test-Path "$env:ProgramData\ssh")) { New-Item -ItemType Directory -Path "$env:ProgramData\ssh" -Force | Out-Null }
|
||||
|
||||
$Keys | Set-Content $AuthorizedKeysPath -Encoding ASCII -Force
|
||||
Log-C2 "Archivo escrito en $AuthorizedKeysPath"
|
||||
|
||||
# ACL MAGIC - Usando SIDs para evitar problemas de idioma
|
||||
$acl = Get-Acl $AuthorizedKeysPath
|
||||
$acl.SetOwner([System.Security.Principal.SecurityIdentifier]"S-1-5-18") # SYSTEM
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$rule1 = New-Object System.Security.AccessControl.FileSystemAccessRule([System.Security.Principal.SecurityIdentifier]"S-1-5-18", "FullControl", "Allow")
|
||||
$rule2 = New-Object System.Security.AccessControl.FileSystemAccessRule([System.Security.Principal.SecurityIdentifier]"S-1-5-32-544", "FullControl", "Allow")
|
||||
$acl.SetAccessRule($rule1)
|
||||
$acl.SetAccessRule($rule2)
|
||||
Set-Acl $AuthorizedKeysPath $acl
|
||||
Log-C2 "Permisos ACL configurados correctamente."
|
||||
|
||||
# Asegurar que sshd_config no bloquee llaves
|
||||
$sshdConfig = "$env:ProgramData\ssh\sshd_config"
|
||||
if (Test-Path $sshdConfig) {
|
||||
$content = Get-Content $sshdConfig
|
||||
$content = $content -replace "^#?PubkeyAuthentication.*", "PubkeyAuthentication yes"
|
||||
$content = $content -replace "^#?PasswordAuthentication.*", "PasswordAuthentication yes" # Por ahora dejamos ambos
|
||||
$content | Set-Content $sshdConfig
|
||||
Log-C2 "sshd_config actualizado (PubkeyAuthentication yes)"
|
||||
}
|
||||
} catch {
|
||||
Log-C2 "ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
$keySource = "C:\Users\admindasu\.ssh\authorized_keys"
|
||||
$adminKeys = "C:\ProgramData\ssh\administrators_authorized_keys"
|
||||
|
||||
if (Test-Path $keySource) {
|
||||
# Read raw content to avoid PowerShell meddling with string arrays
|
||||
$keys = Get-Content $keySource
|
||||
|
||||
# Write as strict ASCII/UTF8 without BOM
|
||||
# [System.IO.File]::WriteAllText is the safest way to ensure no UTF-16 LE BOM is added.
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($False)
|
||||
[System.IO.File]::WriteAllLines($adminKeys, $keys, $utf8NoBom)
|
||||
|
||||
Write-Output "administrators_authorized_keys re-written as UTF-8 (No BOM)"
|
||||
|
||||
# Also fix permissions just in case
|
||||
$acl = Get-Acl $adminKeys
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\SYSTEM", "FullControl", "Allow")
|
||||
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule("BUILTIN\Administradores", "FullControl", "Allow")
|
||||
$acl.SetAccessRule($systemRule)
|
||||
$acl.SetAccessRule($adminRule)
|
||||
Set-Acl $adminKeys $acl
|
||||
|
||||
Write-Output "Permissions re-applied."
|
||||
} else {
|
||||
Write-Output "Source key not found."
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
$path = "C:\ProgramData\ssh\sshd_config"
|
||||
$content = Get-Content $path -Raw
|
||||
$content = $content -replace "(?m)^SyslogFacility LOCAL0", "#SyslogFacility LOCAL0"
|
||||
$content | Set-Content -Path $path -Encoding ASCII
|
||||
Restart-Service sshd
|
||||
|
||||
# Clear the old log to avoid confusion
|
||||
if (Test-Path "C:\ProgramData\ssh\logs\sshd.log") {
|
||||
Clear-Content "C:\ProgramData\ssh\logs\sshd.log"
|
||||
}
|
||||
Write-Output "sshd_config updated for file logging and restarted."
|
||||
@@ -0,0 +1,28 @@
|
||||
$path = "C:\ProgramData\ssh\administrators_authorized_keys"
|
||||
|
||||
if (Test-Path $path) {
|
||||
$acl = Get-Acl $path
|
||||
|
||||
# Disable inheritance
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
|
||||
# Remove all existing access rules (we're starting fresh)
|
||||
$acl.Access | ForEach-Object { $acl.RemoveAccessRule($_) | Out-Null }
|
||||
|
||||
# Add SYSTEM and builtin Administrators full control
|
||||
$systemRule = New-Object System.Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\SYSTEM", "FullControl", "Allow")
|
||||
# Using well-known SID for Builtin Administrators (S-1-5-32-544) to avoid localization issues (Administradores vs Administrators)
|
||||
$adminSid = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544")
|
||||
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule($adminSid, "FullControl", "Allow")
|
||||
|
||||
$acl.AddAccessRule($systemRule)
|
||||
$acl.AddAccessRule($adminRule)
|
||||
|
||||
# Set Owner to Builtin Administrators
|
||||
$acl.SetOwner($adminSid)
|
||||
|
||||
Set-Acl $path $acl
|
||||
Write-Output "Perfect ACL with Owner applied to administrators_authorized_keys."
|
||||
} else {
|
||||
Write-Output "File not found."
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
$path = "C:\ProgramData\ssh\sshd_config"
|
||||
$content = Get-Content $path
|
||||
$newContent = $content | ForEach-Object {
|
||||
if ($_ -match "Match Group administrators") {
|
||||
"#$_"
|
||||
} elseif ($_ -match "AuthorizedKeysFile __PROGRAMDATA__") {
|
||||
"#$_"
|
||||
} elseif ($_ -match "#PubkeyAuthentication yes") {
|
||||
"PubkeyAuthentication yes"
|
||||
} else {
|
||||
$_
|
||||
}
|
||||
}
|
||||
$newContent | Set-Content $path
|
||||
Restart-Service sshd
|
||||
@@ -0,0 +1,33 @@
|
||||
# Temporary fix to test if StrictModes is blocking the key
|
||||
$path = "C:\ProgramData\ssh\sshd_config"
|
||||
|
||||
$sshdConfig = @"
|
||||
Port 7022
|
||||
|
||||
# Authentication
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
StrictModes no
|
||||
|
||||
# Host keys
|
||||
HostKey __PROGRAMDATA__/ssh/ssh_host_rsa_key
|
||||
HostKey __PROGRAMDATA__/ssh/ssh_host_ecdsa_key
|
||||
HostKey __PROGRAMDATA__/ssh/ssh_host_ed25519_key
|
||||
|
||||
# Logging
|
||||
SyslogFacility LOCAL0
|
||||
LogLevel DEBUG3
|
||||
|
||||
# Subsystem
|
||||
Subsystem sftp sftp-server.exe
|
||||
|
||||
Match Group administrators
|
||||
AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
|
||||
"@
|
||||
|
||||
$sshdConfig | Set-Content -Path $path -Encoding ASCII
|
||||
Write-Output ">>> sshd_config reescrito con StrictModes no para prueba"
|
||||
|
||||
Restart-Service sshd
|
||||
Write-Output ">>> sshd reiniciado OK"
|
||||
@@ -0,0 +1,74 @@
|
||||
function Log-Msg {
|
||||
param([string]$Message)
|
||||
Write-Host $Message -ForegroundColor Cyan
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://100.111.195.4:8000/log" -Method Post -Body "TELEMETRIA: $Message" -ContentType "text/plain" -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {}
|
||||
}
|
||||
|
||||
$TARGET_PORT = 7022
|
||||
|
||||
Log-Msg "=========================================================="
|
||||
Log-Msg "OPT ADN: GESTION INTELIGENTE DE OPENSSH SERVER (PORT $TARGET_PORT)"
|
||||
Log-Msg "=========================================================="
|
||||
|
||||
try {
|
||||
# 1. Verificar Instalación
|
||||
$sshCheck = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
|
||||
if ($sshCheck.State -ne 'Installed') {
|
||||
Log-Msg "Estado: NO INSTALADO. Iniciando instalacion..."
|
||||
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 | Out-Null
|
||||
Log-Msg "Caracteristica instalada con exito."
|
||||
} else {
|
||||
Log-Msg "Estado: INSTALADO. Verificando configuracion..."
|
||||
}
|
||||
|
||||
# 2. Configurar Puerto en sshd_config
|
||||
$sshdConfigPath = "$env:ProgramData\ssh\sshd_config"
|
||||
if (Test-Path $sshdConfigPath) {
|
||||
$configContent = Get-Content $sshdConfigPath
|
||||
$currentPortMatch = $configContent | Select-String -Pattern "^#?Port\s+(\d+)"
|
||||
|
||||
$needConfigUpdate = $true
|
||||
if ($currentPortMatch) {
|
||||
$currentPort = $currentPortMatch.Matches[0].Groups[1].Value
|
||||
if ($currentPort -eq $TARGET_PORT.ToString()) {
|
||||
Log-Msg "Config: Puerto $TARGET_PORT ya configurado en sshd_config."
|
||||
$needConfigUpdate = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($needConfigUpdate) {
|
||||
Log-Msg "Config: Cambiando puerto a $TARGET_PORT..."
|
||||
if ($currentPortMatch) {
|
||||
$newContent = $configContent -replace "^#?Port\s+\d+", "Port $TARGET_PORT"
|
||||
} else {
|
||||
$newContent = $configContent + "`nPort $TARGET_PORT"
|
||||
}
|
||||
$newContent | Set-Content $sshdConfigPath
|
||||
$global:RestartSshNeeded = $true
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Configurar Firewall
|
||||
$ruleName = "OpenSSH-Server-In-TCP-$TARGET_PORT"
|
||||
if (!(Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue)) {
|
||||
Log-Msg "Firewall: Creando regla para puerto $TARGET_PORT..."
|
||||
New-NetFirewallRule -Name $ruleName -DisplayName "OpenSSH Server (Port $TARGET_PORT)" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort $TARGET_PORT | Out-Null
|
||||
}
|
||||
|
||||
# 4. Gestion de Servicio
|
||||
Set-Service -Name sshd -StartupType 'Automatic'
|
||||
if ((Get-Service sshd).Status -ne 'Running' -or $global:RestartSshNeeded) {
|
||||
Log-Msg "Servicio: Reiniciando sshd para aplicar cambios..."
|
||||
Restart-Service sshd -Force -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
Log-Msg "Servicio: sshd ya esta operando."
|
||||
}
|
||||
|
||||
# 5. Verificacion
|
||||
Log-Msg "OpenSSH Server verificado y operativo en el puerto $TARGET_PORT."
|
||||
} catch {
|
||||
Log-Msg "CRITICO: Fallo en gestion SSH: $_"
|
||||
}
|
||||
Log-Msg "=========================================================="
|
||||
@@ -0,0 +1 @@
|
||||
try { $pw = ConvertTo-SecureString '"$PW"' -AsPlainText -Force; $cred = New-Object System.Management.Automation.PSCredential ('DASUTEN\admindasu', $pw); Add-Computer -DomainName dasuten.utnlr -Credential $cred -Server 100.85.117.101 -Force -Restart } catch { $_ | Out-File join_error.txt }
|
||||
@@ -0,0 +1,21 @@
|
||||
# Rebuild sshd_config for dasu-srvv-dc
|
||||
$sshdConfig = @"
|
||||
Port 7022
|
||||
PubkeyAuthentication yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
Subsystem sftp sftp-server.exe
|
||||
LogLevel DEBUG3
|
||||
|
||||
# COMENTADO: los admins usaran su .ssh/authorized_keys personal
|
||||
#Match Group administrators
|
||||
# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
|
||||
"@
|
||||
|
||||
$path = "C:\ProgramData\ssh\sshd_config"
|
||||
$sshdConfig | Set-Content -Path $path -Encoding UTF8
|
||||
Write-Output "sshd_config reescrito OK"
|
||||
Write-Output (Get-Content $path)
|
||||
|
||||
Restart-Service sshd
|
||||
Write-Output "sshd reiniciado"
|
||||
@@ -0,0 +1,13 @@
|
||||
# Start a temporary SSHD server on port 7023 in debug mode and capture its output
|
||||
$sshdPath = "C:\Windows\System32\OpenSSH\sshd.exe"
|
||||
$logPath = "C:\ProgramData\ssh\sshd_debug.log"
|
||||
|
||||
Remove-Item $logPath -ErrorAction SilentlyContinue
|
||||
|
||||
# Open Firewall
|
||||
New-NetFirewallRule -Name "OpenSSH-Debug" -DisplayName "OpenSSH-Debug" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 7023 -ErrorAction SilentlyContinue
|
||||
|
||||
# Start the process in the background, redirecting stderr and stdout
|
||||
$proc = Start-Process -FilePath $sshdPath -ArgumentList "-p 7023 -d" -RedirectStandardOutput $logPath -RedirectStandardError $logPath -WindowStyle Hidden -PassThru
|
||||
|
||||
Write-Output "Temporary SSHD started on port 7023."
|
||||
@@ -0,0 +1,68 @@
|
||||
function Log-C2 {
|
||||
param([string]$msg)
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] $msg"
|
||||
Write-Host "📡 $line" -ForegroundColor Cyan
|
||||
try {
|
||||
if (-not $WZ_HOST) { $WZ_HOST = "100.111.195.4:8000" }
|
||||
Invoke-RestMethod -Uri "http://$WZ_HOST/log" -Method Post -Body $line -ContentType "text/plain" -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Log-C2 "=========================================================="
|
||||
Log-C2 "🚀 INICIANDO DESPLIEGUE DE TAILSCALE"
|
||||
Log-C2 "=========================================================="
|
||||
|
||||
$tailscalePath = "C:\Program Files\Tailscale\tailscale.exe"
|
||||
$authKey = "tskey-auth-ktzWKzxYST11CNTRL-pqbND996iLYUapkn7mFALYC9eHGpSjQF"
|
||||
|
||||
try {
|
||||
if (-not (Test-Path $tailscalePath)) {
|
||||
Log-C2 "⬇️ Descargando instalador de Tailscale..."
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$installer = "$env:TEMP\tailscale-setup.exe"
|
||||
Invoke-WebRequest -Uri "https://pkgs.tailscale.com/stable/tailscale-setup.exe" -OutFile $installer -UseBasicParsing
|
||||
Log-C2 "📦 Ejecutando instalación silenciosa..."
|
||||
Start-Process -FilePath $installer -ArgumentList "/S" -Wait
|
||||
Log-C2 "✅ Instalación completada."
|
||||
} else {
|
||||
Log-C2 "ℹ️ Tailscale ya se encuentra instalado."
|
||||
}
|
||||
|
||||
Log-C2 "🔄 Reiniciando estado de Tailscale..."
|
||||
& $tailscalePath down 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
& $tailscalePath reset 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
Log-C2 "🔑 Autenticando con el nodo..."
|
||||
$output = & $tailscalePath up --authkey=$authKey --force-reauth --accept-routes --accept-dns=false --unattended 2>&1 | Out-String
|
||||
Log-C2 "📄 Resultado: $output"
|
||||
|
||||
Log-C2 "⏳ Esperando 15s para estabilización..."
|
||||
Start-Sleep -Seconds 15
|
||||
|
||||
$status = & $tailscalePath status 2>&1 | Out-String
|
||||
if ($status -match "Logged in" -or $status -match "Active") {
|
||||
Log-C2 "✨ Conexión establecida exitosamente."
|
||||
$ip = & $tailscalePath ip -4 2>&1 | Out-String
|
||||
Log-C2 "📍 IP Tailscale: $ip"
|
||||
} else {
|
||||
Log-C2 "⚠️ Estado: $status"
|
||||
}
|
||||
|
||||
$svc = Get-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
||||
if ($svc) {
|
||||
Log-C2 "⚙️ Configurando servicio en modo automático..."
|
||||
Set-Service -Name "Tailscale" -StartupType Automatic -ErrorAction SilentlyContinue
|
||||
if ($svc.Status -ne "Running") {
|
||||
Start-Service -Name "Tailscale" -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
Log-C2 "❌ ERROR FATAL: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Log-C2 "=========================================================="
|
||||
Log-C2 "🏁 OPERACION FINALIZADA"
|
||||
Log-C2 "=========================================================="
|
||||
@@ -0,0 +1,49 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
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
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCxCHrqnaVrkihb/0yBAxzcb9knhCAWSFFC9J6rEKHE4Yu5DW3XZ/aoWKFq1NEZS6yNQqBqFGvAe32RBwnX7RiJEkhYgS/xr2SJorIxTWWJUOmV5i7OopQQQoZ7YUoY56tdVrYaulJCDyJkvnmGZwGdUS782BEb9nWDwXd5t1qDZJtCWCV/xK34zRuTy7DqHPU3sx+PUwj0KWy8rQBeRK2KNvHCpcVTQvx4u1QS9Cbbqsic+BrZfaU1LBa70GdJL8GY9p1ZIlSqqPMyzcpILh7CwrnyNr7hj+kaKVIdzeo+0WIf97XsjD0dNNt5VPP3kBrnTA62EQ6eraZl7V01Hx6gK178K9C3fcgPHsxZASf8/Vo+zOeg3ChCYQFPF9YzSyQpAJE2yT7ptuzXulhifpRBIs9d8HS5+6mWYgaLPqwDpZRvYB5NsX96m2OX3LBIeeJm4ZEc4531jsvOJNPiok9SVg083aAUbflFKtWV5M01X54rT4+uU6Uy4LRQGXPr5Mor+BBZyZ91bTtX8nGOVZrQ3Cxm4jc0Q7Aj902SX4yqrImLuoM7XIbVTHGQt+gsgVSxEbb4KaF4jOvRZKYrDcSFrJN9KWcx5SLCDZmN4TlGqgbVPrfxHzF7BqLrzq+bcG+BVm3DP3ROBwAjbXPcztiaDHpPjWDiZByy5nHnzqmmCw== rmonla@srv-ns8
|
||||
@@ -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.
|
||||
@@ -0,0 +1 @@
|
||||
OpenSSH-Win64.zip
|
||||
@@ -0,0 +1,40 @@
|
||||
function Log($msg) {
|
||||
Write-Host $msg -ForegroundColor Cyan
|
||||
if ($WZ_HOST) {
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://${WZ_HOST}/log" -Method Post -Body @{msg=$msg} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log " REPARANDO PERMISOS E INICIANDO SSHD "
|
||||
Log "=========================================="
|
||||
|
||||
Log "1) Bypass ExecutionPolicy para FixHostFilePermissions..."
|
||||
try {
|
||||
# Cambiamos la politica solo para este proceso
|
||||
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
|
||||
$out = & "C:\OpenSSH-Win64\FixHostFilePermissions.ps1" -Confirm:$false 2>&1
|
||||
foreach ($line in $out) { Log " -> $line" }
|
||||
Log "Script de permisos finalizado."
|
||||
} catch {
|
||||
Log "Error ejecutando script de permisos: $_"
|
||||
}
|
||||
|
||||
Log "2) Iniciando servicio sshd..."
|
||||
try {
|
||||
Start-Service sshd -ErrorAction Stop
|
||||
$svc = Get-Service sshd
|
||||
Log "Servicio sshd. Estado actual: $($svc.Status) !!"
|
||||
} catch {
|
||||
Log "Fallo al iniciar el servicio: $_"
|
||||
Log "Ejecutando sshd.exe -t para ver el error real de config:"
|
||||
$test = & "C:\OpenSSH-Win64\sshd.exe" -t 2>&1
|
||||
foreach ($line in $test) { Log " sshd -t: $line" }
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log " FIN REPARACION "
|
||||
Log "=========================================="
|
||||
return
|
||||
@@ -0,0 +1,115 @@
|
||||
$ErrorActionPreference = "Continue"
|
||||
$ZOMBI_URL = "http://10.0.10.8:8000"
|
||||
|
||||
function Log($msg) {
|
||||
$ts = Get-Date -Format "HH:mm:ss"
|
||||
Write-Host "[$ts] $msg" -ForegroundColor Cyan
|
||||
try { Invoke-RestMethod -Uri "$ZOMBI_URL/log" -Method Post -Body @{msg="[$env:COMPUTERNAME] $msg"} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
}
|
||||
|
||||
$DEST = "C:\SysDasuten"
|
||||
$ZIP = "C:\TEMP\runSysDasuten.zip"
|
||||
$SQL_SERVER = "sql-dasuten"
|
||||
$SQL_DB = "sysdasuten"
|
||||
|
||||
Log "=========================================="
|
||||
Log "DEPLOY SYSDASUTEN — $env:COMPUTERNAME"
|
||||
Log "=========================================="
|
||||
|
||||
# --- PASO 1: Descargar ZIP ---
|
||||
if (-not (Test-Path $DEST\Sistema\DasutenSQL.exe)) {
|
||||
Log "PASO 1: Descargando runSysDasuten.zip (~268 MB)..."
|
||||
New-Item -ItemType Directory -Path "C:\TEMP" -Force | Out-Null
|
||||
try {
|
||||
Invoke-WebRequest -Uri "$ZOMBI_URL/payloads/runSysDasuten.zip" -OutFile $ZIP -UseBasicParsing -ErrorAction Stop
|
||||
Log "Descarga completada: $(((Get-Item $ZIP).Length / 1MB).ToString('N0')) MB"
|
||||
} catch {
|
||||
Log "ERROR descarga: $($_.Exception.Message)"
|
||||
return
|
||||
}
|
||||
|
||||
# --- PASO 2: Extraer ---
|
||||
Log "PASO 2: Extrayendo a $DEST..."
|
||||
New-Item -ItemType Directory -Path $DEST -Force | Out-Null
|
||||
Expand-Archive -Path $ZIP -DestinationPath "C:\" -Force
|
||||
# El zip contiene runSysDasuten/ como raiz, renombrar
|
||||
if (Test-Path "C:\runSysDasuten") {
|
||||
Copy-Item -Path "C:\runSysDasuten\*" -Destination $DEST -Recurse -Force
|
||||
Remove-Item "C:\runSysDasuten" -Recurse -Force
|
||||
}
|
||||
Remove-Item $ZIP -Force -ErrorAction SilentlyContinue
|
||||
Log "Extraido OK."
|
||||
} else {
|
||||
Log "PASO 1-2: SKIP — Ya existe $DEST\Sistema\DasutenSQL.exe"
|
||||
}
|
||||
|
||||
# --- PASO 3: Modificar Kermet.ini ---
|
||||
Log "PASO 3: Configurando Kermet.ini..."
|
||||
$iniPaths = @("$DEST\Kermet.ini", "$DEST\Sistema\Kermet.ini")
|
||||
foreach ($ini in $iniPaths) {
|
||||
if (Test-Path $ini) {
|
||||
$content = Get-Content $ini -Raw
|
||||
$content = $content -replace 'SERVER=srvFENIX', "SERVER=$SQL_SERVER"
|
||||
$content = $content -replace 'SERVER=172\.16\.9\.204\\SQL2017', "SERVER=$SQL_SERVER"
|
||||
Set-Content $ini $content -Force
|
||||
Log "Actualizado: $ini -> SERVER=$SQL_SERVER"
|
||||
}
|
||||
}
|
||||
|
||||
# --- PASO 4: Instalar Fuentes ---
|
||||
Log "PASO 4: Instalando fuentes..."
|
||||
$fontDir = "$DEST\Fonts\Fonts"
|
||||
if (Test-Path $fontDir) {
|
||||
$shell = New-Object -ComObject Shell.Application
|
||||
$fontsFolder = $shell.NameSpace(0x14) # Windows Fonts folder
|
||||
Get-ChildItem "$fontDir\*.ttf" | ForEach-Object {
|
||||
$fontPath = $_.FullName
|
||||
$fontName = $_.Name
|
||||
if (-not (Test-Path "C:\Windows\Fonts\$fontName")) {
|
||||
Copy-Item $fontPath "C:\Windows\Fonts\" -Force
|
||||
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"
|
||||
New-ItemProperty -Path $regKey -Name $fontName -Value $fontName -PropertyType String -Force | Out-Null
|
||||
Log "Fuente instalada: $fontName"
|
||||
} else {
|
||||
Log "Fuente ya existe: $fontName"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log "WARN: No se encontro directorio de fuentes"
|
||||
}
|
||||
|
||||
# --- PASO 5: Verificar firewall SQL en sql-dasuten ---
|
||||
Log "PASO 5: Probando conectividad SQL..."
|
||||
$tcpTest = Test-NetConnection -ComputerName $SQL_SERVER -Port 1433 -WarningAction SilentlyContinue
|
||||
if ($tcpTest.TcpTestSucceeded) {
|
||||
Log "Conexion TCP a ${SQL_SERVER}:1433 OK"
|
||||
} else {
|
||||
Log "WARN: No se puede conectar a ${SQL_SERVER}:1433 — verificar firewall"
|
||||
}
|
||||
|
||||
# --- PASO 6: Probar conexion SQL con sqlcmd (si existe) ---
|
||||
$sqlcmd = Get-Command sqlcmd -ErrorAction SilentlyContinue
|
||||
if ($sqlcmd) {
|
||||
Log "PASO 6: Probando SQL con sqlcmd..."
|
||||
$result = sqlcmd -S $SQL_SERVER -E -Q "SELECT DB_NAME() AS db_actual; SELECT name FROM sys.databases WHERE name = '$SQL_DB'" -W 2>&1
|
||||
Log "SQL Result: $result"
|
||||
} else {
|
||||
Log "PASO 6: SKIP — sqlcmd no disponible (normal en Win10), probar con DasutenSQL.exe"
|
||||
}
|
||||
|
||||
# --- PASO 7: Crear acceso directo ---
|
||||
Log "PASO 7: Creando acceso directo en Escritorio..."
|
||||
$desktopPath = [Environment]::GetFolderPath('CommonDesktopDirectory')
|
||||
$shortcutPath = "$desktopPath\DasutenSQL.lnk"
|
||||
$WshShell = New-Object -ComObject WScript.Shell
|
||||
$shortcut = $WshShell.CreateShortcut($shortcutPath)
|
||||
$shortcut.TargetPath = "$DEST\Sistema\DasutenSQL.exe"
|
||||
$shortcut.WorkingDirectory = "$DEST\Sistema"
|
||||
$shortcut.Description = "Sistema DASUTEN"
|
||||
$shortcut.Save()
|
||||
Log "Acceso directo creado: $shortcutPath"
|
||||
|
||||
Log "=========================================="
|
||||
Log "DEPLOY SYSDASUTEN COMPLETADO"
|
||||
Log "Ejecutar desde: $DEST\Sistema\DasutenSQL.exe"
|
||||
Log "=========================================="
|
||||
@@ -0,0 +1,40 @@
|
||||
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 "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 (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. Ahora SSH esta disponible."
|
||||
Log-Msg "=========================================================="
|
||||
return
|
||||
@@ -0,0 +1,49 @@
|
||||
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 "INSTALANDO OPENSSH (con fix TEMP)"
|
||||
Log-Msg "=========================================================="
|
||||
|
||||
try {
|
||||
# Fix TEMP: usar carpeta de sistema en vez de la del usuario
|
||||
$env:TEMP = "C:\Windows\Temp"
|
||||
$env:TMP = "C:\Windows\Temp"
|
||||
Log-Msg "TEMP redirigido a C:\Windows\Temp"
|
||||
|
||||
$cap = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
|
||||
if ($cap.State -eq 'Installed') {
|
||||
Log-Msg "OpenSSH Server ya instalado."
|
||||
} else {
|
||||
Log-Msg "Instalando OpenSSH.Server..."
|
||||
Add-WindowsCapability -Online -Name $cap.Name
|
||||
Log-Msg "Instalado OK."
|
||||
}
|
||||
|
||||
Set-Service sshd -StartupType Automatic
|
||||
Start-Service sshd -ErrorAction SilentlyContinue
|
||||
Log-Msg "Servicio sshd configurado e iniciado."
|
||||
|
||||
# Puerto 7022
|
||||
$cfg = "C:\ProgramData\ssh\sshd_config"
|
||||
$lines = Get-Content $cfg | Where-Object { $_ -notmatch '^\s*#?\s*Port\s+\d+' }
|
||||
@("Port 7022") + $lines | Set-Content $cfg -Force
|
||||
New-NetFirewallRule -Name 'SSH-7022' -DisplayName 'SSH 7022' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 7022 -ErrorAction SilentlyContinue | Out-Null
|
||||
|
||||
Stop-Service sshd -Force
|
||||
Start-Sleep -Seconds 2
|
||||
Start-Service sshd
|
||||
Log-Msg "OpenSSH configurado en puerto 7022"
|
||||
|
||||
} catch {
|
||||
Log-Msg "ERROR: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
Log-Msg "=========================================================="
|
||||
Log-Msg "OPERACION COMPLETADA"
|
||||
Log-Msg "=========================================================="
|
||||
@@ -0,0 +1,87 @@
|
||||
$ErrorActionPreference = "Continue"
|
||||
$logFile = "C:\ssh_install.log"
|
||||
|
||||
function Log($msg) {
|
||||
$ts = Get-Date -Format "HH:mm:ss"
|
||||
$line = "[$ts] $msg"
|
||||
Write-Host $line -ForegroundColor Cyan
|
||||
Add-Content -Path $logFile -Value $line
|
||||
try {
|
||||
if ($WZ_HOST) {
|
||||
(New-Object Net.WebClient).DownloadString("http://${WZ_HOST}/log?msg=$msg") | Out-Null
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log "OPENSSH LEGACY INSTALLER (2008R2+)"
|
||||
Log "=========================================="
|
||||
|
||||
$sshDir = "C:\OpenSSH-Win64"
|
||||
$zipPath = "C:\OpenSSH-Win64.zip"
|
||||
$sshUrl = "http://${WZ_HOST}/payloads/OpenSSH-Win64.zip"
|
||||
|
||||
try {
|
||||
if (Test-Path "$sshDir\sshd.exe") {
|
||||
Log "OpenSSH ya instalado en $sshDir"
|
||||
} else {
|
||||
Log "Paso 1: Descargando ZIP..."
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.DownloadFile($sshUrl, $zipPath)
|
||||
Log "Descarga OK ($zipPath)"
|
||||
|
||||
Log "Paso 2: Extrayendo ZIP..."
|
||||
$shell = New-Object -ComObject Shell.Application
|
||||
$zip = $shell.NameSpace($zipPath)
|
||||
$dest = $shell.NameSpace("C:\")
|
||||
$dest.CopyHere($zip.Items(), 0x14)
|
||||
Log "Extraido en $sshDir"
|
||||
}
|
||||
|
||||
Log "Paso 3: Instalando servicio sshd..."
|
||||
cd $sshDir
|
||||
powershell -ExecutionPolicy Bypass -File "$sshDir\install-sshd.ps1"
|
||||
Log "Servicio instalado"
|
||||
|
||||
Log "Paso 4: Generando host keys..."
|
||||
if (!(Test-Path "$sshDir\ssh_host_rsa_key")) {
|
||||
& "$sshDir\ssh-keygen.exe" -A
|
||||
Log "Host keys generadas"
|
||||
} else {
|
||||
Log "Host keys ya existen"
|
||||
}
|
||||
|
||||
Log "Paso 5: Configurando puerto 7022..."
|
||||
$cfg = "$sshDir\sshd_config"
|
||||
if (Test-Path "$sshDir\sshd_config_default") {
|
||||
Copy-Item "$sshDir\sshd_config_default" $cfg -Force
|
||||
}
|
||||
if (Test-Path $cfg) {
|
||||
$lines = Get-Content $cfg | Where-Object { $_ -notmatch '^\s*#?\s*Port\s+\d+' }
|
||||
$newContent = @("Port 7022") + $lines
|
||||
$newContent | Set-Content $cfg -Force
|
||||
} else {
|
||||
"Port 7022" | Set-Content $cfg
|
||||
}
|
||||
Log "Config: Port 7022"
|
||||
|
||||
Log "Paso 6: Firewall..."
|
||||
netsh advfirewall firewall add rule name="SSH-7022" dir=in action=allow protocol=TCP localport=7022
|
||||
Log "Firewall actualizado"
|
||||
|
||||
Log "Paso 7: Iniciando servicio..."
|
||||
Set-Service sshd -StartupType Automatic -ErrorAction SilentlyContinue
|
||||
Start-Service sshd -ErrorAction Stop
|
||||
Log "Servicio sshd INICIADO en puerto 7022"
|
||||
|
||||
} catch {
|
||||
Log "ERROR: $($_.Exception.Message)"
|
||||
Log "LINEA: $($_.InvocationInfo.ScriptLineNumber)"
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log "FIN. Log guardado en $logFile"
|
||||
Log "=========================================="
|
||||
Write-Host ""
|
||||
Write-Host "Presiona ENTER para cerrar..." -ForegroundColor Yellow
|
||||
Read-Host
|
||||
@@ -0,0 +1,106 @@
|
||||
$ErrorActionPreference = "Continue"
|
||||
$env:TEMP = "C:\Windows\Temp"
|
||||
$env:TMP = "C:\Windows\Temp"
|
||||
|
||||
function Log($msg) {
|
||||
$ts = Get-Date -Format "HH:mm:ss"
|
||||
Write-Host "[$ts] $msg" -ForegroundColor Cyan
|
||||
try { Invoke-RestMethod -Uri "http://10.0.10.8:8000/log" -Method Post -Body @{msg="[$env:COMPUTERNAME] $msg"} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
}
|
||||
|
||||
$hn = $env:COMPUTERNAME
|
||||
$os = (Get-WmiObject Win32_OperatingSystem).Caption
|
||||
$sshPort = 7022
|
||||
|
||||
Log "=========================================="
|
||||
Log "W-ZOMBI SETUP — $hn"
|
||||
Log "OS: $os"
|
||||
Log "=========================================="
|
||||
|
||||
# --- PASO 1: Detectar e instalar OpenSSH ---
|
||||
$sshInstalled = $false
|
||||
|
||||
# Verificar si sshd ya existe
|
||||
if (Get-Service sshd -ErrorAction SilentlyContinue) {
|
||||
Log "PASO 1: sshd ya existe."
|
||||
$sshInstalled = $true
|
||||
} elseif ($os -match "200[38]|Vista|XP") {
|
||||
# Legacy: Win32-OpenSSH manual
|
||||
Log "PASO 1: OS Legacy ($os) — instalando Win32-OpenSSH..."
|
||||
$zip = "C:\OpenSSH-Win64.zip"
|
||||
$dir = "C:\OpenSSH-Win64"
|
||||
try {
|
||||
(New-Object Net.WebClient).DownloadFile("http://10.0.10.8:8000/payloads/OpenSSH-Win64.zip", $zip)
|
||||
$shell = New-Object -ComObject Shell.Application
|
||||
$shell.NameSpace("C:\").CopyHere($shell.NameSpace($zip).Items(), 0x14)
|
||||
& "$dir\install-sshd.ps1" 2>&1 | Out-Null
|
||||
& "$dir\ssh-keygen.exe" -A 2>&1 | Out-Null
|
||||
Set-Service sshd -StartupType Automatic
|
||||
$sshInstalled = $true
|
||||
Log "Win32-OpenSSH instalado OK."
|
||||
} catch {
|
||||
Log "ERROR Legacy: $($_.Exception.Message)"
|
||||
}
|
||||
} else {
|
||||
# Moderno: Add-WindowsCapability (Win10/2016+)
|
||||
Log "PASO 1: OS Moderno — instalando via Add-WindowsCapability..."
|
||||
try {
|
||||
$cap = Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
|
||||
if ($cap.State -ne 'Installed') {
|
||||
Add-WindowsCapability -Online -Name $cap.Name | Out-Null
|
||||
}
|
||||
Set-Service sshd -StartupType Automatic
|
||||
Start-Service sshd -ErrorAction SilentlyContinue
|
||||
$sshInstalled = $true
|
||||
Log "OpenSSH Server instalado OK."
|
||||
} catch {
|
||||
Log "ERROR Moderno: $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $sshInstalled) {
|
||||
Log "FALLO: No se pudo instalar SSH. Abortando."
|
||||
return
|
||||
}
|
||||
|
||||
# --- PASO 2: Configurar puerto 7022 ---
|
||||
Log "PASO 2: Configurando puerto $sshPort..."
|
||||
|
||||
$cfgPaths = @("C:\ProgramData\ssh\sshd_config", "C:\OpenSSH-Win64\sshd_config")
|
||||
$cfgPath = $cfgPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
|
||||
if ($cfgPath) {
|
||||
$lines = Get-Content $cfgPath | Where-Object { $_ -notmatch '^\s*#?\s*Port\s+\d+' }
|
||||
@("Port $sshPort") + $lines | Set-Content $cfgPath -Force
|
||||
Log "Config: $cfgPath -> Port $sshPort"
|
||||
} else {
|
||||
Log "WARN: No se encontro sshd_config"
|
||||
}
|
||||
|
||||
# --- PASO 3: Firewall ---
|
||||
Log "PASO 3: Firewall..."
|
||||
try {
|
||||
New-NetFirewallRule -Name "SSH-$sshPort" -DisplayName "SSH $sshPort" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort $sshPort -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {
|
||||
netsh advfirewall firewall add rule name="SSH-$sshPort" dir=in action=allow protocol=TCP localport=$sshPort 2>&1 | Out-Null
|
||||
}
|
||||
Log "Firewall: puerto $sshPort abierto"
|
||||
|
||||
# --- PASO 4: Reiniciar sshd ---
|
||||
Log "PASO 4: Reiniciando sshd..."
|
||||
Stop-Service sshd -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
Start-Service sshd -ErrorAction SilentlyContinue
|
||||
Log "sshd reiniciado."
|
||||
|
||||
# --- PASO 5: Verificar ---
|
||||
$listening = netstat -an | Select-String ":$sshPort\s+.*LISTEN"
|
||||
if ($listening) {
|
||||
Log "VERIFICADO: $hn escuchando en puerto $sshPort"
|
||||
} else {
|
||||
Log "WARN: Puerto $sshPort no detectado en netstat"
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log "SETUP COMPLETADO — $hn : $sshPort"
|
||||
Log "=========================================="
|
||||
@@ -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
|
||||
@@ -0,0 +1,42 @@
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Log($msg) {
|
||||
$ts = Get-Date -Format "HH:mm:ss"
|
||||
Write-Host "[$ts] $msg" -ForegroundColor Cyan
|
||||
try { Invoke-RestMethod -Uri "http://10.0.10.8:8000/log" -Method Post -Body @{msg="[$env:COMPUTERNAME] $msg"} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null } catch {}
|
||||
}
|
||||
|
||||
Log "=========================================="
|
||||
Log "SQL SYSADMIN FIX — Agregando DASUTEN\admindasu"
|
||||
Log "=========================================="
|
||||
|
||||
# Paso 1: Parar SQL Server
|
||||
Log "PASO 1: Deteniendo MSSQLSERVER..."
|
||||
net stop MSSQLSERVER /y 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
# Paso 2: Iniciar en modo single-user
|
||||
Log "PASO 2: Iniciando en modo single-user..."
|
||||
net start MSSQLSERVER /m 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
# Paso 3: Agregar sysadmin
|
||||
Log "PASO 3: Agregando DASUTEN\admindasu como sysadmin..."
|
||||
$result = sqlcmd -S localhost -E -Q "ALTER SERVER ROLE sysadmin ADD MEMBER [DASUTEN\admindasu]; SELECT 'SYSADMIN_OK' AS resultado;" -W 2>&1
|
||||
Log "Resultado: $result"
|
||||
|
||||
# Paso 4: Reiniciar normal
|
||||
Log "PASO 4: Reiniciando SQL Server en modo normal..."
|
||||
net stop MSSQLSERVER /y 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 3
|
||||
net start MSSQLSERVER 2>&1 | Out-Null
|
||||
Start-Sleep -Seconds 5
|
||||
|
||||
# Paso 5: Verificar
|
||||
Log "PASO 5: Verificando acceso..."
|
||||
$check = sqlcmd -S localhost -E -Q "SELECT SYSTEM_USER AS usuario, IS_SRVROLEMEMBER('sysadmin') AS es_sysadmin" -W 2>&1
|
||||
Log "Verificacion: $check"
|
||||
|
||||
Log "=========================================="
|
||||
Log "SQL SYSADMIN FIX COMPLETADO"
|
||||
Log "=========================================="
|
||||
@@ -0,0 +1,48 @@
|
||||
function Log-Msg {
|
||||
param([string]$Message)
|
||||
Write-Host $Message -ForegroundColor Cyan
|
||||
try {
|
||||
if ($WZ_HOST) {
|
||||
Invoke-RestMethod -Uri "http://${WZ_HOST}/log" -Method Post -Body @{msg=$Message} -UseBasicParsing -ErrorAction SilentlyContinue | Out-Null
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
Log-Msg "=========================================================="
|
||||
Log-Msg "CONFIGURANDO SSH EN PUERTO 7022"
|
||||
Log-Msg "=========================================================="
|
||||
|
||||
$cfg = "C:\ProgramData\ssh\sshd_config"
|
||||
|
||||
try {
|
||||
# Leer config, quitar cualquier linea Port anterior
|
||||
$lines = Get-Content $cfg | Where-Object { $_ -notmatch '^\s*#?\s*Port\s+\d+' }
|
||||
|
||||
# Agregar Port 7022 al inicio
|
||||
$lines = @("Port 7022") + $lines
|
||||
|
||||
# Escribir config limpia
|
||||
$lines | Set-Content $cfg -Force
|
||||
Log-Msg "sshd_config actualizado: Port 7022"
|
||||
|
||||
# Firewall: agregar 7022, quitar 22
|
||||
New-NetFirewallRule -Name 'SSH-7022' -DisplayName 'SSH 7022' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 7022 -ErrorAction SilentlyContinue | Out-Null
|
||||
Remove-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue
|
||||
Log-Msg "Firewall actualizado: 7022 abierto, 22 cerrado"
|
||||
|
||||
# Reiniciar servicio
|
||||
Stop-Service sshd -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
Start-Service sshd
|
||||
Log-Msg "Servicio sshd reiniciado OK"
|
||||
|
||||
$port = (Get-Content $cfg | Select-String "^Port").ToString().Trim()
|
||||
Log-Msg "Verificacion: $port"
|
||||
} catch {
|
||||
Log-Msg "ERROR: $_"
|
||||
}
|
||||
|
||||
Log-Msg "=========================================================="
|
||||
Log-Msg "OPERACION COMPLETADA"
|
||||
Log-Msg "=========================================================="
|
||||
return
|
||||
@@ -0,0 +1,38 @@
|
||||
@echo off
|
||||
chcp 65001 >nul 2>&1
|
||||
title W-ZOMBI Agent - Conectado a srv-ns8
|
||||
color 0A
|
||||
|
||||
echo ======================================================
|
||||
echo W-ZOMBI Agent — Esperando ordenes de srv-ns8
|
||||
echo ======================================================
|
||||
echo.
|
||||
echo Este agente se queda abierto y ejecuta lo que
|
||||
echo srv-ns8 le envie. NO cerrar esta ventana.
|
||||
echo.
|
||||
echo Servidor: http://10.0.10.8:8000
|
||||
echo ======================================================
|
||||
echo.
|
||||
|
||||
:LOOP
|
||||
echo [%TIME%] Consultando payload activo...
|
||||
|
||||
REM Descargar y ejecutar el payload activo via PowerShell
|
||||
powershell -ExecutionPolicy Bypass -Command ^
|
||||
"$ErrorActionPreference='Continue'; " ^
|
||||
"try { " ^
|
||||
" $script = (New-Object Net.WebClient).DownloadString('http://10.0.10.8:8000/payloads/activo.ps1'); " ^
|
||||
" if ($script -and $script.Trim() -ne '') { " ^
|
||||
" Write-Host '[ZOMBI] Ejecutando payload...' -ForegroundColor Green; " ^
|
||||
" Invoke-Expression $script " ^
|
||||
" } else { " ^
|
||||
" Write-Host '[ZOMBI] Sin payload pendiente.' -ForegroundColor DarkGray " ^
|
||||
" } " ^
|
||||
"} catch { " ^
|
||||
" Write-Host '[ZOMBI] Sin conexion a srv-ns8, reintentando...' -ForegroundColor Yellow " ^
|
||||
"}"
|
||||
|
||||
echo.
|
||||
echo [%TIME%] Esperando 15 segundos... (CTRL+C para salir)
|
||||
timeout /t 15 /nobreak >nul
|
||||
goto LOOP
|
||||
Executable
+155
@@ -0,0 +1,155 @@
|
||||
#!/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
|
||||
EXT_PORT = (ENV['EXT_PORT'] || PORT).to_i
|
||||
require 'socket'
|
||||
def get_ip
|
||||
ENV['HOST_IP'] || Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }&.ip_address || '127.0.0.1'
|
||||
end
|
||||
HOST_IP = get_ip
|
||||
PUBLIC_URL = "#{HOST_IP}:#{EXT_PORT}"
|
||||
|
||||
# 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 #{PUBLIC_URL}/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
|
||||
$WZ_HOST = "#{PUBLIC_URL}"
|
||||
Write-Host "======================================================" -ForegroundColor Green
|
||||
Write-Host " ZOMBI C2 LOOP — Conectado a $WZ_HOST " -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://#{PUBLIC_URL}/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
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env ruby
|
||||
require_relative '../lib/wzombi/config'
|
||||
require_relative '../lib/wzombi/server'
|
||||
require_relative '../lib/wzombi/command_store'
|
||||
|
||||
base = File.expand_path("..", __dir__)
|
||||
config = WZombi::Config.new(base_path: base)
|
||||
|
||||
def help
|
||||
puts <<~HELP
|
||||
Uso:
|
||||
w-zombi start
|
||||
w-zombi cmd "<comando>"
|
||||
w-zombi ayuda | -h | -help
|
||||
|
||||
Ejemplo ADN:
|
||||
adn tools w-zombi cmd "ipconfig"
|
||||
HELP
|
||||
end
|
||||
|
||||
cmd = ARGV[0]
|
||||
|
||||
case cmd
|
||||
when "start"
|
||||
WZombi::Server.new(config).start
|
||||
|
||||
when "cmd"
|
||||
command = ARGV[1]
|
||||
if command.nil?
|
||||
puts "Falta comando"
|
||||
exit
|
||||
end
|
||||
store = WZombi::CommandStore.new(config.cmd_path)
|
||||
store.update(command)
|
||||
puts "Comando actualizado: #{command}"
|
||||
|
||||
when "ayuda", "-h", "-help", nil
|
||||
help
|
||||
|
||||
else
|
||||
puts "Comando desconocido"
|
||||
help
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
{"id":0,"cmd":""}
|
||||
@@ -0,0 +1,25 @@
|
||||
require 'json'
|
||||
|
||||
module WZombi
|
||||
class CommandStore
|
||||
def initialize(path)
|
||||
@path = path
|
||||
init_file
|
||||
end
|
||||
|
||||
def init_file
|
||||
File.write(@path, { id: 0, cmd: "" }.to_json) unless File.exist?(@path)
|
||||
end
|
||||
|
||||
def current
|
||||
JSON.parse(File.read(@path))
|
||||
end
|
||||
|
||||
def update(cmd)
|
||||
data = current
|
||||
data["id"] += 1
|
||||
data["cmd"] = cmd
|
||||
File.write(@path, data.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module WZombi
|
||||
class Config
|
||||
attr_reader :puerto, :base_path
|
||||
|
||||
def initialize(base_path:)
|
||||
@base_path = base_path
|
||||
@puerto = ENV.fetch("WZ_PORT", 8000).to_i
|
||||
end
|
||||
|
||||
def log_path
|
||||
File.join(base_path, "data/telemetria.log")
|
||||
end
|
||||
|
||||
def cmd_path
|
||||
File.join(base_path, "data/cmd.json")
|
||||
end
|
||||
|
||||
def payloads_path
|
||||
File.join(base_path, "payloads")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module WZombi
|
||||
module Engine
|
||||
class PS1Builder
|
||||
def self.build(host:)
|
||||
<<~PS1
|
||||
# W-ZOMBI ENGINE
|
||||
$WZ_HOST = "#{host}"
|
||||
$LAST_CMD_ID = 0
|
||||
|
||||
function Get-Cmd {
|
||||
try {
|
||||
return Invoke-RestMethod -Uri "http://$WZ_HOST/cmd"
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function Send-Log {
|
||||
param([string]$msg)
|
||||
try {
|
||||
Invoke-RestMethod -Uri "http://$WZ_HOST/log" -Method Post -Body $msg -ContentType "text/plain"
|
||||
} catch {}
|
||||
}
|
||||
|
||||
while ($true) {
|
||||
$cmdData = Get-Cmd
|
||||
if ($cmdData.id -ne $LAST_CMD_ID) {
|
||||
$LAST_CMD_ID = $cmdData.id
|
||||
$result = Invoke-Expression $cmdData.cmd 2>&1 | Out-String
|
||||
Send-Log $result
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
PS1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
module WZombi
|
||||
class Logger
|
||||
def initialize(path)
|
||||
@path = path
|
||||
end
|
||||
|
||||
def log(msg)
|
||||
line = "[#{Time.now.strftime('%H:%M:%S')}] #{msg}"
|
||||
File.open(@path, "a") { |f| f.puts(line) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
require 'json'
|
||||
|
||||
module WZombi
|
||||
module Routes
|
||||
class Cmd
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(_req, res)
|
||||
res['Content-Type'] = 'application/json'
|
||||
res.body = File.read(@config.cmd_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
module WZombi
|
||||
module Routes
|
||||
class Log
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(req, res)
|
||||
File.open(@config.log_path, "a") { |f| f.puts(req.body) }
|
||||
res.body = "OK"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
require_relative '../engine/ps1_builder'
|
||||
|
||||
module WZombi
|
||||
module Routes
|
||||
class Zombi
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(_req, res)
|
||||
res.body = Engine::PS1Builder.build(host: "127.0.0.1:#{@config.puerto}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
require 'webrick'
|
||||
require_relative 'routes/zombi'
|
||||
require_relative 'routes/cmd'
|
||||
require_relative 'routes/log'
|
||||
|
||||
module WZombi
|
||||
class Server
|
||||
def initialize(config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def start
|
||||
server = WEBrick::HTTPServer.new(Port: @config.puerto)
|
||||
|
||||
server.mount_proc('/zombi.ps1', &Routes::Zombi.new(@config).method(:call))
|
||||
server.mount_proc('/cmd', &Routes::Cmd.new(@config).method(:call))
|
||||
server.mount_proc('/log', &Routes::Log.new(@config).method(:call))
|
||||
|
||||
trap("INT") { server.shutdown }
|
||||
|
||||
puts "[W-ZOMBI] Servidor corriendo en puerto #{@config.puerto}"
|
||||
server.start
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user