
Note di reverse engineering, codice sorgente deoffuscato, IOC e regole YARA per il RAT Python Tourmaline ClickFix, che coprono il suo tunnel DNS e il C2 dead-drop su blockchain.
Recentemente mi sono imbattuto in un malware abilmente progettato che si fa chiamare "Torumaline".
Questo README è stato scritto molto gentilmente da Claude, dato che ero super impegnato con gli esami al momento della nascita di questo repository.
Analizzare ed decifrare questo malware mi ha portato via una grossa fetta del mio tempo dedicato alla preparazione di matematica!
Leggi il blogpost!
SOLO PER SCOPI EDUCATIVI E DI RICERCA
Tutti i campioni sono forniti per l'analisi di malware e la ricerca difensiva. Non eseguire al di fuori di un ambiente sandboxed e air-gapped.
Tourmaline (Tourmaline.exe) è un sofisticato infostealer/backdoor Windows multi-stadio distribuito tramite una campagna ClickFix - un falso popup del browser Cloudflare "Verifica che sei umano" che induce le vittime a eseguire manualmente un comando o un installer malevolo.
Il binario è un installer Inno Setup 6.7.0 (Revisione 2, offset a 64 bit) costruito su misura che include un runtime Python 3.11 completo e due stadi di payload Python offuscato. Stabilisce una 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
Il malware viene distribuito tramite ClickFix - una tecnica di social engineering in cui un sito web compromesso o controllato dall'attaccante sovrappone un falso CAPTCHA Cloudflare o una pagina di verifica del browser. L'overlay istruisce la vittima a:
Win+RIl comando incollato scarica ed esegue silenziosamente Tourmaline.exe. L'installer viene eseguito con /VERYSILENT /SUPPRESSMSGBOXES /NORESTART.
| Proprietà | Valore |
|---|---|
| Nome file | Tourmaline.exe |
| Dimensione | 11.341.339 byte (~10,8 MB) |
| MD5 | b74ac808dea2de31caf024310694ef0c |
| SHA256 | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
| Formato | Inno Setup 6.7.0 Unicode (Revisione 2, offset a 64 bit) |
| Tipo PE | PE a 32 bit, 11 sezioni |
| Dimensione overlay | 10.447.387 byte |
| Offset magic Inno | 0xD9864 (890.148 byte) |
| Nome app | Tourmaline |
| Versione app | 2.63.519 |
| GUID app | {2C25872D-85FC-44C7-9B16-844E39E50A44} |
| Percorso di installazione | {commonappdata}\Tourmaline |
| Runtime incluso | Python 3.11 (embed completo) |
| File payload | 36 file in uno stream LZMA2 solid |
Nota:
innoextract 1.9standard non riesce a estrarre questo binario — supporta solo la Revisione 1. La Revisione 2 utilizza offset a 64 bit e richiede un parsing personalizzato (vedisrc/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
Il loader (main.py) utilizza un countdown da 99.999.999 per trovare la chiave di decifratura XOR. Su una macchina reale questo si completa in pochi secondi (poiché _n parte da un valore alto e il valore reale è vicino a 14.511.188). In una sandbox con un limite di tempo breve, il ciclo non si completa mai.
Struttura della chiave:
full_key = struct.pack('>I', _n) + bytes.fromhex('2b0cffe07b06ac25793b3f00cfaa2dd5881c2d6378165247539dbbdaeb')
Poiché conosciamo i primi 16 byte del plaintext (g1 = lambda l6, ), possiamo recuperare _n istantaneamente tramite un attacco XOR known-plaintext:
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 per riprodurre questo.
| Parametro | Valore |
|---|---|
| IP C2 | 158.94.211.185 |
| Porta C2 | 53 (UDP) |
| Protocollo | Tunnel DNS-over-UDP personalizzato |
| Dominio spoofato | microsoft.com |
| Chiave ChaCha20 | 36f555c87f71581f57d83576c88193fdc00a0173f741a3e198e2d7abdc9cda59 |
| Contratto blockchain | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 (Sepolia) |
| Chiave pubblica ECDSA | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEt17l3rGHHR9sYdHEvV8S+JRRCUHQQveSadlGk04xM/8WClDf/67Tyritt2+T18SY8n0xv1TKKl0AgnNFuboEEQ== |
| Nome servizio | SystemService / TourmalineUpdate |
| Mutex | Global\Tourmaline_<MachineGuid-hash> |
Il malware chiama uno smart contract Ethereum sulla testnet Sepolia per recuperare l'indirizzo IP C2 attivo. Ciò significa che l'attaccante può cambiare l'IP C2 in qualsiasi momento aggiornando il contratto — l'interruzione tradizionale del C2 basata su blocklist di IP è inefficace.
Contract : 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5
Selector : 0xeb9fd6fe
Network : Ethereum Sepolia (chainId 11155111)
RPC : https://ethereum-sepolia-rpc.publicnode.com
Result : ChaCha20-encrypted blob containing current C2 IP
I dati restituiti vengono decifrati con la chiave ChaCha20 hardcoded per rivelare l'indirizzo C2 attivo. Al momento dell'analisi, questo si risolveva in 158.94.211.185 (verificato tramite chiamata RPC live).
Tutto il traffico C2 viene inviato come query DNS UDP grezze direttamente all'IP C2 sulla porta 53. Le query sono strutturate come <encoded_data>.microsoft.com, aggirando il filtraggio basato su DNS e apparendo come telemetria Windows legittima.
socket.getaddrinfo di Python né alcuna libreria DNSLa backdoor implementa un motore exec() Python arbitrario. I task sono:
exec() in un namespace persistente con cattura dell'outputTipi di task supportati: exec Python, upload/download di file, comando shell, self-update.
| Tipo | Valore |
|---|---|
| IP C2 | 158.94.211.185 |
| Porta C2 | 53/UDP |
| Dominio spoofato | *.microsoft.com (UDP personalizzato, non DNS reale) |
| RPC Ethereum | https://ethereum-sepolia-rpc.publicnode.com |
| Smart Contract | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 |
| File | SHA256 |
|---|---|
Tourmaline.exe | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
Tourmaline.exe (MD5) | b74ac808dea2de31caf024310694ef0c |
| Chiave | Valore |
|---|---|
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid | Letto per il fingerprinting della vittima |
%APPDATA%\Tourmaline\ | Directory di installazione |
| Task Scheduler | TourmalineUpdate |
| Nome visualizzato del servizio | Hardware monitoring service |
| Mutex | Global\Tourmaline_<hash> |
Vedi iocs/rules.yar per le regole di rilevamento che coprono:
Se questo malware è stato eseguito su una macchina:
taskkill /F /IM pythonw.exe%APPDATA%\Tourmaline\ o %COMMONAPPDATA%\Tourmaline\TourmalineUpdateHKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run per Tourmaline o SystemService158.94.211.185:53/UDP; blocca tutto il traffico UDP/53 in uscita non previstoethereum-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 | Valore |
|---|---|
| Data di scoperta | Settembre 2026 |
| Ricercatore | Vihaan Pundir (v-pun215) |
| Vettore di distribuzione | ClickFix falso CAPTCHA Cloudflare |
| Classificazione | Backdoor / C2 con DNS Tunnel / Dead-Drop su Blockchain |
| Piattaforma | Windows (64-bit) |
Vedi LICENSE. Questo repository è solo per scopi educativi e di ricerca difensiva.