Analyze a stealthy malware sample that silently propagates across systems. Your task is to uncover its hidden logic, understand how it maintains persistence, and reveal the secret it’s designed to protect.
The sample is a native Windows x64 loader that never carries its logic in plain sight. It initializes COM, instantiates a managed .NET class through a hard-coded CLSID, and drives it with a handful of vtable calls: one to start up, one to decrypt, one to run. The embedded payload and the killswitch domain are both Base64 blobs XORed with a key generated by a simple linear formula, so neither shows up in strings. Before detonating, the loader resolves the decrypted killswitch domain, and its propagation routine enumerates shares on remote servers. The decrypted payload is a PowerShell reverse shell. All of it was recovered statically, without running the sample.
danger.zip → password-protected archive holding the sample. Extract into a fresh directory and never execute itstrings and objdump -d -M intel; parse .rdata constants with PythonFormat note: The two opcode-style answers are a three-byte
FF 50 xxpattern (call [rax+disp8]), written as uppercase hex pairs separated by spaces.
Triage the extracted binary with strings and read the import table. CoInitialize, CoCreateInstance and OleRun all hang off the same DLL, which is the COM runtime.
unzip -P '&PD8LhraU1hx' ../evidence/danger.zip -d x
strings -a -n5 x/Danger/*.exe
Disassemble the binary and find the CoCreateInstance call in main. The CLSID argument is a pointer into .rdata at 0x140005a48. Read the 16 bytes there and unpack them as a mixed-endian GUID (first three fields little-endian, the rest as bytes) to get the string form.
objdump -d -M intel x/Danger/*.exe > disasm.txt
import struct; print(struct.unpack('<IHH8B', raw_16_bytes_at_0x140005a48))
Tip: Resolve the virtual address
0x140005a48to a file offset through the.rdatasection header before slicing the bytes. The answer is the uppercase form of the GUID.
A native binary that loads the CLR and calls CoCreateInstance on a fixed CLSID is talking to a COM-visible managed class through a COM Callable Wrapper. That mechanism is COM Interop. This one was reasoned from the Q1 and Q2 evidence rather than read from an artifact: the managed assembly itself is not in the evidence, so there is no type library or class definition to confirm it.
After CoCreateInstance returns the interface pointer, main calls through its vtable. The first managed call is call [rax+0x68] at 0x140001d23, which encodes as FF 50 68. The managed assembly is not available, so the method order was inferred from the sequence of vtable calls in main, not from a method table.
grep -nE 'call +QWORD PTR \[rax\+0x[0-9a-f]+\]' disasm.txt
The remaining vtable call, call [rax+0x60] at 0x14000197a, sits inside the routine at 0x140001310, which handles the share-scanning and payload-launch logic. It encodes as FF 50 60. Like Q4, this is inferred from call order and surrounding code, since the managed method names are not visible.
grep -nE 'call +QWORD PTR \[rax\+0x60\]' disasm.txt
The key is built in a loop at 0x140001f03 with imul ecx,eax,7 followed by add cl,0x42, so each key byte is (i*7 + 0x42) & 0xff. The constants come out as 7 and 0x42.
grep -nE 'imul +ecx,eax,0x7|add +cl,0x42' disasm.txt
Gotcha: The format mask wants the addition constant in assembler hex notation. Decimal
66and the0x42prefix form both fail; the suffix form42his the one that fits.
The call at 0x1400020f6 is call [rax+0x58] (FF 50 58). It is made with the Base64 key and a Base64 ciphertext starting KXgm... as arguments, and its output feeds straight into the getaddrinfo call from Q7. That data flow marks it as the decrypt call.
grep -nE 'call +QWORD PTR \[rax\+0x58\]' disasm.txt
The domain never appears in strings. It is the second Base64 string in .rdata, KXgmYHMADxsV8uHiuPPB3w==. Base64-decode it and XOR byte by byte with the Q5 key stream to recover the plaintext. The same key decrypts the larger embedded blob, which turns out to be a PowerShell reverse shell to 10.0.0.13:80.
import base64; c=base64.b64decode('KXgmYHMADxsV8uHiuPPB3w=='); print(bytes(b ^ ((i*7+0x42)&0xff) for i,b in enumerate(c)))
getaddrinfo is imported from the Winsock library and called at 0x1400021fc with the string returned by the Q6 decrypt call, which is the Q10 domain.
grep -n 'getaddrinfo' disasm.txt
The import table has NetShareEnum from the Netapi32 family, which returns a SHARE_INFO entry for each share on a server. It backs the propagation routine at 0x140001310.
strings -a -n5 x/Danger/*.exe | grep -i NetShare
| Phase | Action |
|---|---|
| Execution | Loader initializes COM through ole32.dll and instantiates a managed class by CLSID |
| Execution | Native code drives the managed object through call [rax+disp8] vtable calls (COM Interop) |
| Defense Evasion | Payload and killswitch domain stored as Base64, XORed with a linear key stream |
| Command and Control | Decrypted killswitch domain is resolved with getaddrinfo |
| Discovery | NetShareEnum lists the shares on reachable servers |
| Execution | Final vtable call runs the decrypted payload, a PowerShell reverse shell |
| Type | Value |
|---|---|
| Killswitch domain | k1v7-echosim[.]net |
| CLSID | DABCD999-1234-4567-89AB-1234567890FF |
| Reverse shell target | 10.0.0.13:80 |
| Key formula | key[i] = (i*7 + 0x42) & 0xff |
| Technique | ID | Description |
|---|---|---|
| Inter-Process Communication: Component Object Model | T1559.001 | Native loader drives a managed class through COM |
| Deobfuscate/Decode Files or Information | T1140 | Base64 plus XOR decryption of payload and domain |
| Obfuscated Files or Information | T1027 | Killswitch domain and payload absent from plaintext strings |
| Network Share Discovery | T1135 | NetShareEnum against remote servers |
| Command and Scripting Interpreter: PowerShell | T1059.001 | Decrypted payload is a PowerShell reverse shell |
| Dynamic Resolution | T1568 | Domain resolved at runtime with getaddrinfo before proceeding |
Hiding logic in a managed assembly behind COM breaks native-only analysis. The native loader is almost empty; the behavior lives in a .NET class reached by a fixed CLSID. Hunt for unregistered or odd CLSIDs passed to CoCreateInstance from unsigned binaries, and always collect the managed assembly alongside the loader.
Linear XOR keys leave the constants in the code. A key of (i*7 + 0x42) & 0xff is trivial once the imul/add pair is found. Static signatures on the key loop (an imul by a small constant followed by add cl, imm) catch families that reuse the pattern even when the ciphertext changes.
Killswitch lookups are a network detection primitive. The loader resolves a domain that is not in strings before it acts. Alert on newly seen, algorithm-looking domains queried by a process that then calls NetShareEnum, and sinkhole the killswitch domain internally to learn which hosts are executing it.
Share enumeration from a non-admin tool is the propagation signal. NetShareEnum against many servers in a short window, followed by SMB writes, is how this sample spreads. Monitor for it from processes that are not file managers or management agents.
The sample is a thin native shell around a COM-hosted managed payload, with a killswitch and a PowerShell reverse shell kept behind one trivial XOR formula. Once the key loop was found, everything else fell out statically; the lesson is that cheap obfuscation still defeats string-based detection but not a look at the arithmetic.