TryHackMe After Hours Walkthrough: Uncovering Hidden WMI Persistence

Room: After Hours
Platform: TryHackMe – Hacker Holidays 2026
Category: Windows Forensics / WMI Persistence
Focus: WMI Repository Analysis, Persistence, PowerShell, Base64, .NET Payload Analysis

Key Commands

After Hours was a Windows forensic challenge built around a persistence mechanism that deliberately avoids the places defenders usually check first.

The Concierge Briefing gives the first major clue:

Nothing obvious shows up in Startup, Scheduled Tasks, or the registry Run keys.

Instead of relying on traditional persistence locations, the attacker buried their mechanism inside Windows Management Instrumentation (WMI).

The supplied evidence consisted of raw WMI repository artifacts:

INDEX.BTR
MAPPING1.MAP
MAPPING2.MAP
MAPPING3.MAP
OBJECTS.DATA

The objective was to parse these artifacts, identify a suspicious custom configuration, locate the malicious class, extract its payload, and decode it to recover the flag.

Understanding the Evidence

The most interesting artifact was:

OBJECTS.DATA

OBJECTS.DATA is part of the Windows WMI repository. This immediately made the briefing’s reference to persistence hiding somewhere “quieter” much more meaningful.

Rather than spending time looking for ordinary registry persistence, the investigation needed to focus on WMI objects and permanent event subscriptions.

Searching the Raw WMI Data

A good starting point is simply examining the strings contained within the repository:

strings -el OBJECTS.DATA | less

You can also search for WMI persistence-related terminology:

strings -el OBJECTS.DATA | grep -iE "EventFilter|EventConsumer|CommandLine|PowerShell"

The goal isn’t necessarily to immediately find the flag. We’re trying to reconstruct what the attacker configured.

Several interesting objects eventually emerge.

One is an:

__EventFilter

with the name:

EngineTelemetryFilter

Its WMI query monitors the system clock:

SELECT * FROM __InstanceModificationEvent WITHIN 60
WHERE TargetInstance ISA 'Win32_LocalTime'
AND TargetInstance.Minute = 30

That means the event becomes interesting whenever the Windows clock reaches a particular minute.

This explains the room’s references to activity occurring after hours.

Finding the WMI Consumer

A permanent WMI subscription normally needs something to happen when its event condition is met.

Further investigation reveals a:

CommandLineEventConsumer

named:

EngineTelemetryConsumer

This is much more suspicious.

A CommandLineEventConsumer can execute a command whenever its associated WMI event filter fires.

The consumer launches PowerShell invisibly and supplies it with encoded data.

At this point, we have the basic persistence chain:

WMI Event
↓
__EventFilter
↓
EngineTelemetryFilter
↓
CommandLineEventConsumer
↓
EngineTelemetryConsumer
↓
PowerShell

This is why traditional Startup and Run-key analysis wouldn’t expose the persistence mechanism.

Decoding the PowerShell

The command contains Base64-encoded PowerShell.

A useful way to decode PowerShell’s -EncodedCommand content on Linux is:

echo '' | base64 -d

However, PowerShell encoded commands commonly use UTF-16LE, so a cleaner approach is:

echo '' | base64 -d | iconv -f UTF-16LE -t UTF-8

After decoding the script, one line becomes particularly important:

$file = ([WmiClass]'ROOT\cimv2:Win32_HardwareTelemetry').Properties['ConfigData'].Value;

Now the real trick behind the challenge becomes apparent.

The PowerShell script isn’t downloading its second-stage payload.

It retrieves it from another WMI object.

Discovering the Malicious Custom Class

The suspicious custom WMI class is:

Win32_HardwareTelemetry

At first glance, that sounds convincingly Windows-like.

But the important property is:

ConfigData

The attacker has effectively created a storage location inside WMI:

ROOT\cimv2
└── Win32_HardwareTelemetry
└── ConfigData
       ↓
       Encoded Payload

That’s the hidden configuration data mentioned in the room objective.

This is a great example of why defenders shouldn’t automatically trust objects simply because their names look like legitimate Windows components.

Extracting the Embedded Payload

The decoded PowerShell also tells us exactly how the data is processed.

Conceptually, it performs:

ConfigData
↓
Base64 Decode
↓
DEFLATE Decompression
↓
.NET Assembly
↓
Assembly.Load()
↓
EntryPoint.Invoke()

So ConfigData isn’t merely a configuration string.

It’s an encoded executable.

After extracting the property’s contents, the Base64 layer can be removed:

echo '' | base64 -d > payload.deflate

The resulting data then needs to be DEFLATE-decompressed.

A short Python snippet works well:

import zlib

data = open("payload.deflate", "rb").read()

payload = zlib.decompress(data, -zlib.MAX_WBITS)

open("payload.exe", "wb").write(payload)

Then verify what was recovered:

file payload.exe

The extracted payload turns out to be a small Windows .NET executable.

Analyzing the .NET Payload

Before jumping into a full decompiler, it’s always worth checking strings:

strings payload.exe

Interesting strings inside the executable include references such as:

AfterHours
bytelotusdc
cmd.exe

Eventually, a particularly suspicious command appears.

It uses:

net user

to create a local Windows account.

The command also contains another Base64-looking value.

That gives us one final decoding step.

Final Base64 Layer

Take the encoded value from the command and decode it:

echo '' | base64 -d

The resulting plaintext contains the challenge flag.

I have intentionally omitted both the encoded value and decoded flag here so the room can still be completed without the walkthrough simply handing over the answer.

The Full Attack Chain

Putting everything together reveals a clever persistence mechanism:

Permanent WMI Subscription
↓
EngineTelemetryFilter
↓
CommandLineEventConsumer
↓
Hidden PowerShell
↓
Win32_HardwareTelemetry
↓
ConfigData
↓
Base64
↓
DEFLATE
↓
.NET Executable
↓
cmd.exe
↓
Local Account Creation

Rather than storing a malicious executable somewhere obvious on disk, the attacker used WMI both as a persistence mechanism and as a payload storage mechanism.

Why This Challenge Was Interesting

The strongest lesson from After Hours is that persistence hunting shouldn’t stop at the usual locations.

Startup folders, services, Scheduled Tasks, and registry Run keys are important, but Windows provides many other mechanisms capable of triggering code.

Permanent WMI event subscriptions are particularly interesting because they combine three components:

__EventFilter
__EventConsumer
__FilterToConsumerBinding

The filter defines when something happens.

The consumer defines what happens.

The binding connects the two.

After Hours adds another layer by storing the executable inside a custom WMI class property. That makes the WMI repository not only part of the persistence mechanism but effectively a storage container for the malware itself.

Key Commands


strings -el OBJECTS.DATA | less
strings -el OBJECTS.DATA | grep -iE "EventFilter|EventConsumer|CommandLine|PowerShell"
echo '' | base64 -d | iconv -f UTF-16LE -t UTF-8
file payload.exe
strings payload.exe

The important investigation pattern was:

Find persistence
→ identify execution mechanism
→ decode PowerShell
→ identify custom WMI class
→ extract ConfigData
→ Base64 decode
→ DEFLATE decompress
→ analyze .NET executable
→ decode final value
Lessons Learned

WMI is more than a management interface. Attackers can abuse permanent event subscriptions for persistence and execution.

Legitimate-looking class names deserve scrutiny. Win32_HardwareTelemetry sounds believable enough to blend into a WMI repository.

Follow the decoding instructions left by the malware itself. Once the PowerShell was recovered, its code effectively documented the remaining extraction process: retrieve the property, Base64-decode it, decompress it, and load the resulting assembly.

Don’t immediately reach for the heaviest forensic tool. strings, grep, Base64 utilities, Python, and careful examination of PowerShell were enough to unravel most of the challenge.

Layered encoding isn’t encryption. Base64 and DEFLATE make data less obvious during casual inspection, but once the processing chain is identified, reversing it is straightforward.

Similar Posts