Emotet's loader has been quietly rewritten across the last two months. The shell of the binary still looks the same — small, packed, signed with a throwaway certificate — but the unpacking routine has changed in a way that breaks several public unpackers.
This post walks the new stage end-to-end. We'll triage the sample, defeat the unpacker statically with IDA, sanity-check it dynamically in a sandbox, and finish with a YARA rule that catches the unpacked payload across rotated builders.
TL;DR
- New Emotet loader uses a stack-strings + RC4-with-rotated-key obfuscation layer.
- Anti-analysis is light: a CPU-feature check, a parent-process check, and a 7-second sleep skip.
- Once unpacked, the second stage is the same family we've tracked since November — same C2 framing, new keys.
Sample triage
We start with a routine triage on the dropper. Section names look benign, the import table is sparse, and entropy on .text sits around 7.4 — a strong hint that the real code lives elsewhere.
SHA-256: 5ab1f2c0e9...d41 (truncated). First seen 2026-04-19, submitted from JP.
$ file loader.bin
loader.bin: PE32+ executable (GUI) x86-64, for MS Windows
$ pesec loader.bin
[+] sections: .text .rdata .data .pdata .rsrc
[+] entropy(.text) = 7.41 // likely packed
[+] imports = 14 // suspiciously sparseUnpacking the loader
The unpacker resolves its own imports through a hashed PEB walk and decrypts the second stage with RC4. The wrinkle this round is that the key is rotated by a counter derived from a stack-string, so naive key extraction tools miss it.
from arc4 import ARC4
def unpack(blob, key, rot):
# rotate the key by `rot` bytes before each block
out = bytearray()
for i in range(0, len(blob), 16):
k = key[rot:] + key[:rot]
out += ARC4(k).decrypt(blob[i:i + 16])
rot = (rot + 3) & 0x0F
return bytes(out)Anti-analysis tricks
Three checks fire before unpacking proceeds. None of them are novel, but in combination they break a handful of automated sandboxes that don't emulate cpuid faithfully.
cpuidleaf0x40000000— bails if hypervisor vendor strings match common sandbox values.- Parent process name — bails if parent isn't one of
[explorer.exe, services.exe]. GetTickCountsleep skip — sleeps 7s and aborts if elapsed time is under 5s (sandbox time-jump).
YARA rule for the unpacked stage
Below is a starter rule. It anchors on the rotated-key routine plus two strings that survived the rebuild. We've validated it against 47 unpacked samples from the last 90 days with zero false positives in our corpus.
rule Emotet_Loader_2026 {
meta:
author = "0xfuzz"
date = "2026-04-22"
tlp = "clear"
strings:
$rot = { 8B C? 83 E0 0F 41 8A ?? 32 ?? 88 }
$s1 = "hxv7-" ascii
$s2 = "WinDbgFrameMissing" wide
condition:
uint16(0) == 0x5A4D and all of them
}IOCs
If you're hunting this in your environment, the rotated-key signature is the most stable anchor — strings rotate every two builds. Reach out via the contact page if you've seen drift on the C2 framing; I'm collating samples for a follow-up.