A Bit of Context 一点背景
IoctlHunter is a command-line tool designed to simplify the analysis of IOCTL calls made by userland software targeting Windows drivers.
IoctlHunter 是一个命令行工具,旨在简化对针对 Windows 驱动程序的用户空间软件发出的 IOCTL 调用的分析。
TL;DR: Here are the videos demonstrating the usage of IoctlHunter
TL;DR:以下是演示IoctlHunter用法的视频
From a cybersecurity perspective, IoctlHunter empowers security researchers to identify IOCTL calls that could potentially be reused in standalone binaries to perform various actions, such as privilege escalation (EoP) or killing Endpoint Detection and Response (EDR) processes.
从网络安全的角度来看,IoctlHunter 使安全研究人员能够识别可能在独立二进制文件中重用的 IOCTL 调用,以执行各种操作,例如权限提升 (EoP) 或终止终结点检测和响应 (EDR) 进程。
This technique, also known as BYOVD (Bring Your Own Vulnerable Driver), involves embedding a signed vulnerable driver within a binary. Once deployed on a targeted system, the binary loads the driver and sends IOCTL calls to it to execute specific offensive actions with kernel-level privileges.
此技术也称为 BYOFD(自带易受攻击的驱动程序),涉及在二进制文件中嵌入已签名的易受攻击的驱动程序。部署在目标系统上后,二进制文件将加载驱动程序并向其发送 IOCTL 调用,以使用内核级权限执行特定的攻击性操作。
This article was written in continuity of a blog post written by Alice. In this awesome article, Alice explains how it is possible to perform a static analysis of Windows drivers to retrieve features allowing a userland software to kill protected processes such as EDR ones.
本文是爱丽丝撰写的一篇博文的延续。在这篇精彩的文章中,Alice 解释了如何对 Windows 驱动程序执行静态分析以检索功能,从而允许用户空间软件终止受保护的进程,例如 EDR 进程。
While reading it, it definitely challenged me to build a tool that enables lazy reverse engineers to easily discover drivers providing juicy features for offensive use cases.
在阅读它时,它确实挑战了我构建一个工具,使懒惰的逆向工程师能够轻松发现为攻击性用例提供多汁功能的驱动程序。
We will not deep dive into how drivers work or describe all their interactions with userland processes in detail. Thus, I strongly recommend reading Alice’s blog post to gain a deep understanding of how drivers work and how to exploit them.
我们不会深入探讨驱动程序的工作方式,也不会详细描述他们与用户空间流程的所有交互。因此,我强烈建议您阅读 Alice 的博客文章,以深入了解驱动程序的工作原理以及如何利用它们。
However, before understanding how IoctlHunter works, let me introduce a few key concepts.
但是,在了解 IoctlHunter 的工作原理之前,让我介绍几个关键概念。
Driver Loading 驱动程序加载
First, drivers must be loaded on the running Windows system. This can be achieved by running the following command lines:
首先,驱动程序必须加载到正在运行的 Windows 系统上。这可以通过运行以下命令行来实现:
$> sc.exe create MyDriver binPath= C:\windows\temp\MyDriver.sys type= kernel $> sc.exe start MyDriver |
As you can see, the load of a driver consist in starting a service. Thus, the same result can be achieved by performing the following steps:
如您所见,驱动程序的负载在于启动服务。因此,可以通过执行以下步骤来获得相同的结果:
- Create a registry path within
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MyDriver
(see. the function RegCreateKeyExW fromAdvapi32.dll
)
在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MyDriver
中创建注册表路径(参见 RegCreateKeyExW 函数Advapi32.dll
) - Set multiple registry key within it including the
ImagePath
which is a string pointing to the absolute file path where the driver binary is stored on the disk (see. the function RegSetValueExA fromAdvapi32.dll
)
在其中设置多个注册表项,包括ImagePath
这是一个字符串,指向驱动程序二进制文件存储在磁盘上的绝对文件路径(请参阅函数 RegSetValueExA fromAdvapi32.dll
) - Start the service and load the driver by specifying the newly created registry path (see. the function NtLoadDriver from
ntdll.dll
)
通过指定新创建的注册表路径启动服务并加载驱动程序(请参阅函数 NtLoadDriver fromntdll.dll
)
Once your driver successfully loaded, you will need to open a handle
on it to interact with it. This can be achieved by calling function such as CreateFileA
驱动程序成功加载后,需要 handle
打开驱动程序才能与之交互。这可以通过调用 CreateFileA 等函数来实现
The tool Backstab, developed in C++ by Yasser, provide a very nice implementation of arbitrary driver loading and unloading there: Driverloading.c
由 Yasser 用 C++ 开发的工具 Backstab 提供了任意驱动程序加载和卸载的非常好的实现:Driverloading.c
Run kernel land code 运行内核 land code
Once you obtain a handle on your loaded driver, you are now able to send instruction to it. Indeed, drivers can exposed specific functions to be run on the kernel side.
获取已加载驱动程序的句柄后,现在可以向其发送指令。事实上,驱动程序可以公开要在内核端运行的特定函数。
In order to specify which function must be executed, userland programs can send the IOCTL (I/O control code). This 32 bits value is bascically dedicated to indicate to the driver which function must be called by within the drivers code.
为了指定必须执行哪个函数,用户空间程序可以发送 IOCTL(I/O 控制代码)。此 32 位值基本上专用于向驱动程序指示必须在驱动程序代码中调用的函数。
The DeviceIoControl
function (see. MS documentation) provides this interface between user land programs and the driver code running in the kernel side.
函数( DeviceIoControl
请参阅。MS documentation)在用户本地程序和内核端运行的驱动程序代码之间提供了这个接口。
Few parameters are required to do the stuff:
执行这些操作需要很少的参数:
- A handle to the targeted driver (
hDevice
)
目标驱动程序的句柄 (hDevice
) - The transmitted IOCTL code (
dwIoControlCode
)
传输的 IOCTL 代码 (dwIoControlCode
) - The associated transmitted data (
lpInBuffer
) and its size (nInBufferSize
)
关联的传输数据 ( ) 及其大小 (lpInBuffer
nInBufferSize
) - The returned data (
lpOutBuffer
,nOutBufferSize
)
返回数据 (lpOutBuffer
,nOutBufferSize
)
Some examples of interesting IOCTL calls involve transmitting basic data in the lpInBuffer
parameter, such as an integer specifying a process to be terminated. However, the typical usage of DeviceIoControl
often requires submitting a custom C-like structure in this parameter, containing various data types. Understanding how to construct such a structure may necessitate static analysis of the driver.
一些有趣的 IOCTL 调用示例涉及在 lpInBuffer
参数中传输基本数据,例如指定要终止的进程的整数。但是,通常 DeviceIoControl
需要在此参数中提交自定义的类似 C 的结构,其中包含各种数据类型。了解如何构建这种结构可能需要对驱动程序进行静态分析。
In such case, a good approach is first to identify the IOCTl code related to the function that we are intersting in. Then, with a reverse software like cutter, ghidra or IDA, we can start by looking for the Driver main function and browse the code until we found “a switch case” pattern were the dispatching between all IOCTL codes is made between all driver implemented functions (not always the case!).
在这种情况下,一个好的方法是首先识别与我们正在插入的函数相关的 IOCTl 代码。然后,使用 cutter、ghidra 或 IDA 等反向软件,我们可以从查找驱动程序主函数开始并浏览代码,直到我们找到“开关案例”模式,即在所有驱动程序实现的函数之间进行所有 IOCTL 代码之间的调度(并非总是如此!
Alice 在驱动程序端调度 IOCTL 代码的开关案例示例
Finally, once a static comparison between our IOCTL code is made, we are not that far of the paramater containing the lpInBuffer
pointer. If you look for data pointed by it, you should be able to analyse the provided structure.
最后,一旦对 IOCTL 代码进行了静态比较,我们离包含 lpInBuffer
指针的参数就不远了。如果您查找它所指向的数据,您应该能够分析提供的结构。
Chain it 链接它
As we’ve discussed, multiple calls can be intercepted to dynamically retrieve drivers loaded by a program based on changes to registry keys.
正如我们所讨论的,可以截获多个调用,以根据对注册表项的更改动态检索程序加载的驱动程序。
Moreover, DeviceIoControl
calls contain sufficient information to help us retrieve the portion of driver code that will be executed, the data sent as parameters to perform tasks, and the size of this data.
此外, DeviceIoControl
调用包含足够的信息来帮助我们检索将要执行的驱动程序代码部分、作为执行任务的参数发送的数据以及此数据的大小。
In short, from a “user land” perspective, a security researcher can intercept all the previously mentioned Win32 functions to dynamically obtain the following information:
简而言之,从“用户领域”的角度来看,安全研究人员可以拦截前面提到的所有 Win32 函数,以动态获取以下信息:
- Automatically detect loaded drivers.
自动检测加载的驱动程序。 - Intercept IOCTL calls and the associated data.
截获 IOCTL 调用和关联数据。 - Retrieve the driver using the handle passed as a parameter to
DeviceIoControl
.
使用作为参数传递给 的句柄检索驱动程序DeviceIoControl
。
With this information, you can extract necessary data in a lab while analyzing a tool with a driver capable of executing kernel-level actions for offensive purposes.
有了这些信息,你就可以在实验室中提取必要的数据,同时使用能够出于攻击目的执行内核级操作的驱动程序来分析工具。
Once you’ve gathered all this information by intercepting these functions or by statically reversing the driver, you’ll have everything needed to create a binary that loads the driver and sends the correct IOCTL with the appropriate data.
通过截获这些函数或静态反转驱动程序来收集所有这些信息后,你将拥有创建加载驱动程序并发送具有相应数据的正确 IOCTL 的二进制文件所需的一切。
However, in real world scenarios, multiple drivers can be dynamically loaded and called by a single executable. This can make it challenging, with the hooking approach, to identify the IOCTLs that match the observed features while using tools not specifically designed for this purpose. That’s why I began developing IoctlHunter.
但是,在实际方案中,单个可执行文件可以动态加载和调用多个驱动程序。这使得使用钩接方法识别与观察到的特征相匹配的 IOCTL 具有挑战性,同时使用不是专门为此目的设计的工具。这就是我开始开发 IoctlHunter 的原因。
Ease the process with IoctlHunter
使用 IoctlHunter 简化流程
Unlike some of today’s tools, IoctlHunter differs from static driver analysis approaches. The tool aim is to execute a binary that is likely reliant on drivers providing interesting offensive features. By exploring the various options presented by such programs, IoctlHunter helps in monitoring the IOCTL calls that occured.
与当今的一些工具不同,IoctlHunter 不同于静态驱动程序分析方法。该工具的目的是执行一个二进制文件,该二进制文件可能依赖于提供有趣的攻击性功能的驱动程序。通过探索此类程序提供的各种选项,IoctlHunter 有助于监视发生的 IOCTL 调用。
The mindset to adopt when using IoctlHunter is a bit like using BurpSuite to analyze a website. You navigate to the options that might interest you and look in IoctlHunter for the potential associated IOCTL.
使用 IoctlHunter 时采用的心态有点像使用 BurpSuite 分析网站。导航到您可能感兴趣的选项,并在 IoctlHunter 中查找潜在的关联 IOCTL。
The tool provides several essential pieces of information to replay an IOCTL, thanks to the DeviceIoControl
function (see MS documentation):
由于该 DeviceIoControl
功能,该工具提供了重放 IOCTL 的几个基本信息(参见 MS 文档):
- The target driver 目标驱动程序
- The transmitted IOCTL code (
dwIoControlCode
)
传输的 IOCTL 代码 (dwIoControlCode
) - The associated transmitted data (
lpInBuffer
) and its size (nInBufferSize
)
关联的传输数据 ( ) 及其大小 (lpInBuffer
nInBufferSize
) - The returned data (
lpOutBuffer
,nOutBufferSize
)
返回数据 (lpOutBuffer
,nOutBufferSize
)
With this information, it is possible to conduct a static analysis of the driver to scrutinize the associated code in detail, starting from the IOCTL code. However, it is also possible to directly replay this IOCTL call if its usage appears straightforward.
使用此信息,可以从 IOCTL 代码开始对驱动程序进行静态分析,以详细检查关联的代码。但是,如果此 IOCTL 调用的使用看起来很简单,也可以直接重播此 IOCTL 调用。
检测动态加载的驱动程序
IoctlHunter 输出提供“dwIoControlCode”和“lpInBuffer”数据的十六进制转储
Successful use of IoctlHunter empowers Red Teamers and Penetration Testers to create a standalone executable that installs a specific driver and issues one or more IOCTL calls to perform various tasks. The advantage lies in the ability to execute signed drivers with kernel privileges, and to exploit useful features in an offensive way.
成功使用 IoctlHunter 使 Red Teamers 和 Penetration Testers 能够创建一个独立的可执行文件,该可执行文件安装特定驱动程序并发出一个或多个 IOCTL 调用来执行各种任务。其优点在于能够使用内核权限执行已签名的驱动程序,并以令人反感的方式利用有用的功能。
Obviously such standalone BYOVD binaries requires the SeLoadDriverPrivilege
flags to be able to do the magic!
显然,这种独立的 BYOVD 二进制文件需要 SeLoadDriverPrivilege
标志才能施展魔法!
How it works? 它是如何工作的?
As IoctlHunter base its analysis on hooking, I decided to use the most advanced and easily scriptable tool to do this job: Frida. Frida is basically a dynamic instrumentation tool which support Python to script with. Its main usage consists in injecting code into a process and to hook functions within running processes, facilitating a debugging, as well as reverse engineering. I am sure lots of you already used it for mobile pentests (certificate pinning bypass FTW!) or to easily reverse thick clients.
由于 IoctlHunter 的分析基于钩子,我决定使用最先进、最容易编写脚本的工具来完成这项工作:Frida。Frida 基本上是一个动态检测工具,支持 Python 脚本。它的主要用途包括将代码注入进程,并在正在运行的进程中挂钩函数,促进调试以及逆向工程。我相信你们中的许多人已经将它用于移动渗透测试(证书固定绕过 FTW!) 或轻松反转胖客户端。
From this awesome libraries, IoctlHunter is able to spawn or attach to an existing process to be analyse. Then, a RPC communication is established between the two process and IoctlHunter is able to hook useful functions in order to collect all IOCTL calls, apply fynamic filters on them and much more!
从这个很棒的库中,IoctlHunter 能够生成或附加到要分析的现有进程中。然后,在两个进程之间建立RPC通信,IoctlHunter能够挂钩有用的函数,以收集所有IOCTL调用,对它们应用fynamic过滤器等等!
The full developped Frida script can be found there: script.ts
完整的 Frida 脚本可以在那里找到:script.ts
Demo with PowerTools 使用PowerTools进行演示
In Alice’s blog post, titled “Finding and Exploiting Process Killer Drivers with LOL for $3000,” she demonstrated how a static reverse engineering analysis of the kEvP64.sys
driver used by the PowerTool
software allowed her to develop a process killer tool that could terminate protected processes with kernel-level privileges.
在 Alice 的博客文章中,标题为“以 3000 美元的价格使用 LOL 查找和利用进程杀手驱动程序”,她演示了如何对 PowerTool
软件使用的 kEvP64.sys
驱动程序进行静态逆向工程分析,使她能够开发一种进程杀手工具,该工具可以使用内核级权限终止受保护的进程。
The following video demonstrates how IoctlHunter makes it easy to identify all the elements needed to terminate protected processes using the same tool:
以下视频演示了 IoctlHunter 如何使用同一工具轻松识别终止受保护进程所需的所有元素:
在 PowerTool 上搜寻 IOCTL
Subsequently, using the information obtained, a Golang package provided in the IoctlHunter repository allows you to load and replay the IOCTL calls:
随后,使用获取的信息,IoctlHunter 存储库中提供的 Golang 包允许您加载和重播 IOCTL 调用:
借助 PowerTool 驱动程序终止受保护的进程
Limitiations 限制
It is important to underline that IoctlHunter is not designed to supplant traditional static or dynamic reverse engineering methods used for vulnerability discovery in drivers. Instead, it serves as a complementary tool to help in the dynamic identification of specific IOCTL calls information, providing additional insights into the behavior of drivers loaded by a given software. As describe in this article, the complexity resides in the identification of the data structure linked to the lpInBuffer
data buffer.
需要强调的是,IoctlHunter 并非旨在取代用于驱动程序漏洞发现的传统静态或动态逆向工程方法。相反,它充当补充工具,帮助动态识别特定的 IOCTL 调用信息,从而提供对给定软件加载的驱动程序行为的更多见解。如本文所述,复杂性在于识别链接到 lpInBuffer
数据缓冲区的数据结构。
Furthermore, the tool primarily involves injecting itself into processes for analysis. However, this approach may not work directly when targeting processes are protected with anti-tampering mechanisms. For instance, EDR (Endpoint Detection and Response) processes may not allow injection via Frida without the prior use of a specific driver to open a handle on these protected processes.
此外,该工具主要涉及将自身注入到分析过程中。但是,当目标进程受到防篡改机制的保护时,此方法可能无法直接起作用。例如,EDR(端点检测和响应)进程可能不允许通过 Frida 进行注入,而无需事先使用特定驱动程序来打开这些受保护进程的句柄。
IoctlHunter is designed to gather IOCTL data in a controlled lab environment. This enables the disabling of security mechanisms and the utilization of existing techniques to inject into protected processes.
IoctlHunter 旨在在受控的实验室环境中收集 IOCTL 数据。这样就可以禁用安全机制,并利用现有技术注入受保护的进程。
Finally, the actual version of IoctlHunter allows for hooking various functions within the Windows API that have multiple implementations and/or function prototypes (see blue box on the screenshot below). This diversity arises from the existence of functions suffixed with either ‘W’ or ‘A’, with ‘W’ denoting wide-character (Unicode) and ‘A’ for ANSI character functions, depending on the string encoding used. Additionally, some functions may have ‘Ex’ suffixed, which generally indicates an extended version of the function with additional features or parameters.
最后,IoctlHunter 的实际版本允许在 Windows API 中挂钩具有多个实现和/或函数原型的各种函数(请参阅下面屏幕截图中的蓝色框)。这种多样性源于后缀为“W”或“A”的函数的存在,“W”表示宽字符 (Unicode),“A”表示 ANSI 字符函数,具体取决于所使用的字符串编码。此外,某些函数可能带有“Ex”后缀,这通常表示具有附加功能或参数的函数的扩展版本。
Furthermore, certain functions may be prefixed with ‘Nt’ or ‘Zw’, signifying native API calls that interact more directly with the operating system’s kernel. However, these functions ultimately call each other, and not every program necessarily calls the same function (see green box on the screenshot below). IoctlHunter is designed to hook the most common functions by default to avoid duplication. In cases where it’s necessary, the --all-symbols
option allows for hooking all ‘versions’ of a function but may result in duplicate function calls.
此外,某些函数可能以“Nt”或“Zw”为前缀,表示与操作系统内核更直接交互的本机 API 调用。但是,这些函数最终会相互调用,并且并非每个程序都必然调用相同的函数(请参阅下面屏幕截图中的绿色框)。IoctlHunter 旨在默认挂钩最常见的函数,以避免重复。在必要时,该 --all-symbols
选项允许挂钩函数的所有“版本”,但可能会导致重复的函数调用。
与Frida的钩子函数
What’s next? 下一步是什么?
First, it could be interesting to facilitate the ability to replay IOCTL calls. When the --output
parameter is enabled, the generated file contains a base64 encoded buffer in the data provided through the lpInBuffer
parameter of the DeviceIoControl
function. It might be cool to introduce a feature that allows the replay of such IOCTL calls on the same driver for debugging purposes.
首先,促进重播 IOCTL 调用的能力可能会很有趣。启用该 --output
参数后,生成的文件在通过 DeviceIoControl
函数 lpInBuffer
参数提供的数据中包含一个 base64 编码的缓冲区。引入一项功能,允许在同一驱动程序上重播此类 IOCTL 调用以进行调试,这可能很酷。
The second point of improvement is not directly related to IoctlHunter. Instead, it concerns the project Loldrivers.io created this year by The Haag, which provides an extensive database of known vulnerable drivers. This database serves as an excellent starting point for identifying IOCTL vulnerabilities in BYOVD (Bring Your Own Vulnerable Driver) attacks (see Alice’s tool LOLDrivers_finder).
第二点改进与IoctlHunter没有直接关系。相反,它涉及 The Haag 今年创建的项目 Loldrivers.io 该项目,该项目提供了已知易受攻击的驱动程序的广泛数据库。此数据库是识别 BYOVD(自带易受攻击的驱动程序)攻击中的 IOCTL 漏洞的绝佳起点(请参阅 Alice 的工具LOLDrivers_finder)。
However, the Loldrivers project does not offer detailed information regarding specific vulnerable features or how to exploit them (IOCTL codes, required input data, etc.). Obviously, the collection of this information typically involves significant reverse engineering work. Still, it might be beneficial to reference such details when available, similar to how it’s done for PowerTools, ProcExp512, RTCore64, and other vulnerable drivers. This could assist in the development of tools for drivers already known to be vulnerable (and potentially blacklisted by Microsoft). Additionally, it might contribute to the creation of more precise detection rules based on EDR telemetry (not tested!)
但是,Loldrivers 项目没有提供有关特定易受攻击功能或如何利用它们的详细信息(IOCTL 代码、所需的输入数据等)。显然,这些信息的收集通常涉及大量的逆向工程工作。不过,在可用时引用此类详细信息可能会有所帮助,类似于对 PowerTools、ProcExp512、RTCore64 和其他易受攻击的驱动程序的处理方式。这可以帮助为已知易受攻击(并可能被 Microsoft 列入黑名单)的驱动程序开发工具。此外,它可能有助于创建基于 EDR 遥测的更精确的检测规则(未测试!
Acknowledgements 确认
- Alice for her blog post and tools regarding drivers
Alice 的博客文章和有关驱动程序的工具 - The Haag for the Loldrivers.io project
Loldrivers.io 项目的哈格 - Yasser for the tool Backstab
Yasser工具Backstab - The Microsoft and Frida documentations
Microsoft 和 Frida 文档
原文始发于Zak:IoctlHunter Release (v0.2)