EDR Series

翻译:

EDR Series

How EDR Hooks API Calls (Part-1)

EDR 是目前比较热门的话题,本文对 McAfee EDR Hook API 的方式进行一些技术分析。

我们知道多数 EDR 在用户层进行 hook(多数是在 ntdll 中)。本文中我们讲讨论 EDR 如何 hook 这些 Dll 中的 API。我们本文以 McAfee EDR 为例,可能不适用于其它 EDR 产品。

文末有所有的函数流程来帮助理解。

img

图中我们可以看到 3 个与 McAfee 相关的 DLL 被注入到了当前进程中:

  • mfedeeprem32.dll - McAfee Deep remediation Injected
  • mfehcinj.dll - McAfee HookCore Injected Environment
  • mfehcthe.dll - McAfee HookCore Thin Hook Envrionment

现在的问题是,“哪个 DLL 是最先加载的?”,“它们是一起加载进来的吗?还是一个 DLL 加载了其它 DLL?”。接下来我们使用 x64dbg 寻找这些问题的答案。

在 x64dbg 中启用 DLL Entry 和 DLL Load 的断点:

img

现在我们重新开始i程序,可以在 DLL 加载时触发断点。我们一直 continue,直到发现其中一个 McAfee DLL。在几个断点之后我们发现 mfehcinj.dll 被加载了,这是第一个加载的 DLL。

img

我们观察它的导出表,可以看见 4 个导出函数,其中一个是初始化函数:

img

Initialize 函数中我们可以看到对 ThinHookInterface 的调用。ThinHookInterface 基本上时 加载 mfehcthe.dll 然后尝试从中获取GetThinHookInterface函数的地址并进行一些操作。

img

ThinHookInterface中我们可以看到使用LoadLibararyA加载 mfehcthe.dll。然后获取GetThinHookInterface函数并调用它。

img

在调用GetThinHookInterface之后,检查返回值是否是 0。如果不是则执行到loc_10007240,执行一些步骤后调用sub_10006a60resolve_ldr_x_addr)。

img

进入resolve_ldr_x_addr,我们可以看到它使用sub_100088a0(GetProcAddress_Dyn)获取 ntdll 中的两个函数LdrLoadDllLdrResolveDelayLoadedAPI

img

实际上,GetProcAddress_dyn函数分配了一些内存并将一些数据和获得的函数地址放在一起:

img

现在我们会有些疑惑为什么将这些获取到的函数地址放到堆上,这是因为这些函数还没有被调用过。经过一些操作后我们回到了 Initialize 函数。再下面几步我们可以看到函数sub_10003550(mfedeeprem32_and_loading_proc_address)的调用。简单来说,这个函数加载 mfedeeprem32.dll 并且解析了一些函数地址。

img

如果我们仔细观察sub_10003550来查找堆函数sub_10002a90的调用,我们可以看到这个函数是非常大的。它做了很多操作例如创建命名管道,加载 DLL,解析函数地址等等,其中包含很多函数调用。

img

然而在这里,我们只关心两件事:

  • 被加载的 DLL
  • 其它函数是如何解析并存放到堆中的

我们可以看到loc_10002ED1这个位置,mfedeeprem32.dll 被加载:

img

一些创建命名管道和配置的操作之后,它调用了 mfedeeprem32.dll 中的函数sub_1000A380。这个函数真正解析了一些函数地址并放到了堆中。

img

  1. mfedeeprem32.sub_100A380 调用 mfedeeprem32.sub_1000A150
  2. mfedeeprem32.sub_1000A150中存在一个循环来解析所有的函数
  3. mfedeeprem32.sub_1000A150调用mfehcinj.sub_10003B40

mfehcinj.sub_10003B40调用sub_100088A0(GetProc_address_Dyn)来解析函数地址并且存放到堆中。

img

以下是所有解析的 API 函数地址(包括最初解析的两个 API):

img

我们可以看到表中的 19 个 API 是系欸后存储在内存中的。这些完成后我们回到 Initialize函数。过一段时间后我们进入sub_10005490,这是 hook 开始的地方。

这里有一些可以再 API Hook 图中仔细查看的函数调用。

sub_10005490中调用了mfehcthe.sub_10004FC0

img

如果我们深入查看这个函数,我们可以进行到mfehcthe.sub_10001650,在这里两个函数被放到内存中。这两个函数与 EDR 的 hook 函数有关,我们稍后会证明这一点。

mfehcthe.sub_10001650中,我们可以看到分配了内存并设置为 0xcc。

img

上面提到两个函数被使用 memcpy 复制进了新分配的内存

img

img

在函数mfehcthe.sub_100068F0中,另一个函数被写入内存(在偏移 0x660 处),这是 hook 后进入的函数,这个函数将调用以上的两个函数。

img

我们假设mini_shellcode_1在地址 0xBB0000,hook 后的函数/代码将在偏移 0x660 即 0xBB0660。

img

我们可以在调试器中确认:

img

目前为止,EDR hook 函数的代码已经放进了内存中,但是用户层的 patch 还没有应用。需要进行 Jump patch 来在执行 syscall 前检查行为是否可疑。

在函数mfethe.sub_10006180中进行了 patch。

img

mfehcthe.sub_10006180中我们可以看到调用了 WriteProcessMemory,其中 BaseAddress 是要 Hook 的函数地址,buffer 是 5 字节的 jmp 命令。BaseAddress 是从之前保存的地址中取得的,也就是说有 19 个 BaseAddress。Jump patch 是在调用mfehcthe.sub_10004FC0之前计算的。

img

如果我们仔细查看调试器,我们可以确认此时 BaseAddress 是LdrLoadDll的地址并且 buffer 是 jmp hook。

img

并且我们也可以确认有 5 字节被覆盖成了 jmp 指令

img

而在进行 patch 之前一切都是正常的

img

Patch 之后,我们可以看到 jmp 到的地址是 0xBB0660,这就是 hook 函数的地址。

img

然后 19 个函数全部都被 hook。

总结起来,19 个函数被查找地址,解析后存放到内存中。然后两个函数被复制进内存,这两个函数用来检查调用 API 的代码。然后,另一个函数被放到内存中偏移 0x660 处。

这三个函数被放到了内存中的相同区域中。第三个函数(0x660)中有一些动态解析的内容,比如第一个函数的地址(mini_shellcode_1)。

第二个函数将在第一个函数中被调用。然后一切都准备好了,需要将 jmp patch 计算出来并应用到这 19 个函数中,这些 jmp 将指向内存中偏移 0x660 的函数

img

Function Re-casting (Part-2)

在查看 McAfee EDR 时,我们注意到在进程创建时会加载一些 DLL,我们害发现这些 DLL 会进行一些加载库,解析函数地址,写内存等操作,这是我们关心的。所以我们可以利用 EDR 中这些 DLL 的导出函数来做这些事情。首先我们需要查找可能有用的函数。在进行一些逆向后我们发现了允许我们加载库并且获取函数地址的函数。我们可以看到这三个参数都是可以完全控制的:

img

Param_1 = Module Name

Param_2 = Function Name

Param_3 = FARPROC Pointer

以上函数尝试使用GetModuleHandleA获取模块的句柄(如果模块已经存在),然后调用GetProcAddress;如果不存在则先加载模块然后调用GetProcAddress,这正是我们要找的函数,而且所有的参数都可控。这个函数很容易控制,但是不是所有函数都是这样。过了一会我们发现一个可以创建线程和远程线程的函数。在以下函数中我们可以控制 Param 123 和RtlCreateUserThreadCreateRemoteThreadEx的地址。

Param_1 = handle

Param_2 = thread routine/function

Param_3 = parameters for thread function

如果全局变量RtlCreateUserThread没有设置且CreateRemoteThreadEx设置了,它将会调用CreateRemoteThreadEx,反之一样。

img

目前还剩下一件事就是控制全局变量。全局变量是静态的,我们可以用偏移加上模块基地址来获取。

1
2
RtlCreateUserThread = BaseAddress of mfehcinj.dll + 0x7fb44
CreateRemoteThreadEx = BaseAddress of mfehcinj.dll + 0x7fb50

所以我们可以复制两个函数的地址到这些全局变量的位置,记得想要调用一个的时候要将另一个清零。

McAfee 状态

img

Shellcode 执行

img

注意:这只是一个利用 McAfee 注入的 DLL 的 POC,只能用于 McAfee 环境。由于这种方式依赖了 McAfee 环境所以分析人员很难静态分析字符串加密的二进制文件。然而它也有一些缺点。

作者

lll

发布于

2023-03-21

更新于

2023-03-23

许可协议