最近我刚刚开始学习Windows内核漏洞利用,因此决定以博客的形式分享一些我的学习心得。

上一部分介绍了如何搭建实验环境;现在,我们将接触Ashfaq Ansari所开发的HEVD(HackSys Extreme Vulnerable Driver,HackSys Team小组所开发的一个Kernel Driver,其中包含大量常见漏洞且原理简单,主要考验各种利用方法,项目网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver),进而将其熟练掌握。下一部分,我计划简要介绍一些漏洞示例和利用技术。

本文所用到的软硬件环境:

·上一部分所描述的实验环境;

·HackSys Extreme Vulnerable Driver(HEVD),包括预构建版本(下载网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases)和源代码(下载网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver);

·OSR驱动加载器(下载网址:https://www.osronline.com/article.cfm?article=157);

·DebugView工具(下载网址:https://technet.microsoft.com/en-us/sysinternals/debugview.aspx,位于Sysinternals工具集中,下载网址:https://technet.microsoft.com/en-us/sysinternals/bb842062);

·Visual Studio 2012开发环境(可以选择你喜欢的版本)。

安装并测试HEVD

首先,我将演示如何安装HEVD:我们将搭建并配置调试方和被调试方,使得调试字符串和HEVD的符号可见;我们还将接触专项利用工具。

查看调试字符串

HEVD和专项利用工具以调试字符串的形式打印大量的信息。我们既可以从调试方主机(使用WinDbg调试器),也从被调试方主机(使用DebugView工具)查看这些信息。

为了查看驱动安装过程中所打印的字符串,我们将在安装HEVD之前进行适当的配置。

调试方:

为了获得命令行提示符“kd”,我们需要中断被调试方的执行流程(在WinDbg调试器中,选择“调试”菜单 -> “中断”选项)。然后,我们使用如下命令,启动打印调试字符串的功能:

ed nt!Kd_Default_Mask 8

之后,我们通过执行“g”命令,使被调试方恢复运行。

注意:开启该功能会使被调试方运行速度变慢;因此,应尽可能在本地查看调试字符串(即只在被调试方)。

被调试方:

我们需要以管理员权限运行DebugView工具,然后选择“拦截”菜单 -> “拦截内核模式”,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(1)

安装驱动

首先,我们将预构建的程序包(驱动 漏洞)下载到被调试方(即被攻击主机),安装并测试。我们可以在HackSys Team小组的github页面发布版块(网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases)找到它。程序包丽包括两个版本的驱动,有漏洞/无漏洞;我们选择有漏洞,32位系统编译(i386)的版本,如下图所示

什么是内核漏洞(初学Windows内核漏洞利用)(2)

我们选择服务启动类型为“自动”;然后点击“注册服务”按钮,在其成功后点击“启动服务”。

我们可以在WinDbg调试器(调试方主机)和被调试方主机上的DbgView工具上看到所打印的HEVD旗标信息。

添加符号

HEVD的预编译程序包自带符号文件(sdb文件),我们可以将其添加到调试方。首先,我们通过发送中断信号来停止被调试方的运行,并使用“lm”命令查看所有已加载模块。

为了找到HEVD模块,我们通过如下命令来设置过滤器:

lm m H*

从结果可见,HEVD模块没有附加任何符号;还好,这很容易修复。首先,为了打印WinDbg调试器在搜索符号过程中参考路径的所有信息,我们使用如下命令开启符号加载通知:

!sym_noisy

然后,使用如下命令尝试重载符号:

.reload

…并尝试再次引用它们。从所见到的路径中,我们可以复制pdb文件;在将pdb文件移动到调试方主机的合适位置之后,重新加载符号。可以通过使用如下命令尝试打印HEVD中的所有函数,来测试符号是否成功添加:

x HEVD!*

测试漏洞利用工具

相同的程序包中还包含一组专项利用工具,我们可以通过执行适当的命令来运行其中每一个工具。下面我们尝试使用其中一些工具,并将cmd.exe设置为待运行的程序,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(3)

使用Pool Overflow漏洞利用工具,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(4)

如果整个漏洞利用过程运行成功,目标程序(cmd.exe)将被分配更高的权限。

通过执行命令“whoami”可以确认,该程序确实提权运行了,结果如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(5)

同时,我们可以在调试方主机上看到漏洞利用工具所打印的调试字符串,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(6)

除了Double Fetch漏洞之外,其他所有的漏洞利用工具在单核环境下都将运行良好;如果我们想要(Double Fetch)漏洞利用工具生效,就需要保证被调试主机为双核环境。

注意:某些漏洞利用工具并不是100%可靠,在使用它们之后可能会造成系统崩溃;别担心,这很正常。

驱动你好,开始交流!

与用户层情况类似,内核层的漏洞利用同样开始于寻找能够向程序提供输入的点;然后,我们需要找到能够干扰执行流程(与用户层不同,内核层的一次崩溃将直接导致蓝屏!)的输入;最终,我们将尝试精心构造输入,以便我们能够控制有漏洞程序的执行流程。

为了能够从用户模式与驱动交流,我们将向其发送IOCTL(Input-Output control, 输入/输出控制)信号。IOCTL信号允许我们从用户层向驱动发送一些输入缓冲区;这就是我们可以尝试漏洞利用突破的点。

HEVD包含了各种类型漏洞的样例,其中每一个都可以通过使用不同的IOCTL信号来触发,并通过所提交的缓冲区来利用;其中某些(并非所有)漏洞在触发时将导致系统崩溃。

查找设备名称与IOCTL信号列表

我们在与驱动交流之前,需要知道两件事情:

1. 驱动所创建的设备(若没有创建任何设备,则我们无法与之交流);

2. 驱动所接收的IOCTL(输入/输出控制)信号列表。

HEVD是开源的,因此我们可以从源代码中直接读取所有必要的数据;而在实际环境中,为了获取这些数据我们将花费大量的时间来对驱动进行逆向分析。

HEVD创建设备的代码片段(网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.c#L79),如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(7)

设备名称如上图所示。

现在,我们需要找到IOCTL信号列表,从阅读IRP数组的相关代码(网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.c#L109)开始,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(8)

IRP_MJ_DEVICE_CONTROL相关的函数将分配发送给驱动的IOCTL信号;因此,我们需要深入阅读该函数代码(网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.c#L193),如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(9)

代码中包含一个switch语句,其作用是调用一个对应的处理函数来处理一个特定的IOCTL信号;我们可以通过复制该switch语句的case分支来得到IOCTL信号列表。一个头部的预处理生命定义了这些常量的值(网址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.h#L57),如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(10)

编写客户端应用程序

至此,我们获取了我们的程序用来和驱动交流的所有必要数据;我们可以将其全部放入一个头文件中(如hevd_constants.h文件中所示)。

每个IOCTL信号的数值由标准Windows系统头文件winioctl.h所定义的一个宏来创建,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(11)

如果你的程序中包含了windows.h头文件,则以上宏将自动添加。现在,我们不需要为特定常量的具体含义所烦恼;我们将直接原样使用已定义的元素。

因此,我们已经准备好编写一个简单的用户层应用程序,来与驱动交流:首先,我们使用CreateFile函数来打开设备;然后,我们使用DeviceIoControl函数来发送IOCTL信号。

以下你将看到一个精简版的例子,该应用程序向驱动发送STACK_OVERFLOW这一IOCTL信号(具体实现代码见send_ioctl.cpp文件)。

尝试编译该程序,并在被调试方主机上使用。打开DebugView工具,并观察驱动所打印的调试字符串,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(12)

如果调试方主机也开启了打印调试字符串的功能,则会看到相同的输出,如下图所示。

什么是内核漏洞(初学Windows内核漏洞利用)(13)

如图所见,驱动获取了我们的输入,并对其给出报告。

练习:系统崩溃!

作为练习,我创建了一个HEVD的小型客户端,其功能是向HEVD发送多种带有指定长度输入缓冲区的IOCTL信号。源代码网址如下:

https://github.com/hasherezade/wke_exercises/tree/master/task1

以及编译的32位二进制程序网址如下:

https://drive.google.com/open?id=0Bzb5kQFOXkiSNTlVSjMtZzRfZDg

尝试多种IOCTL信号,直到系统崩溃。由于被调试方运行于调试方的控制之下,因此其系统并不会蓝屏;反之,WinDbg调试器将被触发。试着对每种情况进行简单的分析;从使用如下命令打印信息开始:

!analyze –v

其他一些有帮助的命令如下:

k — 堆栈跟踪

更多的命令信息,请使用“.hh”命令查看WinDbg调试器帮助文件。

在我们的示例应用程序中,用户缓冲区使用字符“A”(ASCII码为0x41)进行填充(网址:https://github.com/hasherezade/wke_exercises/blob/master/task1/src/main.cpp#L34),具体语句如下:

RtlFillMemory(inBuffer, bufSize, ‘A’);

因此,只要我们在崩溃分析中见到该形式的字符串,就说明特定的数据能够被用户填充。

需要注意的是,触发相同的漏洞可能给出不同的输出,这依赖于当前引起崩溃的源头,相关的因素比如溢出的尺寸,内存的当前布局等等。

附录参考

http://expdev-kiuhnm.rhcloud.com/2015/05/17/windbg/ — WinDbg调试器介绍,作者Massimiliano Tomassoli

https://github.com/mwrlabs/win_driver_plugin — 一种能够为分析IOCTL相关代码或逆向分析Windows驱动提供帮助的IDA Pro 插件,作者Sm Brown

本文由看雪翻译小组 木无聊偶编译

,