WMI检测思路与实现

0x01 前言

         在《WMI攻守之道》中,我们通过分析WMI产生的流量数据了解到WMI通过DCE/RPC协议进行通信,这个协议主要由DCOM远程激活机制和NTLM身份认证。DCOM远程激活是WMI远程连接的必要步骤,所以可以通过检测DCOM远程激活,进而检测WMI连接。
mark

         而在windows系统中存在多个DCOM对象,所以需要通过CLSID判断是否是WMI的CLSID。继而检测是否是WMI远程连接。而WMI的CLSID值为8BC3F05E-D86B-11D0-A075-00C04FB68820。本文行文仓促,如有错误,请各位积极指正。
mark

0x02 WMI检测思路

         在CVE-2015-2370之DCOM DCE/RPC协议原理详细分析一文中,详细描述了DCOM远程激活机制的细节,远程激活一共有两种方式:一种是采用CoCreateInstanceEx方式指定远程服务器和激活身份等参数调用rpscss的IRemoteSCMActivator接口的RemoteCreateInstance方法激活,另外一种是客户端marshal服务端unmarshal方式。经过分析,WMI的远程激活采用的是第一种方式,即通过RemoteCreateInstance方法激活。

         IRemoteSCMActivator::RemoteCreateInstance方法的原型如下,参数pActProperties指向了MInterfacePointer结构,其包含了一个OBJREF_CUSTOM对象。

1
2
3
4
5
6
7
8
HRESULT RemoteCreateInstance(
[in] handle_t rpc,
[in] ORPCTHIS* orpcthis,
[out] ORPCTHAT* orpcthat,
[in, unique] MInterfacePointer* pUnkOuter,
[in, unique] MInterfacePointer* pActProperties,
[out] MInterfacePointer** ppActProperties
);

         MInterfacePointer结构如下,包含了ulCntData,和abData两个字段,ulCntData表示的是cbData字段的大小。abData包含OBJREF 的结构。

1
2
3
4
typedef struct tagMInterfacePointer {
unsigned long ulCntData;
[size_is(ulCntData)] byte abData[];
} MInterfacePointer;

mark

         根据微软文档的描述,pActProperties包含了一个OBJREF结构,OBJREF是 DCOM 远程协议对象引用的封送格式。OBJREF有四种不同的格式,其中由flags属性指定不同的格式。当flag为4,说明其包含OBJREF_CUSTOM结构。具体的结构说明可以参见微软文档OBJREF结构
mark

         OBJREF_CUSTOM结构的CLSID值为{00000338-0000-0000-c000-000000000046},表示的是CLSID_ActivationPropertiesIn。其他的GUID仍然可以在微软文档中查看Standards Assignments

         包含激活属性的BLOB结构包含多个激活属性,其中实例化信息数据,请求信息数据,以及位置信息数据属性是必选的,而安全信息数据,激活上下文信息数据,实例信息数据,特殊属性数据都是可选的。
mark

         其中标识WMI的CLSID存储在InstantiationInfoData,而存储连接的地址存储在SecurityInfoData中。
mark
mark

         RemoteCreateInstance方法位于rpcss.dll中,该函数并未导出,DCOM激活服务由系统服务RPCSS服务提供。一般的,windows系统服务都由svchost进行托管。利用tasklist /SVC查看。
mark
mark

         RemoteCreateInstance方法在rpcss.dll中,rpcss.dll中存在两个RemoteCreateInstance方法,其中_RemoteCreateInstance才是IRemoteSCMActivator接口的RemoteCreateInstance方法。这里我们使用双机调试的方法查看pActProperties。上图可以看到Pid为828的进程是rpcss服务的托管进程。使用!process 0 0查看所有的进程,然后使用.process \i eprocess切换到指定进程。并在rpcss下_RemoteCreateInstance断点。具体如下.

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
0: kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 869cf690 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8a401a70 HandleCount: 511.
Image: System
PROCESS 88c16d40 SessionId: 0 Cid: 0238 Peb: 7ffd8000 ParentCid: 01b8
DirBase: 3e81a0e0 ObjectTable: 9210cea8 HandleCount: 560.
Image: lsass.exe
PROCESS 88c07030 SessionId: 0 Cid: 0240 Peb: 7ffd9000 ParentCid: 01b8
DirBase: 3e81a100 ObjectTable: 921e4008 HandleCount: 146.
Image: lsm.exe
PROCESS 8740f728 SessionId: 0 Cid: 02a4 Peb: 7ffdf000 ParentCid: 0230
DirBase: 3e81a120 ObjectTable: 97838640 HandleCount: 354.
Image: svchost.exe
PROCESS 88c9b7e8 SessionId: 0 Cid: 02e0 Peb: 7ffdf000 ParentCid: 0230
DirBase: 3e81a140 ObjectTable: 97901bc0 HandleCount: 315.
Image: HipsDaemon.exe
PROCESS 88cee9c0 SessionId: 0 Cid: 02f8 Peb: 7ffdf000 ParentCid: 0230
DirBase: 3e81a160 ObjectTable: 978f33a0 HandleCount: 55.
Image: vmacthlp.exe
PROCESS 88d1b030 SessionId: 0 Cid: 033c Peb: 7ffdf000 ParentCid: 0230
DirBase: 3e81a180 ObjectTable: 978d42e8 HandleCount: 265.
Image: svchost.exe
PROCESS 88d32c08 SessionId: 0 Cid: 0384 Peb: 7ffda000 ParentCid: 0230
DirBase: 3e81a1a0 ObjectTable: 9793c728 HandleCount: 446.
Image: svchost.exe
PROCESS 88d78898 SessionId: 0 Cid: 03dc Peb: 7ffdf000 ParentCid: 0230
DirBase: 3e81a1e0 ObjectTable: 8c074820 HandleCount: 461.
Image: svchost.exe
0: kd> .process /i 88d1b030
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
0: kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
840b27b8 cc int 3
0: kd> bp rpcss!_RemoteCreateInstance

         如图,可以看到pActProperties+0x174存储的是CLSID,pActProperties+0x284存储的是ip地址。
mark
mark

         所以检测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结构如下:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
1: kd> dt _EPROCESS
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER
+0x0a8 ExitTime : _LARGE_INTEGER
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : Ptr32 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY
+0x0c0 ProcessQuotaUsage : [2] Uint4B
+0x0c8 ProcessQuotaPeak : [2] Uint4B
+0x0d0 CommitCharge : Uint4B
+0x0d4 QuotaBlock : Ptr32 _EPROCESS_QUOTA_BLOCK
+0x0d8 CpuQuotaBlock : Ptr32 _PS_CPU_QUOTA_BLOCK
+0x0dc PeakVirtualSize : Uint4B
+0x0e0 VirtualSize : Uint4B
+0x0e4 SessionProcessLinks : _LIST_ENTRY
+0x0ec DebugPort : Ptr32 Void
+0x0f0 ExceptionPortData : Ptr32 Void
+0x0f0 ExceptionPortValue : Uint4B
+0x0f0 ExceptionPortState : Pos 0, 3 Bits
+0x0f4 ObjectTable : Ptr32 _HANDLE_TABLE
+0x0f8 Token : _EX_FAST_REF
+0x0fc WorkingSetPage : Uint4B
+0x100 AddressCreationLock : _EX_PUSH_LOCK
+0x104 RotateInProgress : Ptr32 _ETHREAD
+0x108 ForkInProgress : Ptr32 _ETHREAD
+0x10c HardwareTrigger : Uint4B
+0x110 PhysicalVadRoot : Ptr32 _MM_AVL_TABLE
+0x114 CloneRoot : Ptr32 Void
+0x118 NumberOfPrivatePages : Uint4B
+0x11c NumberOfLockedPages : Uint4B
+0x120 Win32Process : Ptr32 Void
+0x124 Job : Ptr32 _EJOB
+0x128 SectionObject : Ptr32 Void
+0x12c SectionBaseAddress : Ptr32 Void
+0x130 Cookie : Uint4B
+0x134 Spare8 : Uint4B
+0x138 WorkingSetWatch : Ptr32 _PAGEFAULT_HISTORY
+0x13c Win32WindowStation : Ptr32 Void
+0x140 InheritedFromUniqueProcessId : Ptr32 Void
+0x144 LdtInformation : Ptr32 Void
+0x148 VdmObjects : Ptr32 Void
+0x14c ConsoleHostProcess : Uint4B
+0x150 DeviceMap : Ptr32 Void
+0x154 EtwDataSource : Ptr32 Void
+0x158 FreeTebHint : Ptr32 Void
+0x160 PageDirectoryPte : _HARDWARE_PTE_X86
+0x160 Filler : Uint8B
+0x168 Session : Ptr32 Void
+0x16c ImageFileName : [15] UChar
+0x17b PriorityClass : UChar
+0x17c JobLinks : _LIST_ENTRY
+0x184 LockedPagesList : Ptr32 Void
+0x188 ThreadListHead : _LIST_ENTRY
+0x190 SecurityPort : Ptr32 Void
+0x194 PaeTop : Ptr32 Void
+0x198 ActiveThreads : Uint4B
+0x19c ImagePathHash : Uint4B
+0x1a0 DefaultHardErrorProcessing : Uint4B
+0x1a4 LastThreadExitStatus : Int4B
+0x1a8 Peb : Ptr32 _PEB
+0x1ac PrefetchTrace : _EX_FAST_REF
+0x1b0 ReadOperationCount : _LARGE_INTEGER
+0x1b8 WriteOperationCount : _LARGE_INTEGER
+0x1c0 OtherOperationCount : _LARGE_INTEGER
+0x1c8 ReadTransferCount : _LARGE_INTEGER
+0x1d0 WriteTransferCount : _LARGE_INTEGER
+0x1d8 OtherTransferCount : _LARGE_INTEGER
+0x1e0 CommitChargeLimit : Uint4B
+0x1e4 CommitChargePeak : Uint4B
+0x1e8 AweInfo : Ptr32 Void
+0x1ec SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x1f0 Vm : _MMSUPPORT
+0x25c MmProcessLinks : _LIST_ENTRY
+0x264 HighestUserAddress : Ptr32 Void
+0x268 ModifiedPageCount : Uint4B
+0x26c Flags2 : Uint4B
+0x26c JobNotReallyActive : Pos 0, 1 Bit
+0x26c AccountingFolded : Pos 1, 1 Bit
+0x26c NewProcessReported : Pos 2, 1 Bit
+0x26c ExitProcessReported : Pos 3, 1 Bit
+0x26c ReportCommitChanges : Pos 4, 1 Bit
+0x26c LastReportMemory : Pos 5, 1 Bit
+0x26c ReportPhysicalPageChanges : Pos 6, 1 Bit
+0x26c HandleTableRundown : Pos 7, 1 Bit
+0x26c NeedsHandleRundown : Pos 8, 1 Bit
+0x26c RefTraceEnabled : Pos 9, 1 Bit
+0x26c NumaAware : Pos 10, 1 Bit
+0x26c ProtectedProcess : Pos 11, 1 Bit
+0x26c DefaultPagePriority : Pos 12, 3 Bits
+0x26c PrimaryTokenFrozen : Pos 15, 1 Bit
+0x26c ProcessVerifierTarget : Pos 16, 1 Bit
+0x26c StackRandomizationDisabled : Pos 17, 1 Bit
+0x26c AffinityPermanent : Pos 18, 1 Bit
+0x26c AffinityUpdateEnable : Pos 19, 1 Bit
+0x26c PropagateNode : Pos 20, 1 Bit
+0x26c ExplicitAffinity : Pos 21, 1 Bit
+0x26c Spare1 : Pos 22, 1 Bit
+0x26c ForceRelocateImages : Pos 23, 1 Bit
+0x26c DisallowStrippedImages : Pos 24, 1 Bit
+0x26c LowVaAccessible : Pos 25, 1 Bit
+0x270 Flags : Uint4B
+0x270 CreateReported : Pos 0, 1 Bit
+0x270 NoDebugInherit : Pos 1, 1 Bit
+0x270 ProcessExiting : Pos 2, 1 Bit
+0x270 ProcessDelete : Pos 3, 1 Bit
+0x270 Wow64SplitPages : Pos 4, 1 Bit
+0x270 VmDeleted : Pos 5, 1 Bit
+0x270 OutswapEnabled : Pos 6, 1 Bit
+0x270 Outswapped : Pos 7, 1 Bit
+0x270 ForkFailed : Pos 8, 1 Bit
+0x270 Wow64VaSpace4Gb : Pos 9, 1 Bit
+0x270 AddressSpaceInitialized : Pos 10, 2 Bits
+0x270 SetTimerResolution : Pos 12, 1 Bit
+0x270 BreakOnTermination : Pos 13, 1 Bit
+0x270 DeprioritizeViews : Pos 14, 1 Bit
+0x270 WriteWatch : Pos 15, 1 Bit
+0x270 ProcessInSession : Pos 16, 1 Bit
+0x270 OverrideAddressSpace : Pos 17, 1 Bit
+0x270 HasAddressSpace : Pos 18, 1 Bit
+0x270 LaunchPrefetched : Pos 19, 1 Bit
+0x270 InjectInpageErrors : Pos 20, 1 Bit
+0x270 VmTopDown : Pos 21, 1 Bit
+0x270 ImageNotifyDone : Pos 22, 1 Bit
+0x270 PdeUpdateNeeded : Pos 23, 1 Bit
+0x270 VdmAllowed : Pos 24, 1 Bit
+0x270 CrossSessionCreate : Pos 25, 1 Bit
+0x270 ProcessInserted : Pos 26, 1 Bit
+0x270 DefaultIoPriority : Pos 27, 3 Bits
+0x270 ProcessSelfDelete : Pos 30, 1 Bit
+0x270 SetTimerResolutionLink : Pos 31, 1 Bit
+0x274 ExitStatus : Int4B
+0x278 VadRoot : _MM_AVL_TABLE
+0x298 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x2a8 TimerResolutionLink : _LIST_ENTRY
+0x2b0 RequestedTimerResolution : Uint4B
+0x2b4 ActiveThreadsHighWatermark : Uint4B
+0x2b8 SmallestTimerResolution : Uint4B
+0x2bc TimerResolutionStackRecord : Ptr32 _PO_DIAG_STACK_RECORD

         其中重要的是位于+0xB8处的ActiveProcessLinks,这是一个_LIST_ENTRY结构,其指向的是下一个进程的_LIST_ENTRY结构,然后减去0xB8的偏移,即可获得下一个进程的EPROCESS。通过这个双向列表,可以遍历整个进程列表,然后是位于+0xB4的UniqueProcessId,这表示的是Pid。

         首先使用!process 获取当前进程的EPROCESS。当前的EPROCESS为0x869CF690。

1
2
3
4
5
6
1: kd> !process
PROCESS 869cf690 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8a401a70 HandleCount: 497.
Image: System
VadRoot 87c10c48 Vads 11 Clone 0 Private 3. Modified 8125. Locked 64.
DeviceMap 8a408840

         然后使用dt _EPROCESS 869cf690获取ActiveProcessLinks,UniqueProcessId, ImageFileName等进程信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1: kd> dt _EPROCESS 869cf690
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d7d3a0`340bdebf
+0x0a8 ExitTime : _LARGE_INTEGER 0x0
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : 0x00000004 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x87e14b28 - 0x84183ba8 ]
+0x0c0 ProcessQuotaUsage : [2] 0
+0x168 Session : (null)
....
+0x16c ImageFileName : [15] "System"
+0x17b PriorityClass : 0x2 ''
+0x17c JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x184 LockedPagesList : (null)

         通过ActiveProcessLinks遍历下一个进程的EPROCESS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1: kd> dt _EPROCESS 0x87e14b28-0xB8
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x098 ProcessLock : _EX_PUSH_LOCK
+0x0a0 CreateTime : _LARGE_INTEGER 0x01d7d3a0`345a6c28
+0x0a8 ExitTime : _LARGE_INTEGER 0x0
+0x0b0 RundownProtect : _EX_RUNDOWN_REF
+0x0b4 UniqueProcessId : 0x00000120 Void
+0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x8847b780 - 0x869cf748 ]
......
+0x160 Filler : 0
+0x168 Session : (null)
+0x16c ImageFileName : [15] "smss.exe"
+0x17b PriorityClass : 0x2 ''

         获取指定进程的EPROCESS,则可以如此实现。

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
// 原理:遍历EPROCESS列表
PEPROCESS GetSpecialProcess(ULONG dwPid)
{
//获取当前进程的EPROCESS
PEPROCESS pResultEprocess = NULL;
PEPROCESS pCurrentProcess = NULL;
pCurrentProcess = PsGetCurrentProcess();
if (NULL == pCurrentProcess)
{
DbgPrint("[!] PsGetCurrentProcess");
return NULL;
}
PLIST_ENTRY pCurList = (PLIST_ENTRY)((ULONG)pCurrentProcess + LIST_OFFSET);
PLIST_ENTRY pList = pCurList;
PEPROCESS pEprocess = NULL;
while (pList->Flink != pCurList)
{
pEprocess = (PEPROCESS)((ULONG)pList - LIST_OFFSET);
if (pEprocess == NULL)
{
DbgPrint("pEprocess Error");
continue;
}
ULONG ProcessId = -1;
ProcessId = *(ULONG*)((ULONG)pEprocess + PID_OFFSET);
if (ProcessId == -1)
{
DbgPrint("ProcessId Error");
continue;
}
if (ProcessId == dwPid)
{
pResultEprocess = pEprocess;
break;
}
pList = pList->Flink;
}
return pResultEprocess;
}

         接着通过EPROCESS,就可以定位rpcss.dll模块。EPROCESS结构偏移为0x1A8保存着进程PEB,PEB又称进程环境块,通过PEB,获取PEB_LDR_DATA,继而通过PEB_LDR_DATA结构,可以遍历模块列表。

         通过EPROCESS获取PEB,继而可以获取_PEB_LDR_DATA。然后通过InLoadOrderModuleList遍历模块。关于通过PEB遍历模块列表,大家可以在各个论坛上了解这方面的知识点。

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
1: kd> dt _PEB 7ffd4000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 BitField : 0x8 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsLegacyProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 SpareBits : 0y000
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00d90000 Void
+0x00c Ldr : 0x77437880 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x003e1128 _RTL_USER_PROCESS_PARAMETERS
....
1: kd> dt _PEB_LDR_DATA 0x77437880
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x3e1a00 - 0x443248 ]
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x3e1a08 - 0x443250 ]
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x3e1a90 - 0x443258 ]
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
....
1: kd> dt _LDR_DATA_TABLE_ENTRY 0x3e1a00
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x3e1a80 - 0x7743788c ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x3e1a88 - 0x77437894 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x018 DllBase : 0x00d90000 Void
+0x01c EntryPoint : 0x00d96170 Void
+0x020 SizeOfImage : 0x11000
+0x024 FullDllName : _UNICODE_STRING "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe"
+0x02c BaseDllName : _UNICODE_STRING "vmtoolsd.exe"
....
1: kd> dt _LDR_DATA_TABLE_ENTRY 0x3e1a80
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x3e1d78 - 0x3e1a00 ]
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x3e1d80 - 0x3e1a08 ]
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x3e1e70 - 0x7743789c ]
+0x018 DllBase : 0x77360000 Void
+0x01c EntryPoint : (null)
+0x020 SizeOfImage : 0x13c000
+0x024 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
+0x02c BaseDllName : _UNICODE_STRING "ntdll.dll"

         接着就是定位_RemoteCreateInsance函数,_RemoteCreateInsance函数并不是导出函数,所以只能通过特征码爆破搜索_RemoteCreateInsance函数地址。我看过相关暴力搜索函数的方法,很多都是通过搜索函数调用的方式进行定位,但是我并没有发现_RemoteCreateInsance函数存在直接调用。于是通过IDA看了_RemoteCreateInsance函数的反汇编代码,可以看到两个硬编码的返回值。经过我的测试,只有_RemoteCreateInsance函数才能同时搜索到这两个硬编码。于是只需要搜索这两个编码便可以定位_RemoteCreateInsance函数。
mark

         获取了_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写入目标进程,至于原因,仍然放在番外一节中讲述。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
HRESULT InstallHook(PVOID pFunctionAddr_RemoteCreateInstance, PEPROCESS pEprocessOfRpcss)
{
//初始化HookData
HookData.TargetFunctionAddr = pFunctionAddr_RemoteCreateInstance;
HookData.JmpBackAddr = (ULONG)pFunctionAddr_RemoteCreateInstance + 5;
HookData.NewFunctionByte = ExAllocatePool(NonPagedPool, 5);
HookData.OldFunctionByte = ExAllocatePool(NonPagedPool, 5);
RtlZeroMemory(HookData.NewFunctionByte, 5);
RtlZeroMemory(HookData.OldFunctionByte, 5);
//在进城中开辟空间存储shellcode
HANDLE hProcess = NULL;
NTSTATUS ntStatus = 0;
ntStatus = ObOpenObjectByPointer((PVOID)pEprocessOfRpcss,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
NULL,
GENERIC_ALL,
*PsProcessType,
KernelMode,
&hProcess
);
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("[!]ObOpenObjectByPointer hProcess Failed", ntStatus);
return -1;
}
PVOID fnDetourRemoteCreateInstanceShellcode_Addr = NULL;
ULONG uSizeOffnDetourRemoteCreateInstanceShellcode = 0x200;
ntStatus = ZwAllocateVirtualMemory(hProcess, &fnDetourRemoteCreateInstanceShellcode_Addr, 0, &uSizeOffnDetourRemoteCreateInstanceShellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!NT_SUCCESS(ntStatus))
{
DbgPrint("[!]Virtual memory for fnDetourRemoteCreateInstance Failed", ntStatus);
return -1;
}
HookData.pfnDetourFun = fnDetourRemoteCreateInstanceShellcode_Addr;
//检查是否被Hook
UCHAR OldFunctionByte[5] = { 0x8B, 0xFF, 0x55, 0x8B, 0xEC };
if (RtlCompareMemory((PVOID)HookData.TargetFunctionAddr, (PVOID)OldFunctionByte, 5) != 5)
{
DbgPrint("[!]detected target function hooked");
return -1;
}
// 保存Target 函数 Bytes
RtlCopyMemory(HookData.OldFunctionByte, pFunctionAddr_RemoteCreateInstance, 5);
//将DetourRemoteCreateInstance函数Shellcode写入内存
RtlZeroMemory(fnDetourRemoteCreateInstanceShellcode_Addr, uSizeOffnDetourRemoteCreateInstanceShellcode);
RtlCopyMemory(fnDetourRemoteCreateInstanceShellcode_Addr, _DetourRemoteCreateInstance, uSizeOffnDetourRemoteCreateInstanceShellcode);
//IRQL
WPOFF();
KIRQL oldIrql;
oldIrql = KeRaiseIrqlToDpcLevel();
//修改入口点数据
HookData.NewFunctionByte[0] = 0xE9;
*(ULONG*)(HookData.NewFunctionByte + 1) = (ULONG)fnDetourRemoteCreateInstanceShellcode_Addr - HookData.TargetFunctionAddr - 5;
RtlCopyMemory(HookData.TargetFunctionAddr, HookData.NewFunctionByte, 5);
KeLowerIrql(oldIrql);
WPON();
return STATUS_SUCCESS;
}

         如何构造DetourFun函数,DetourFun函数主要有两个目的,第一个就是解析_RemoteCreateInstance函数的pActProperties参数中的IP和CLSID,另外一个是和驱动程序进行通信,反馈结果。本文采用常见的驱动通信的方式,首先仍然通过PEB获取Kernel32的模块地址,然后通过导出表获取所需要的函数地址,比如CreateFile,WriteFile,CloseHandle等和驱动通信相关的函数地址,然后解析pActProperties参数。

         最最重要的是如何构造TrampolineFun所需要的功能。但是在描述TrampolineFun功能之前,需要了解一下调用函数的方式,在调用x86的stdcall函数时,会先将参数从右到左依次传入堆栈,然后将返回地址压入堆栈,然后构造TrampolineFun函数,这样就可以保证堆栈的平衡。

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
mov eax, [ebp - 24h]
add eax, 5
mov[ebp - 24h], eax //RemoteCreateInstance函数地址
//压入参数
mov eax, [ebp + 1Ch]
push eax
mov ecx, [ebp + 18h]
push ecx
mov edx, [ebp + 14h]
push edx
mov eax, [ebp + 10h]
push eax
mov ecx, [ebp + 0Ch]
push ecx
mov edx, [ebp + 8]
push edx
mov edx, [ebp - 24h]
//压入返回地址
call NEXT
NEXT :
pop eax
add eax,12
push eax
//压入ebp
mov edi, edi
push ebp
mov ebp, esp
jmp edx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
retn

         TrampolineFun函数功能其实就是两部分,一是填充目标函数前5个字节(此处的InlineHook是这样的,亦可填充其他字节)。二是跳转到目标函数后面的地址,保证Hook的持久化。

1
2
3
4
mov edi, edi
push ebp
mov ebp, esp
jmp edx

         最终的结果是这样的。具体源码可以在https://github.com/findream/SecStudy/blob/main/ATT-CK/Windows%20Management%20Instrumentation/WMI_Monitor/MyDriver1/Hook.c可以看到。也可以观看我在B站上上传的WMI远程访问检测的视频。
mark

0x04 番外

         了解操作系统的都知道,普通的应用程序都运行在R3,驱动程序都运行在R0。最开始,将DetourFun存储在驱动程序中,当Hook R3层RemoteCreateInstance函数后,此时EIP位于RemoteCreateInstance函数,当Jmp后,不可能跳转到位于驱动程序中的DetourFun函数。所以首先将shellcode写入R3内存。

0x05 参考文献