>_
The Fuzz.
← /blog
Malware Analysis

Unpacking Emotet's Latest Loader: A Static + Dynamic Walkthrough

Reverse engineering the new Emotet loader stage. We cover the unpacking routine, anti-analysis tricks, and pull a clean payload for YARA authoring.

$ author 0xFuzz·Apr 22, 2026·14 min read
reversingemotetida-proyara

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.

i
ioc

SHA-256: 5ab1f2c0e9...d41 (truncated). First seen 2026-04-19, submitted from JP.

triage.sh
$ 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 sparse

Unpacking 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.

unpack.py
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.

  1. cpuid leaf 0x40000000 — bails if hypervisor vendor strings match common sandbox values.
  2. Parent process name — bails if parent isn't one of [explorer.exe, services.exe].
  3. GetTickCount sleep 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.

emotet_loader_2026.yar
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

typevaluenote
SHA-2565ab1f2c0e9d8...4f1d41loader
SHA-2569c3a4b21f01e...8a7e02unpacked stage
Domaincdn-pulse[.]liveC2
Domainmetrics-deck[.]onlineC2 fallback
IP185.244.181.14first-stage drop

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.

$ echo "thanks for reading" | tee /dev/null
$ next post