绕过几乎所有EDR的方式

渗透技巧 3年前 (2021) admin
1,658 0 0

1. Windows API 挂钩

EDR用于检测和标记windows上的恶意进程的主要功能之一是和ntdll.dll API挂钩,这意味着什么这意味着 EDR 注入 DLL 将注入opcodes,这将使程序执行流程被重定向到他自己的函数中,例如在 Windows 上读取文件时,您可能会使用 NtReadFile,当CPU 将读取 NTDLL 的内存时,dll 获得 NtReadFile 函数,CPU 会有小惊喜,当进入 ntdll 原始函数时,它会告诉它“跳转”到另一个函数,然后 EDR 将通过检查你的进程试图读取的内容来分析参数并发送到 NtReadFile 函数,如果有效,执行流程将返回到原来的 NtReadFile 函数。

1.1 Windows API Hooking 绕过

首先,我确信有其它更好的bypass,但现在我将教你一种我在使用的技术。

直接系统调用:

直接系统调用基本上是一种使用程序集或通过访问手动加载的 ntdll.dll(手动 DLL 映射)直接调用 Windows 用户模式 API 的方法,在本文中,我不会提如何手动映射 DLL。

我们使用的方法是在我们的二进制文件中编译的程序集,它将充当 Windows API。

Windows 系统调用非常简单,这里是 NtCreateFile 的一个小例子:


绕过几乎所有EDR的方式

第一行:第一行移入 rax 寄存器系统调用号

第二行:将rcx寄存器移动到r10中,由于syscall指令破坏了rcx寄存器,我们使用r10来保存发送到syscall函数中的变量。

第三行:很明显,调用保存在 rax 寄存器中的系统调用号。

第四行:ret,将执行流程返回到调用 syscalls 函数的位置。

现在我们知道如何手动调用系统调用后,我们如何在程序中定义它们?很简单,我们在头文件中声明它们。


绕过几乎所有EDR的方式

上面的示例显示了在调用函数 NtCreateFile 时传递给它的参数,正如您所看到的,我将EXTERN_C符号放在函数定义之前,以便告诉链接器该函数在别处找到。

在编译我们的可执行文件之前,我们必须执行以下步骤,右键单击我们的项目并执行以下操作:


绕过几乎所有EDR的方式


现在启用 masm:

绕过几乎所有EDR的方式


现在我们必须将我们的 asm Item 类型编辑为 Microsoft Macro Assembler

绕过几乎所有EDR的方式

完成所有这些之后,我们将头文件包含在 main.cpp 文件中,现在我们可以直接使用 NtCreateFile 了,更神奇的是使用我们刚刚执行的操作 EDR 将无法看到我们使用我们通过用户模式挂钩创建的 NtCreateFile 函数时执行的操作!

如果我不知道如何调用 NtAPI 怎么办?

嗯……老实说,我确实遇到过这种情况,我的解决方案很简单,我对 NtCreateUserProcess 的 NtCreateFile 做了同样的事情——但是,我使用自己的钩子钩住了原始的 NtCreateUserProcess,当它被调用时,我重定向了执行使用 CreateProcesW 生成的所有参数返回到我的汇编函数,它有很好的文档记录并且易于使用,因此当我使用 NtCreateUserProcess 系统调用时,我绕过了 EDR 检查。

我如何自己挂钩 API?

这也很简单,为此您需要使用以下系统调用。NtReadVirtualMemory、NtWriteVirtualMemory 和 NtProtectVirtualMemory,结合这些系统调用,我们可以将钩子安装到我们的进程中,而 EDR 不会注意到我们的操作。因为我已经解释了如何调用系统调用,所以我会建议你用google研究一下如何识别你想要使用的正确系统调用;-)——现在,我将展示一个关于 ntdll 的 x64 位钩子的例子.dll!NtReadFile


绕过几乎所有EDR的方式


在上面的例子中,我们可以看到 mov rax, <Hooking function>; 的操作码。jmp rax。

这些操作码被写入 NtReadFile 的开头,这意味着当我们的程序尝试使用 NtReadFile 时,它将被迫跳转到我们的任意函数。

需要注意的是,由于 ntdll.dll 默认情况下只有读取和执行权限,我们还必须向该部分内存添加写入权限,以便在那里写入我们的钩子。

1.2 Windows API Hooking 绕过——使我们的代码可移植

为了保持我们的代码可移植性,我们必须将我们的代码与任何 Windows 操作系统版本相匹配……虽然听起来很难,但实际上并没有那么难。

在本节中,我将向您展示一个用于获取 Windows 操作系统内部版本号的 POC 代码,请谨慎使用,并在完成本文后改进代码并结合您在此处学到的所有内容(如果您完成本文,您将获得工具来做到这一点)。

Windows 内部版本号存储在 — SOFTWAREMicrosoftWindows NTCurrentVersion 注册表项中,利用这些知识,我们将从注册表中提取其值并将其存储在静态全局变量中。

之后,我们还需要创建一个全局静态变量,用于存储最后调用的系统调用,每次调用系统调用时都必须更改此变量,这为我们提供了以下代码。


绕过几乎所有EDR的方式


为了动态获取系统调用号,我们需要以某种方式将其存储在 RAX 寄存器中,为此我们将创建以下函数。

绕过几乎所有EDR的方式

正如你在上面的例子中看到的,我们的函数有一个映射字典,它有一个基于内部版本号的键和值,并根据当前运行的 Windows 操作系统版本返回正确的系统调用号。

但是我将如何在 RAX 动态存储返回值?

好吧,通常每个函数的返回值在运行后都会存储在 RAX 寄存器中,这意味着如果您执行以下汇编代码:call GetBuildNumber函数的返回值将存储在 RAX 寄存器中,从而导致我们想要的场景。

但是这并不容易,有时会有很多小问题。每次我们从另一个函数内部调用一个函数调用时,第二个函数将遍历 rcx、rdx、r8、r9 寄存器,导致发送给第一个函数的参数丢失,因此我们需要存储堆栈中的先前值,并在我们完成 GetBuildNumber 函数后恢复它们,这可以通过以下代码实现


绕过几乎所有EDR的方式


正如您再次看到的,我们告诉链接器 GetBuildNumber 是一个外部函数,因为它存在于我们的 CPP 代码中。

2. 导入的原生 API——PEB 和 TEB 解释。

如果认为使用直接系统调用会为你解决所有问题,那你就有点误会了,EDR 还可以看到您使用的是哪些 Native Windows API,例如 GetModuleHandleW、GetProcAddress 等,为了克服这个问题,我们首先必须了解如何在不直接使用这些原生API的情况下使用这些函数,这里来帮助我们PEB,PEB是进程环境块,它包含在TEB内部,也就是线程环境块,PEB总是位于TEB 之后的 0x060 偏移量(在 x64 位系统中)。

在 Windows 操作系统中,TEB 位置始终存储在 GS 寄存器中,因此我们可以轻松找到 gs:[60h] 偏移位置处的 PEB。

让我们按照下面的截图来看看如何计算这些偏移量。

这可以使用 WinDbg 使用命令进行检查 dt ntdll!_TEB


绕过几乎所有EDR的方式


正如我们在以下屏幕截图中在 0x060 偏移处看到的,我们找到了 PEB 结构,进一步调查我们可以使用以下命令找到 PEB 中的 Ldr dt ntdll!_PEB

绕过几乎所有EDR的方式

在上面的截图中我们可以看到 Ldr 也位于 0x018 偏移处,PEB LDR 数据包含另一个元素,用于存储有关加载的 DLL 的信息,让我们继续探索。

绕过几乎所有EDR的方式

再往下走,我们看到在 0x010 的偏移量处,我们找到了将被加载的模块列表 (DLL),利用所有这些知识,我们现在可以创建一个 C++ 代码来获取 ntdll 的基地址,而无需使用 GetModuleHandleW,但首先,我们必须知道我们在该列表中寻找什么。

绕过几乎所有EDR的方式

在上面的截图中我们可以看到我们对_LDR_DATA_TABLE_ENTRY结构中的两个元素感兴趣,这些元素是BaseDllName和DllBase,我们可以看到DllBase持有一个指向Dll基地址的void指针,BaseDllName是一个UNICODE_STRING结构,这意味着为了读取 UNICODE_STRING 中的内容,我们需要访问它的Buffer值。

这也可以通过查看 MSDN 上的 UNICODE_STRING typedef 来简单地检查


绕过几乎所有EDR的方式

使用到目前为止我们所学的所有内容,我们将创建并使用以下代码来获取 ntdll — dll 的句柄。

绕过几乎所有EDR的方式

在我们获得了我们想要的 DLL 的句柄后,即 ntdll.dll 我们必须找到它的 API(NtReadFile 等)的偏移量,这也可以通过将 DllBase 地址中的部分映射为 IMAGE 来实现,这可以使用以下代码完成并实现

绕过几乎所有EDR的方式


在我们准备好我们的函数之后,让我们做一个小 POC 来看看我们实际上可以得到一个 DLL 的句柄并在其中找到导出的函数。


绕过几乎所有EDR的方式

3. 总结

本文重点是如何手动获取已加载模块的句柄并使用其函数,如何挂钩 Windows 系统调用,如何使用程序集实际编写我们自己的。

结合所有的知识,现在几乎可以使用想要的一切,在扫描下,躲避EDR的检查,甚至在不使用GetModuleHandleW的情况下使用PEB在ntdll.dll上安装钩子,并且不使用任何本机Windows API如WriteProcessMemory,因为我们可以使用我们自己的程序集执行相同的操作,所以我现在让你们修改我之前向你们展示的挂钩代码,使用我们在本文中学到的 PEB 技巧;-)

这就是是如何绕过几乎所有 EDR 并构建一个 POC 勒索软件来加密整个计算机的。


原文始发于微信公众号(军机故阁):绕过几乎所有EDR的方式

版权声明:admin 发表于 2021年11月7日 上午8:22。
转载请注明:绕过几乎所有EDR的方式 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...