WMI检测思路与实现

admin 2022年7月6日19:49:51评论237 views字数 17048阅读56分49秒阅读模式

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对象。

 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;

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中。
WMI检测思路与实现
mark

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

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检测思路与实现
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: 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函数。
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写入目标进程,至于原因,仍然放在番外一节中讲述。

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远程访问检测的视频。
mark

0x04 番外

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

0x05 参考文献

FROM:tttang . com

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月6日19:49:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   WMI检测思路与实现https://cn-sec.com/archives/1161274.html

发表评论

匿名网友 填写信息