TL;DR
Static analysis of a StealC infostealer that uses the Heaven’s Gate technique to transition from 32-bit to 64-bit mode, trying to bypass EDR hooks. Features RC4-encrypted config strings, a custom “MZER” payload marker, and direct syscalls. Successfully extracted the C2 server, decryption key, and 150+ encrypted strings without dynamic execution — full IOCs at the bottom for anyone who just wants the indicators.
Initial Recon - Wait, that’s not “MZ”…
Got my hands on a suspicious 610KB PE32 executable. Standard stuff at first:
1 | $ file sample.exe |
Loaded it into Binary Ninja and started poking around. The size immediately caught my attention - 610KB is pretty chunky for a simple loader. Usually means there’s something embedded.
While browsing through the decompiled code, I spotted something weird at address 0x44e9f7:
1 | void* data_28604 = sub_451e7f(arg1) |
“MZER”? Not “MZ”? That’s the PE signature every Windows executable starts with. This was clearly looking for an embedded payload with a modified marker to avoid basic signature scanning.
Time to find that payload.
Finding the Hidden Payload
Searched for the “MZER” bytes in the hex view and found it at offset 0x65b38:
1 | 00065b38: 4d 5a 45 52 4c 01 64 86 00 00 00 00 00 00 00 00 MZERL.d......... |
Right after “MZER” there’s the PE COFF header (4c 01 = x64 machine type). This is a 64-bit payload embedded in a 32-bit loader. Interesting.
Extracted it:
1 | $ dd if=sample.exe of=payload.exe bs=1 skip=$((0x65b38)) |
The file command doesn’t recognize it because it’s missing the DOS stub. No problem - I can patch that:
1 | # Add minimal DOS stub |
Now it loads perfectly in Binary Ninja as a PE64 with base address 0x140000000.
Heaven’s Gate - The 32-to-64 Transition
Back to the loader. I wanted to understand how it was injecting this 64-bit payload from a 32-bit process. That’s when I found the Heaven’s Gate technique.
What’s Heaven’s Gate?
On 64-bit Windows, 32-bit processes can temporarily switch to 64-bit mode using far returns with specific segment selectors. This lets them:
- Execute 64-bit code directly
- Make 64-bit syscalls
- Bypass userland hooks (EDR/AV solutions primarily hook 32-bit APIs)
The Assembly
Function at address 0x451e7f contained the transition code:
1 | ; Address: 0x451f5b |
That retf (far return) with 0x33 as the code segment is the magic. After this instruction, the CPU switches to 64-bit mode and starts executing x64 instructions.
The Key - Finding “9vX9oFZSsq”
I went back to analyze the actual decryption functions more carefully. Found the main orchestrator at sub_43fa43:
1 | int32_t sub_43fa43(void* arg1, char* b64_string, int32_t arg3, ...) |
The key address 0x490780 was just a raw pointer here, not the value itself — so the next step was pulling up cross-references to that address in Binary Ninja to find where it actually gets written. That led to the initialization function sub_443ce3:
1 | void sub_443ce3() |
THERE IT IS! The actual RC4 key: 9vX9oFZSsq
Not some repetitive junk string, but a short 10-byte key that looks randomly generated.
Decrypting Everything
Now that I had the real key, I reverse-engineered the crypto routine at sub_44a020. It was standard RC4:
1 | def rc4_decrypt(key, data): |
IT WORKED!
Decrypted all 150+ strings and got:
Windows APIs:
1 | GetUserDefaultLangID |
WinHTTP APIs:
1 | WinHttpSendRequest |
Crypto APIs:
1 | CryptUnprotectData |
Firefox NSS:
1 | PK11_GetInternalKeySlot |
Target paths:
1 | C:\ProgramData\ |
Browser files:
1 | passwords.txt |
Steam files:
1 | Software\Valve\Steam |
And most importantly…
The C2 - Divided and Conquered
Two particularly interesting strings:
String 1 (address 0x45ee0c):
1 | b64 = "GH5j9qydTQUXUW1K4+ty8thabO0=" |
String 2 (address 0x45ee2c):
1 | b64 = "Xzwn5fLRWlIWSDVIqe1z9cdFK7SF" |
Combined: http://23.94.252.171/60cdc8e27a6d4451.php
That’s the C2 server! The malware split it into two separate encrypted strings to make static analysis harder. Smart.
Quick VirusTotal check confirmed this IP was associated with StealC campaigns.
Attribution - “builder_v2”
While analyzing the extracted 64-bit payload, I found a debug string in the .rdata section:
1 | C:\builder_v2\stealc\json.h |
Boom. This is StealC. The family was first advertised as Malware-as-a-Service on January 9, 2023 by a threat actor going by “Plymouth” on the XSS and BHF Russian-speaking forums, pitched as a ready-to-use stealer drawing on Vidar, Raccoon, RedLine and Mars (Sekoia, Feb. 2023). Sekoia had already tracked dozens of samples and 40+ C2 servers within weeks of release, so by late 2025 it’s a well-established commodity stealer rather than a fringe tool. The “builder_v2” debug path confirms this specific sample was built with the second generation of their builder.
Also found two campaign identifiers:
1 | // Hex ID |
These IDs are unique to each campaign/buyer of the StealC malware kit.
What It Steals
Based on the decrypted strings and analyzing the payload, this thing targets:
Browsers (Chrome, Edge, Brave, Firefox)
- Saved passwords (using DPAPI + AES-256-GCM)
- Cookies
- Autofill data
- Browsing history
- Extensions (crypto wallets, etc.)
Gaming
- Steam session files and tokens
- Steam config files (ssfn*, config.vdf, loginusers.vdf)
System Info
- Hardware details (CPU, RAM)
- Geolocation data
- Running processes
- Installed applications
- Screenshot of your desktop
Exfiltration
All data gets packaged as JSON and sent via HTTP POST to the C2:
1 | POST /60cdc8e27a6d4451.php |
Detection & IOCs
File Hashes
Loader (32-bit):
1 | SHA256: e7885e82d3e17e921144b8e098b96ca8dc091b92355f60954cc5c62a46211eca |
Payload (64-bit):
1 | SHA256: eec6e91674aa92c6f65a42802f8da1bbf4c0d4169c471f18c85bdf9ec0b6de95 |
Network IOCs
1 | IP: 23.94.252.171 |
Unique Strings
1 | MZER # Payload marker |
Behavioral Detection
Look for processes doing this combination:
- 32-bit process executing 64-bit syscalls (Heaven’s Gate pattern)
- Accessing multiple browser profile directories rapidly
- Reading
Local State,Login Data, andCookiesfiles - Accessing Steam registry keys + .vdf files
- GDI+ screenshot capture
- HTTP POST with JSON payload to suspicious IP
Confirmed vs. Assumed
Confirmed (verified in this analysis):
- Heaven’s Gate 32→64 bit transition at
0x451f5b(retfto segment0x33) - RC4 key
9vX9oFZSsqand the algorithm, validated by successfully decrypting the C2 string and API names - C2 endpoint
http://23.94.252.171/60cdc8e27a6d4451.php, IP corroborated against VirusTotal - StealC / builder_v2 attribution via the
.rdatadebug path
Assumed / not independently verified:
- The exfiltration JSON schema shown above is reconstructed from decrypted string names (API calls, file paths), not captured from an actual network request — no dynamic execution was performed
Next steps: detonate in an isolated sandbox to confirm the exfiltration format against real traffic, and submit both the loader and payload hashes to VirusTotal for detection coverage.
Analysis performed: December 19-22, 2025
Methodology: Static analysis only
No malware was executed during this analysis