一、概述

在最近的内部渗透测试中,我遇到了一个EDR产品(在本文中将不会提及产品名称)。该产品极大程度的阻碍了我访问lsass内存的过程,并且使我们无法使用自定义的Mimikatz工具来转储明文的凭据。

二、错误的道路

所以现在,假设我是一位恶意软件作者,我其实可以编写特定的驱动程序来对抗检测和阻止。我的第一个想法是Obregistercallback,这是许多反病毒产品常用的方法。由于许多反病毒产品都会非常粗略的重新组装恶意软件Rookit的WinAPI挂钩,因此微软实施了这一回调。但是,在MSDN的最下面,我们能看到一行文字:“该回调从Windows Vista Server Pack 1(SP1)和Windows Server 2008开始可用”。为了提供一些缺少的上下文,我此刻正在Windows Server 2003上进行操作。因此,该系统缺少用于阻止反病毒产品检测的所需功能。

在进行了几个小时的尝试之后,我试图利用csrss.exe,并尝试通过csrss.exe来继承lsass.exe的句柄。最终,成功获得了一个带有PROCESS_ALL_ACCESS到lsass.exe的句柄。我们通过滥用csrss生成子进程,然后继承已存在的lsass句柄,最终实现了这一方法。

然而,当我即将沉浸在喜悦之中时,得到了令人失望的结果。EDR还是阻止了ShellCode注入csrss,同时也阻止了通过RtlCreateUserThread创建线程。由于某种原因,代码未能作为子进程生成,并且没有继承句柄,但我们仍然能以某种方式获得lsass.exe的PROCESS_ALL_ACCESS句柄。

接下来,我们试着打开一个lsass.exe的句柄,代码只有这一行:

HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, lsasspid);

现在,我居然获得了一个完全控制lsass.exe的句柄。EDR没有对此进行模糊测试。在这时,我意识到,我走向了错误的道路,因为EDR从来没有关注我们是否获得句柄访问权限,而是我们接下来要进行的操作会受到严格检查。

三、回到正轨

我们已经获得lsass.exe的完整控制句柄,现在可以继续前进,解决下一个关键的突破口。我们立即尝试使用句柄调用MiniDumpWriteDump(),但结果是失败的。

我们来进一步分析这个警告内容:“Violation: LsassRead”(违规:lsass读取)。实际上,我们并没有做任何读取操作,只想对进程进行转储。然而,要转储远程进程,必须在MiniDumpWriteDump()中调用某种类型的WinAPI,例如ReadProcessMemory(RPM)。我们来看看ReactOS上的MiniDumpWriteDump的源代码。

正如大家所看到的,函数(2) dump_exception_info()以及许多其他函数都依赖于(3) RPM来执行其功能。这些函数由MiniDumpWriteDump (1)引用,这可能就是问题的根源所在。现在,我们有一些经验可以发挥作用。我们必须了解Windows系统内部工作原理,以及如何处理WinAPI的过程。以ReadProcessMemory为例,其工作方式如下。

ReadProcessMemory只是一个包装器,它会进行一系列健全性检查,例如nullptr检查。实际上,这就是RPM所做的一切。但除此之外,RPM还调用函数NtReadVirtualMemory,该函数在执行系统调用指令之前设置寄存器。系统调用指令会通知CPU进入内核模式,然后调用另一个名为NtReadVirtualMemory的函数,它执行ReadProcessMemory应该执行的操作的实际逻辑。

— — — — — -Userland — — — —- — — — | — — — Kernel Land — — — —
 
RPM — > NtReadVirtualMemory --> SYSCALL->NtReadVirtualMemory
 
Kernel32 — — -ntdll — — — — — — — — — - — — — — — ntoskrnl

在有了上述知识后,我们现在必须确定EDR产品如何检测并阻止RPM/NtReadVirtualMemory调用。答案其实非常简单,就是“挂钩”(Hook)。有关挂钩的更多信息,可以参考我之前发表的文章。简而言之,该操作使得我们可以将代码放置在任意函数的中间,并获得对参数和返回变量的访问权限。我非常确定,EDR是通过我提到的一种或多种技术,使用了某种挂钩技术。

但是,读者应该清楚,大多数EDR产品都在使用服务,特别是内核模式下运行的驱动程序。通过访问内核模式,驱动程序可以在RPM的callstack中的任何级别执行挂钩。但是,如果任何驱动程序挂起任何级别的函数都是微不足道的,这会在Windows环境中产生一个巨大的安全漏洞。因此,必须要提出一种解决方案来防止这种性质的改变,这种解决方案就是核心补丁保护(又称为KPP或Patch Guard)。KPP几乎在每个级别都进行内核扫描,如果检测到修改,将会触发BSOD。这包括ntoskrnl部分,其中包含WinAPI内核级别的逻辑。在掌握这些背景知识后,我们可以确信,EDR不会挂钩调用栈中的任何内核级别函数,我们就可以使用用户空间的RPM和NtReadVirtualMemory调用。

四、挂钩

要查看函数在应用程序内存中的位置,与具有%p格式字符串的printf和函数名称作为参数一样简单,如下所示。

但是,与RPM不同,NtReadVirtualMemory不是ntdll中的导出函数,因此我们不能像正常情况下一样引用该函数。我们必须指定函数的签名,并将ntdll.lib链接到项目中,才能执行该操作。

一切就绪,让我们尝试运行。

现在,我们得到了RPM和NtReadVirtualMemory的地址。接下来,我将使用我最喜欢的逆向工具Cheat Engine来读取内存并分析其结构。

ReadProcessMemory:

NtReadVirtualMemory:

对于RPM函数,它看起来一切正常。它会执行一些栈和寄存器的设置,然后在Kernelbase内调用ReadProcessMemory。最终将导致我们进入到ntdll的NtReadVirtualMemory。但是,如果我们查看NtReadVirtualMemory并了解基本的钩子是什么样的,我们就能发现其中的异常之处。函数的前5个字节被修改,其余字节则保持原样。我们可以通过查看周围的其他类似函数来判断这一点。所有其他函数都遵循非常相似的格式:

0x4C, 0x8B, 0xD1, // mov r10, rcx; NtReadVirtualMemory
 
0xB8, 0x3c, 0x00, 0x00, 0x00, // eax, 3ch — aka syscall id
 
0x0F, 0x05, // syscall
 
0xC3 // retn

其中的一个区别就是系统调用ID(它标识了内核域中调用的WinAPI函数)。但是,对于NtReadVirtualMemory,第一条指令实际上是对内存中其他地址的JMP指令。我们来具体看一下。

CyMemDef64.dll:

好吧,所以我们并不是在ntdll的模块中,而是在CyMemDef64.dll的模块中。啊,现在我明白了。

EDR放置了原始NtReadVirtualMemory函数中应该存在的跳转指令,将代码流重定向到他们自己的模块中,然后检查所有类型的恶意活动。如果检查失败,那么Nt*函数将返回错误代码,不会进入内和空间,并执行开始的部分。

五、绕过方法

现在,我们已经掌握了EDR如何检测并阻止我们WinAPI调用的方式,这是非常明显的。但是,我们如何解决这一问题呢?有两种解决方案。

5.1 重新修补补丁

既然我们知道NtReadVirtualMemory函数应该是什么样子,那么我们就可以使用正确的指令轻松覆盖JMP指令。这将阻止我们的调用被CyMemDef64.dll拦截,并会进入到他们无法控制的内核之中。

5.2 Ntdll IAT Hook

我们也可以创建自己的函数,类似于在5.1中所进行的操作,但不是覆盖挂钩函数,而是在其他地方重新创建它。然后,我们进入到Ntdll的导入地址表,交换NtReadVirtualMemory的指针,并将其指向我们新的fixed_NtReadVirtualMemory。这种方法的优点在于,如果EDR检查了它们的挂钩,看起来就像没有经过修改一样。它永远不会被调用,而ntdll IAT指向其他地方。

六、结果

最终,我选择了第一种方法,该方法更为简单。然而,第二种方法也并不复杂,我打算在之后的几天再尝试一下这种方法。

七、总结

这种方式适用于这个特定的EDR,但是逆向类似的EDR产品并创建通用的绕过方法也是非常简单的。该方法也同样适用于64位所有版本的Windows,但我没有在32位上进行过测试。源代码请参见:https://github.com/hoangprod/AndrewSpecial/tree/master

感谢大家阅读本篇文章,如果大家发现存在任何问题,还请指教。

本文翻译自:https://medium.com/@fsx30/bypass-edrs-memory-protection-introduction-to-hooking-2efb21acffd6如若转载,请注明原文地址: http://www.4hou.com/system/15956.html
源链接

Hacking more

...