- 本文转载于跳跳糖安全社区,原文链接为https://tttang.com/archive/1639/
0x01 前言
在《WMI攻守之道》中,我们通过分析WMI产生的流量数据了解到WMI通过DCE/RPC协议进行通信,这个协议主要由DCOM远程激活机制和NTLM身份认证。DCOM远程激活是WMI远程连接的必要步骤,所以可以通过检测DCOM远程激活,进而检测WMI连接。
而在windows系统中存在多个DCOM对象,所以需要通过CLSID判断是否是WMI的CLSID。继而检测是否是WMI远程连接。而WMI的CLSID值为8BC3F05E-D86B-11D0-A075-00C04FB68820
。本文行文仓促,如有错误,请各位积极指正。
0x02 WMI检测思路
在CVE-2015-2370之DCOM DCE/RPC协议原理详细分析一文中,详细描述了DCOM远程激活机制的细节,远程激活一共有两种方式:一种是采用CoCreateInstanceEx方式指定远程服务器和激活身份等参数调用rpscss的IRemoteSCMActivator接口的RemoteCreateInstance方法激活,另外一种是客户端marshal服务端unmarshal方式。经过分析,WMI的远程激活采用的是第一种方式,即通过RemoteCreateInstance方法激活。
IRemoteSCMActivator::RemoteCreateInstance方法的原型如下,参数pActProperties指向了MInterfacePointer结构,其包含了一个OBJREF_CUSTOM对象。
MInterfacePointer结构如下,包含了ulCntData,和abData两个字段,ulCntData表示的是cbData字段的大小。abData包含OBJREF 的结构。
根据微软文档的描述,pActProperties包含了一个OBJREF结构,OBJREF是 DCOM 远程协议对象引用的封送格式。OBJREF有四种不同的格式,其中由flags属性指定不同的格式。当flag为4,说明其包含OBJREF_CUSTOM结构。具体的结构说明可以参见微软文档OBJREF结构
OBJREF_CUSTOM结构的CLSID值为{00000338-0000-0000-c000-000000000046}
,表示的是CLSID_ActivationPropertiesIn。其他的GUID仍然可以在微软文档中查看Standards Assignments
包含激活属性的BLOB结构包含多个激活属性,其中实例化信息数据,请求信息数据,以及位置信息数据属性是必选的,而安全信息数据,激活上下文信息数据,实例信息数据,特殊属性数据都是可选的。
其中标识WMI的CLSID存储在InstantiationInfoData,而存储连接的地址存储在SecurityInfoData中。
RemoteCreateInstance方法位于rpcss.dll中,该函数并未导出,DCOM激活服务由系统服务RPCSS服务提供。一般的,windows系统服务都由svchost进行托管。利用tasklist /SVC
查看。
RemoteCreateInstance方法在rpcss.dll中,rpcss.dll中存在两个RemoteCreateInstance方法,其中_RemoteCreateInstance才是IRemoteSCMActivator接口的RemoteCreateInstance方法。这里我们使用双机调试的方法查看pActProperties。上图可以看到Pid为828的进程是rpcss服务的托管进程。使用!process 0 0
查看所有的进程,然后使用.process \i eprocess
切换到指定进程。并在rpcss下_RemoteCreateInstance
断点。具体如下.
如图,可以看到pActProperties+0x174存储的是CLSID,pActProperties+0x284存储的是ip地址。
所以检测WMI的思路,可以如此实现,首先根据服务名获取Pid,然后Hook该进程的Rpcss.dll的_RemoteCreateInstance
函数,通过判断参数pActProperties偏移为0x174处CLSID是否是WMI的CLSID,获取pActProperties+0x284的IP地址。即可检测和阻止WMI。
0x03 WMI检测实现
当然,基于流量检测WMI是一个不错的选择,此处为了验证相关技术,故没有采用流量检测的方式,而是采用Hook的方式。但是如果要需要运用到正式环境,最好采用流量检测的方式,特别强调,这次描述的检测方法和Code都不要用于正式环境。
通常,Hook R3层的函数,需要将一个dll注入进程,然后Hook该函数。但是通常方法注入系统进程,会因为权限问题无法注入进程。这里我选择通过驱动,定位_RemoteCreateInstance
函数,然后进行Hook。
通常,在内核层Hook应用层的模块,首先需要定位目标的进程。windows内核通常使用EPROCESS 结构体描述进程信息。EPROCESS结构如下:
其中重要的是位于+0xB8处的ActiveProcessLinks
,这是一个_LIST_ENTRY结构,其指向的是下一个进程的_LIST_ENTRY结构,然后减去0xB8的偏移,即可获得下一个进程的EPROCESS。通过这个双向列表,可以遍历整个进程列表,然后是位于+0xB4的UniqueProcessId
,这表示的是Pid。
首先使用!process 获取当前进程的EPROCESS。当前的EPROCESS为0x869CF690。
然后使用dt _EPROCESS 869cf690
获取ActiveProcessLinks,UniqueProcessId, ImageFileName等进程信息。
通过ActiveProcessLinks遍历下一个进程的EPROCESS。
获取指定进程的EPROCESS,则可以如此实现。
接着通过EPROCESS,就可以定位rpcss.dll模块。EPROCESS结构偏移为0x1A8保存着进程PEB,PEB又称进程环境块,通过PEB,获取PEB_LDR_DATA,继而通过PEB_LDR_DATA结构,可以遍历模块列表。
通过EPROCESS获取PEB,继而可以获取_PEB_LDR_DATA。然后通过InLoadOrderModuleList遍历模块。关于通过PEB遍历模块列表,大家可以在各个论坛上了解这方面的知识点。
接着就是定位_RemoteCreateInsance
函数,_RemoteCreateInsance
函数并不是导出函数,所以只能通过特征码爆破搜索_RemoteCreateInsance
函数地址。我看过相关暴力搜索函数的方法,很多都是通过搜索函数调用的方式进行定位,但是我并没有发现_RemoteCreateInsance
函数存在直接调用。于是通过IDA看了_RemoteCreateInsance
函数的反汇编代码,可以看到两个硬编码的返回值。经过我的测试,只有_RemoteCreateInsance
函数才能同时搜索到这两个硬编码。于是只需要搜索这两个编码便可以定位_RemoteCreateInsance
函数。
获取了_RemoteCreateInsance
函数函数地址之后,便可以进行Hook了,此处,本文选择InlineHook,关于InlineHook的具体原理不做赘述,如果有需要了解的可以查看一篇文章带你理解HOOK技术
这里参考我之前写的InlineHook的基本步骤(https://github.com/findream/Windows_Safe_Development/blob/master/Hook/IAT_HOOK/InlineHookMessageBox(%E8%BF%9B%E9%98%B6)/InlineHookMessageBox(%E8%BF%9B%E9%98%B6).cpp)
- 第一步:填充HookData结构体,HookData保存着各种关于Hook的信息
- 第二步:检查是否被Hook
- 第三步:保存函数原始数据
- 第四步:填充TrampolineFun函数
- 第五步:修改原始函数入口点进行Hook
但是本文做些许改动,首先InlineHook应该要构建两个函数,一个是DetourFun,另外一个是TrampolineFun。DetourFun是劫持后的函数,用于替换被劫持的函数,而TrampolineFun为了持久化Hook,以便跳回原始的目标函数。本文首先会删除多余的TrampolineFun函数,具体原因,我会在第四章中描述。
那么如何实现TrampolineFun函数的功能呢,我将TrampolineFun函数功能写在DetourFun函数中。因为TrampolineFun函数本身就是就是构造目标函数的前5个字节,然后跳转到目标函数第六个字节处。这一切本文会放在构造DetourFun去描述。
第二个改动是将DetourFun的shellcode写入目标进程,至于原因,仍然放在番外一节中讲述。
如何构造DetourFun函数,DetourFun函数主要有两个目的,第一个就是解析_RemoteCreateInstance
函数的pActProperties参数中的IP和CLSID,另外一个是和驱动程序进行通信,反馈结果。本文采用常见的驱动通信的方式,首先仍然通过PEB获取Kernel32的模块地址,然后通过导出表获取所需要的函数地址,比如CreateFile,WriteFile,CloseHandle等和驱动通信相关的函数地址,然后解析pActProperties参数。
最最重要的是如何构造TrampolineFun所需要的功能。但是在描述TrampolineFun功能之前,需要了解一下调用函数的方式,在调用x86的stdcall函数时,会先将参数从右到左依次传入堆栈,然后将返回地址压入堆栈,然后构造TrampolineFun函数,这样就可以保证堆栈的平衡。
TrampolineFun函数功能其实就是两部分,一是填充目标函数前5个字节(此处的InlineHook是这样的,亦可填充其他字节)。二是跳转到目标函数后面的地址,保证Hook的持久化。
最终的结果是这样的。具体源码可以在https://github.com/findream/SecStudy/blob/main/ATT-CK/Windows%20Management%20Instrumentation/WMI_Monitor/MyDriver1/Hook.c可以看到。也可以观看我在B站上上传的WMI远程访问检测的视频。
0x04 番外
了解操作系统的都知道,普通的应用程序都运行在R3,驱动程序都运行在R0。最开始,将DetourFun存储在驱动程序中,当Hook R3层RemoteCreateInstance
函数后,此时EIP位于RemoteCreateInstance
函数,当Jmp后,不可能跳转到位于驱动程序中的DetourFun函数。所以首先将shellcode写入R3内存。