
리버스 엔지니어링 노트, 난독화 해제된 소스, IOC, 그리고 Tourmaline ClickFix Python RAT의 YARA 규칙으로, DNS 터널과 블록체인 데드드롭 C2를 다룹니다.
최근에 "Tourmaline"이라고 자칭하는 교묘하게 설계된 악성코드 한 조각을 우연히 발견했습니다.
이 README는 이 저장소가 시작될 당시 제가 시험 준비로 매우 바빴기 때문에 Claude가 아주 친절하게 작성해 주었습니다.
이 악성코드를 익스플로잇하고 복호화하는 데 수학 준비 시간에서 큰 부분을 빼앗겼습니다!
블로그 글을 읽어보세요!
교육 및 연구 목적으로만 사용
모든 샘플은 악성코드 분석 및 방어 연구를 위해 제공됩니다. 샌드박스 처리된 에어갭 환경 외부에서 실행하지 마십시오.
Tourmaline(Tourmaline.exe)은 ClickFix 캠페인을 통해 배포되는 정교한 다단계 Windows 정보 탈취기/백도어입니다. ClickFix 캠페인이란 가짜 Cloudflare "사람인지 확인하세요" 브라우저 팝업으로 사용자를 속여 악성 명령이나 설치 프로그램을 수동으로 실행하게 만드는 수법입니다.
이 바이너리는 전체 Python 3.11 런타임과 두 단계의 난독화된 Python 페이로드를 번들로 포함한 맞춤 제작 Inno Setup 6.7.0 (Revision 2, 64-bit offsets) 설치 프로그램입니다. 이는 다음과 같은 기능을 갖춘 지속적인 백도어를 구축합니다:
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
이 악성코드는 ClickFix를 통해 배포됩니다. ClickFix는 침해되었거나 공격자가 제어하는 웹사이트가 가짜 Cloudflare CAPTCHA 또는 브라우저 확인 페이지를 오버레이하는 사회공학 기법입니다. 오버레이는 피해자에게 다음을 지시합니다:
Win+R 누르기붙여넣은 명령은 Tourmaline.exe를 다운로드하여 조용히 실행합니다. 설치 프로그램은 /VERYSILENT /SUPPRESSMSGBOXES /NORESTART로 실행됩니다.
| 속성 | 값 |
|---|---|
| 파일 이름 | Tourmaline.exe |
| 크기 | 11,341,339 바이트 (~10.8 MB) |
| MD5 | b74ac808dea2de31caf024310694ef0c |
| SHA256 | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
| 형식 | Inno Setup 6.7.0 Unicode (Revision 2, 64-bit offsets) |
| PE 유형 | 32-bit PE, 11개 섹션 |
| 오버레이 크기 | 10,447,387 바이트 |
| Inno 매직 오프셋 | 0xD9864 (890,148 바이트) |
| 앱 이름 | Tourmaline |
| 앱 버전 | 2.63.519 |
| 앱 GUID | {2C25872D-85FC-44C7-9B16-844E39E50A44} |
| 설치 경로 | {commonappdata}\Tourmaline |
| 번들 런타임 | Python 3.11 (전체 임베드) |
| 페이로드 파일 | solid LZMA2 스트림 내 36개 파일 |
참고: 기본
innoextract 1.9는 이 바이너리를 추출할 수 없습니다. Revision 1만 지원하기 때문입니다. Revision 2는 64비트 오프셋을 사용하며 사용자 지정 파싱이 필요합니다 (src/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
로더(main.py)는 99,999,999부터 시작하는 카운트다운을 사용하여 XOR 복호화 키를 찾습니다. 실제 머신에서는 _n이 높은 값에서 시작하고 실제 값이 14,511,188 근처이기 때문에 몇 초 만에 완료됩니다. 시간 제한이 짧은 샌드박스에서는 이 루프가 절대 완료되지 않습니다.
키 구조:
full_key = struct.pack('>I', _n) + bytes.fromhex('2b0cffe07b06ac25793b3f00cfaa2dd5881c2d6378165247539dbbdaeb')
우리는 평문의 처음 16바이트(g1 = lambda l6, )를 알고 있으므로, 알려진 평문 XOR 공격을 통해 _n을 즉시 복구할 수 있습니다:
key_prefix = bytes(ciphertext[i] ^ known_plaintext[i] for i in range(4))
# Result: key_prefix = 00dd6c54 → _n = 14,511,188
src/decrypt_payload.py를 사용하여 이를 재현하십시오.
| 매개변수 | 값 |
|---|---|
| C2 IP | 158.94.211.185 |
| C2 포트 | 53 (UDP) |
| 프로토콜 | 사용자 지정 DNS-over-UDP 터널 |
| 스푸핑된 도메인 | microsoft.com |
| ChaCha20 키 | 36f555c87f71581f57d83576c88193fdc00a0173f741a3e198e2d7abdc9cda59 |
| 블록체인 컨트랙트 | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 (Sepolia) |
| ECDSA 공개키 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEt17l3rGHHR9sYdHEvV8S+JRRCUHQQveSadlGk04xM/8WClDf/67Tyritt2+T18SY8n0xv1TKKl0AgnNFuboEEQ== |
| 서비스 이름 | SystemService / TourmalineUpdate |
| 뮤텍스 | Global\Tourmaline_<MachineGuid-hash> |
악성코드는 Sepolia 테스트넷의 이더리움 스마트 컨트랙트를 호출하여 활성 C2 IP 주소를 검색합니다. 이는 공격자가 컨트랙트를 업데이트하여 언제든지 C2 IP를 변경할 수 있음을 의미하며, 기존의 IP 차단 목록 기반 C2 차단 방식은 효과가 없습니다.
Contract : 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5
Selector : 0xeb9fd6fe
Network : Ethereum Sepolia (chainId 11155111)
RPC : https://ethereum-sepolia-rpc.publicnode.com
Result : ChaCha20-encrypted blob containing current C2 IP
반환된 데이터는 하드코딩된 ChaCha20 키로 복호화되어 실제 C2 주소를 드러냅니다. 분석 시점에 이 주소는 **158.94.211.185**로 확인되었습니다(실시간 RPC 호출로 검증).
모든 C2 트래픽은 원시 UDP DNS 쿼리 형태로 C2 IP의 53번 포트로 직접 전송됩니다. 쿼리는 <encoded_data>.microsoft.com 형식으로 구성되어 DNS 기반 필터링을 우회하며 정상적인 Windows 원격 측정 데이터처럼 보입니다.
socket.getaddrinfo나 어떤 DNS 라이브러리도 사용하지 않음백도어는 임의의 Python exec() 엔진을 구현합니다. 작업은 다음과 같이 처리됩니다:
exec() 실행지원되는 작업 유형: Python exec, 파일 업로드/다운로드, 셸 명령, 자체 업데이트.
| 유형 | 값 |
|---|---|
| C2 IP | 158.94.211.185 |
| C2 포트 | 53/UDP |
| 스푸핑된 도메인 | *.microsoft.com (사용자 정의 UDP, 실제 DNS 아님) |
| Ethereum RPC | https://ethereum-sepolia-rpc.publicnode.com |
| 스마트 컨트랙트 | 0x2d7a04cca0c34005f58393f30ac725e25f19e5f5 |
| 파일 | SHA256 |
|---|---|
Tourmaline.exe | c9b390b3b7148f549df86503858d52e030b2b5e78fed0bc525ded5927f9265d6 |
Tourmaline.exe (MD5) | b74ac808dea2de31caf024310694ef0c |
| 키 | 값 |
|---|---|
HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid | 피해자 핑거프린팅을 위해 읽음 |
%APPDATA%\Tourmaline\ | 설치 디렉터리 |
| 작업 스케줄러 | TourmalineUpdate |
| 서비스 표시 이름 | Hardware monitoring service |
| 뮤텍스 | Global\Tourmaline_<hash> |
다음을 포괄하는 탐지 규칙은 iocs/rules.yar를 참조하십시오:
이 악성코드가 머신에서 실행된 경우: