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对象。
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 的结构。
typedef struct tagMInterfacePointer {
unsigned long ulCntData;
[size_is(ulCntData)] byte abData[];
} MInterfacePointer;
根据微软文档的描述,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
断点。具体如下.
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地址。
所以检测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: 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: 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: 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: 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,则可以如此实现。
// 原理:遍历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: 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
函数。
获取了_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写入目标进程,至于原因,仍然放在番外一节中讲述。
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函数,这样就可以保证堆栈的平衡。
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的持久化。
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远程访问检测的视频。
0x04 番外
了解操作系统的都知道,普通的应用程序都运行在R3,驱动程序都运行在R0。最开始,将DetourFun存储在驱动程序中,当Hook R3层RemoteCreateInstance
函数后,此时EIP位于RemoteCreateInstance
函数,当Jmp后,不可能跳转到位于驱动程序中的DetourFun函数。所以首先将shellcode写入R3内存。