
Notas de ingeniería inversa, código fuente desofuscado, IOCs y reglas YARA para el RAT Python Tourmaline ClickFix, que abarca su túnel DNS y su C2 de dead-drop en blockchain.
Recientemente me topé con una pieza de malware ingeniosamente diseñada que se hace llamar "Torumaline".
Este README fue escrito muy amablemente por Claude, ya que estaba súper ocupado con exámenes en el momento del inicio de este repositorio.
¡Explotar y descifrar este malware me quitó una enorme cantidad de tiempo de preparación para matemáticas!
Lee el blogpost!
SOLO PARA PROPÓSITOS EDUCATIVOS Y DE INVESTIGACIÓN
Todas las muestras se proporcionan para análisis de malware e investigación defensiva. No las ejecutes fuera de un entorno aislado y sin conexión a la red.
Tourmaline (Tourmaline.exe) es un sofisticado infostealer/backdoor de Windows de múltiples etapas distribuido mediante una campaña ClickFix: una ventana emergente falsa de Cloudflare "Verify you are human" que engaña a los usuarios para que ejecuten manualmente un comando o instalador malicioso.
El binario es un instalador personalizado de Inno Setup 6.7.0 (Revision 2, offsets de 64 bits) que incluye un runtime completo de Python 3.11 y dos etapas de payload Python ofuscado. Establece un backdoor persistente con:
microsoft.com)tourmaline/
├── README.md # This file
├── LICENSE # Research use license
├── .gitignore
│
├── src/ # Extracted & deobfuscated source code
│ ├── stage1_loader.py # Stage 1: Original obfuscated XOR time-lock loader (main.py)
│ ├── stage2_backdoor.py # Stage 2: Deobfuscated core backdoor (from QGBdu.dxf)
│ └── decrypt_payload.py # Utility: O(1) payload key recovery & decryption
│
├── iocs/ # Indicators of Compromise
│ ├── indicators.json # Structured IOC data (IPs, hashes, domains, registry)
│ ├── indicators.csv # Flat CSV for SIEM import
│ └── rules.yar # YARA detection rules
│
└── samples/ # Malware samples (password-protected)
├── README.md # Sample archive instructions
├── Tourmaline_sample.zip # Password: infected - contains Tourmaline.exe
└── QGBdu.dxf # Raw encrypted Stage 2 payload blob
El malware se distribuye mediante ClickFix, una técnica de ingeniería social en la que un sitio web comprometido o controlado por el atacante superpone un CAPTCHA falso de Cloudflare o una página de verificación del navegador. La superposición indica a la víctima que:
Win+REl comando pegado descarga y ejecuta silenciosamente Tourmaline.exe. El instalador se ejecuta con /VERYSILENT /SUPPRESSMSGBOXES /NORESTART.
| Propiedad | Valor |
|---|---|
| Nombre del archivo | Tourmaline.exe |
| Tamaño | 11,341,339 bytes (~10.8 MB) |
| MD5 | b74ac808dea2de31caf024310694ef0c |
| SHA256 | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
| Formato | Inno Setup 6.7.0 Unicode (Revision 2, offsets de 64 bits) |
| Tipo de PE | PE de 32 bits, 11 secciones |
| Tamaño del overlay | 10,447,387 bytes |
| Offset mágico de Inno | 0xD9864 (890,148 bytes) |
| Nombre de la app | Tourmaline |
| Versión de la app | 2.63.519 |
| GUID de la app | {2C25872D-85FC-44C7-9B16-844E39E50A44} |
| Ruta de instalación | {commonappdata}\Tourmaline |
| Runtime incluido | Python 3.11 (embed completo) |
| Archivos de payload | 36 archivos en un flujo LZMA2 sólido |
Nota: El
innoextract 1.9estándar no puede extraer este binario; solo admite Revision 1. Revision 2 usa offsets de 64 bits y requiere un análisis personalizado (versrc/decrypt_payload.py).
Tourmaline.exe
│
├─ Inno Setup installer runs silently
│ ├─ Extracts Python 3.11 runtime to %APPDATA%\Tourmaline\
│ ├─ Extracts main.py (Stage 1 loader)
│ ├─ Extracts QGBdu.dxf (encrypted Stage 2 blob)
│ ├─ Kills any existing pythonw.exe instances
│ └─ Registers persistence (Task Scheduler / registry)
│ └─ Name: "TourmalineUpdate" / "Hardware monitoring service"
│
├─ Stage 1: main.py (XOR Time-Lock Loader)
│ ├─ Counts _n from 99,999,999 → 0 (anti-sandbox delay ~hours on slow VMs)
│ ├─ At each _n, constructs 33-byte XOR key: struct.pack('>I', _n) + hardcoded_suffix
│ ├─ Tests key against known plaintext header of QGBdu.dxf
│ └─ On match: decrypts QGBdu.dxf entirely and exec()s the result
│ └─ O(1) bypass: known-plaintext attack on first 4 bytes (see below)
│
└─ Stage 2: QGBdu.dxf → Python backdoor
├─ Reads MachineGuid from HKLM\SOFTWARE\Microsoft\Cryptography
├─ Derives mutex Global\Tourmaline_<hash>
├─ Resolves C2 IP via Ethereum dead-drop (Sepolia testnet)
├─ Establishes DNS tunnel to C2
└─ Poll-execute loop: fetches tasks, exec()s Python, returns output
El loader (main.py) usa una cuenta regresiva desde 99,999,999 para encontrar la clave de descifrado XOR. En una máquina real esto se completa en segundos (porque _n comienza alto y el valor verdadero está cerca de 14,511,188). En una sandbox con un límite de tiempo corto, el bucle nunca se completa.
Estructura de la clave:
full_key = struct.pack('>I', _n) + bytes.fromhex('2b0cffe07b06ac25793b3f00cfaa2dd5881c2d6378165247539dbbdaeb')
Como conocemos los primeros 16 bytes del texto plano (g1 = lambda l6, ), podemos recuperar _n instantáneamente mediante un ataque XOR de texto plano conocido:
key_prefix = bytes(ciphertext[i] ^ known_plaintext[i] for i in range(4))
# Result: key_prefix = 00dd6c54 → _n = 14,511,188
Usa src/decrypt_payload.py para reproducir esto.
| Parámetro | Valor |
|---|---|
| IP de C2 | 158.94.211.185 |
| Puerto de C2 | 53 (UDP) |
| Protocolo | Túnel DNS-over-UDP personalizado |
| Dominio suplantado | microsoft.com |
| Clave ChaCha20 | 36f555c87f71581f57d83576c88193fdc00a0173f741a3e198e2d7abdc9cda59 |
| Contrato de blockchain | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 (Sepolia) |
| Clave pública ECDSA | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEt17l3rGHHR9sYdHEvV8S+JRRCUHQQveSadlGk04xM/8WClDf/67Tyritt2+T18SY8n0xv1TKKl0AgnNFuboEEQ== |
| Nombre del servicio | SystemService / TourmalineUpdate |
| Mutex | Global\Tourmaline_<MachineGuid-hash> |
El malware llama a un contrato inteligente de Ethereum en la testnet Sepolia para recuperar la dirección IP activa del C2. Esto significa que el atacante puede cambiar la IP del C2 en cualquier momento actualizando el contrato; la interrupción tradicional del C2 basada en listas de bloqueo de IPs es ineficaz.
Contract : 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5
Selector : 0xeb9fd6fe
Network : Ethereum Sepolia (chainId 11155111)
RPC : https://ethereum-sepolia-rpc.publicnode.com
Result : ChaCha20-encrypted blob containing current C2 IP
Los datos devueltos se descifran con la clave ChaCha20 hardcodeada para revelar la dirección C2 activa. En el momento del análisis, esto resolvía a 158.94.211.185 (verificado mediante una llamada RPC en vivo).
Todo el tráfico del C2 se envía como consultas DNS UDP sin procesar directamente a la IP del C2 en el puerto 53. Las consultas se estructuran como <encoded_data>.microsoft.com, evadiendo el filtrado basado en DNS y pareciendo telemetría legítima de Windows.
socket.getaddrinfo de Python ni ninguna biblioteca DNSEl backdoor implementa un motor de exec() de Python arbitrario. Las tareas son:
exec() en un espacio de nombres persistente con captura de salidaTipos de tareas admitidos: exec de Python, carga/descarga de archivos, comando de shell, autoactualización.
| Tipo | Valor |
|---|---|
| IP de C2 | 158.94.211.185 |
| Puerto de C2 | 53/UDP |
| Dominio suplantado | *.microsoft.com (UDP personalizado, no DNS real) |
| RPC de Ethereum | https://ethereum-sepolia-rpc.publicnode.com |
| Contrato inteligente | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 |
| Archivo | SHA256 |
|---|---|
Tourmaline.exe | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
Tourmaline.exe (MD5) | b74ac808dea2de31caf024310694ef0c |
| Clave | Valor |
|---|---|
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid | Se lee para la identificación de la víctima |
%APPDATA%\Tourmaline\ | Directorio de instalación |
| Task Scheduler | TourmalineUpdate |
| Nombre para mostrar del servicio | Hardware monitoring service |
| Mutex | Global\Tourmaline_<hash> |
Consulta iocs/rules.yar para ver las reglas de detección que cubren:
Si este malware se ha ejecutado en una máquina:
taskkill /F /IM pythonw.exe%APPDATA%\Tourmaline\ o %COMMONAPPDATA%\Tourmaline\TourmalineUpdateHKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run en busca de Tourmaline o SystemService158.94.211.185:53/UDP; bloquea todo el tráfico UDP/53 saliente inesperadoethereum-sepolia-rpc.publicnode.compip install pyOpenSSL cryptography
# From the repo root:
python3 src/decrypt_payload.py samples/QGBdu.dxf decrypted_stage2.py
7z x -pinfected samples/Tourmaline_sample.zip
| Campo | Valor |
|---|---|
| Fecha de descubrimiento | Septiembre de 2026 |
| Investigador | Vihaan Pundir (v-pun215) |
| Vector de distribución | CAPTCHA falso de Cloudflare mediante ClickFix |
| Clasificación | Backdoor / C2 por túnel DNS / Dead-drop en blockchain |
| Plataforma | Windows (64 bits) |
Consulta LICENSE. Este repositorio es solo para propósitos educativos y de investigación defensiva.