一直以来内核漏洞安全给很多人的印象就是:难,枯燥。但是内核安全是否掌握是衡量一个系统安全工程师水平的标准之一,也是安全从业人员都应该掌握的基本功。本文通过详细的实例带领读者走进内核安全的大门。难度系数:四颗星
原文地址:https://hshrzd.wordpress.com/2017/06/05/starting-with-windows-kernel-exploitation-part-2/
本文默认读者已经配置好了基本实验环境,因为环境配置网络上有大量详细资源,在此编者不再单独成文介绍环境。此文章供参考:https://hshrzd.wordpress.com/201 … setting-up-the-lab/
1.本文所使用的环境:
§ 环境配置 the previous part
§ HackSys Extreme Vulnerable Driver(HEVD) – prebuild version + the source code
§ DebugView (from SysInternals Suite)
§ Visual Studio 2012 (版本随意)
安装并测试HEVD首先,我将展示如何安装HEVD。我们将会配置Debugee和调试器,以查看调试字符串和HEVD的符号。我们也会用一些专用的漏洞来玩。
油管视频(我也很无奈啊~):
查看调试字符串HEVD和一些专门的行为利用了大量的信息作为调试字符串。我们可以从调试器(使用WinDbg)和被调试程序(使用调试器)来观察它们。
安装HEVD之前,我们将设着一些内容来查看驱动程序初始化期间打印的字符串。
在调试器上:为了获得kd标识,我们需要中断调试器的执行(WinDbg->Debug->Break)。然后,我们可以通过命令打印调试字符串:
ed nt!Kd_Default_Mask 8
之后,我们可以通过执行命令进一步运行调试器:
g
Warning: Enabling this slows down the Debugee. So, whenever possible, try to watch DebugStrings locally (on the Debugee only).
这个警告大概是说这个命令会减慢调试器的速度,所以优先选择在本地仅在被调试程序上观察调试字符。
在被调试程序上:
我们需要以管理员身份运行DebugView。然后我们从菜单中依次选择:
Capture->Capture Kernel
安装驱动程序首先,我们将在被调试方(靶机)上下载预构建包(驱动程序+exploit)安装并且进行测试。我们可以在GitHub上找到HackSysTem,https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/releases
这个包包含两个易受攻击的版本的驱动程序,我们将选一个比较脆皮的进行构建(
32位i386)
我们选择自启动。然后我们点击[RegisterService]成功后点击[StartService]
我们应该可以在调试方的WinDbg和靶机的WbgView上看到打印出的HEVD。
添加标识符HEVD的预编译包带有符号(sdb文件),我们还可以将其添加到调试器中。首先,我们通过发送一个中断信号来停止调试器,并查看所有已加载的模块。
我们可以设置一个过滤器来找到HEVD模块
lm m H*
我们可以看到它没有任何标识符,我们可以很容易的将其固定。首先,打开:
!sym_noisy
–用来打印所有和WinDbg引用路径的信息以找到标识符。然后试着重载这些标识符:
.reload
再试着再次提引用。你将看到路径,我们可以复制pdb文件。在将pdb文件移动到调试器机器上的适当位置之后,再次重载标识符。你可以试着打印HEVD的所有功能来进行测试。
x HEVD!*利用测试同一个包中还包含了一组专用的漏洞。我们可以通过执行一个适当的命令来运行它们。我们来尝试进行一些部署,并执行cmd.exe
池溢出利用部署:
如果利用成功,请求的应用程序将会被部署到更高的权限。
通过命令:
whoami
我们可以确定,它的权限升高。
与此同时,我们可以调试机上看到由expolit打印的调试字符串:
所有的攻击,除了双重取回,都应该在一个核心上运行良好。如果我们想要利用这个,则需要在调试器机器上启用两个核心。
警告:有些exp并不是100%可靠的,我们在部署它们之后可能会遇到系统崩溃。别担心,一切不以格盘为目的的崩溃信息都是纸老虎。
与驱动进行一场扎心的交流就像在用户层的情况下一样,在内核方面的开发利用中,从寻找点开始,我们可以为程序提供一个输入。然后,我们需要找到能够破坏执行的输入(与用户层相反——在内核层面上,崩溃将直接导致蓝屏!)最后,我们将尝试以一种控制脆弱程序执行的方式来构造输入。
为了与来自用户模式的驱动程序进行通信,我们将发送一些IOCTL-输入-输出控制。IOCTL允许我们从用户向驱动程序发送一些内容到缓冲区。这是我们可以尝试利用的点。
HEVD包含各种各样的漏洞的演示。每一个都可以使用不同的IOCTL触发,并被所提供的缓冲区所利用。有些(但不是全部)会导致系统在触发时崩溃。
查找设备名称和IOCTL
在我们尝试与设备连接之前,我们需要知道两件事:
驱动程序创建的设备(如果它不创建任何东西,我们将无法进行通信)
驱动程序接受的IOCTL(输入-输出控制)列表
HEVD是开源的,因此我们可以直接从源代码中读取所有必需的数据。在黑盒测试过程中,我们可能要通过逆向驱动程序来读取所需数据。
让我们来看看HEVD创建一个设备的代码片段。
上面提到了这个设备的名称。
现在,我们通过从IRP数组来找IOCTL列表
连接到IRP_MJ_DEVICE_CONTOL的函数将会分配IOCTL发送给驱动程序。所以,我们需要看一下这个函数。
这里有一个switch循环,调用一个特定的处理IOCTL的函数,我们可以对这些switch的case进行转换以获取我们所需要的IOCTL列表。常量的值在header中被忽视。
编写一个客户端应用程序
好的,我们现在已经搞到了所有需要用到的数据,我们可以用我们自己的程序与驱动程序进行通信。我们可以把它们放在头文件中,也就是:hevd_constants.h
#pragma once #include <windows.h> const char kDevName[] = "\\.\HackSy**tremeVulnerableDriver"; #define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_STACK_OVERFLOW_GS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_POOL_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_ALLOCATE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_USE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_FREE_UAF_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_ALLOCATE_FAKE_OBJECT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_TYPE_CONFUSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_INTEGER_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_NULL_POINTER_DEREFERENCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_UNINITIALIZED_HEAP_VARIABLE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_NEITHER, FILE_ANY_ACCESS) #define HACKSYS_EVD_IOCTL_DOUBLE_FETCH CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80D, METHOD_NEITHER, FILE_ANY_ACCESS)
IOCTL的数目是由一个标准的Windows头文件winioctl.h中定义的宏所创建的。
如果你引用了windows.h文件,上面的宏将被自动添加。现在,我们不需要操心特定常量的含义——我们只需要使用已定义的元素就可以了。
因此,我们准备编写一个简单的用户层应用程序,该应用程序将与驱动程序进行对话。首先,我们使用函数CreateFile打开设备。然后,我们可以使用DeviceIoControl.来发送IOCTL。
下面你可以看到一个小例子。这个应用程序将STACK_OVERFLOWIOCTL 发送给驱动程序: send_ioctl.cpp
#include <stdio.h> #include <windows.h> #define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS) const char kDevName[] = "\\.\HackSy**tremeVulnerableDriver"; HANDLE open_device(const char* device_name) { HANDLE device = CreateFileA(device_name, GENERIC_READ | GENERIC_WRITE, NULL, NULL, OPEN_EXISTING, NULL, NULL ); return device; } void close_device(HANDLE device) { CloseHandle(device); } BOOL send_ioctl(HANDLE device, DWORD ioctl_code) { //prepare input buffer: DWORD bufSize = 0x4; BYTE* inBuffer = (BYTE*) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufSize); //fill the buffer with some content: RtlFillMemory(inBuffer, bufSize, 'A'); DWORD size_returned = 0; BOOL is_ok = DeviceIoControl(device, ioctl_code, inBuffer, bufSize, NULL, //outBuffer -> None 0, //outBuffer size -> 0 &size_returned, NULL ); //release the input bufffer: HeapFree(GetProcessHeap(), 0, (LPVOID)inBuffer); return is_ok; } int main() { HANDLE dev = open_device(kDevName); if (dev == INVALID_HANDLE_VALUE) { printf("Failed! "); system("pause"); return -1; } send_ioctl(dev, HACKSYS_EVD_IOCTL_STACK_OVERFLOW); close_device(dev); system("pause"); return 0; }
尝试编译这个程序并将其部署到靶机上。启动DebugView观察由驱动程序打印的调试字符串。
你可能看到类似输出:
你懂得~
以下为作者福利:
练习,让我们一起搞波事情~
我在HEVD创建了一个小客户端,你可以在这看到源代码:
https://github.com/hasherezade/wke_exercises/tree/master/task1
编译后的32位二进制文件: here.
试着玩各种不同的IOCTL,直到你成功。因为Debugee在调试器的控制下运行,你不会出现蓝幕——相反,WinDbg会被触发。试着对每一个案例做一个简短的崩溃分析。从打印信息开始:
!analyze -v其他一些有用的命令:
k - stack trace kb - stack trace with parameters r - registers dd [address]- display data as DWORD starting from the address
要了解更多,请参阅“WinDbg”帮助文件:
.hh
在我们的示例应用程序中,用户缓冲区填满了“A”-ASCII 0×41
https://github.com/hasherezade/wke_exercises/blob/master/task1/src/main.cpp#L34
RtlFillMemory(inBuffer, bufSize, 'A');
油管示例:
Example #1
https://www.youtube.com/watch?v=lw7vMrkeTpY
Example #2
https://www.youtube.com/watch?v=YV0IqXEUf5s
请注意,触发相同的漏洞可能会给您带来不同的输出,这取决于崩溃的直接原因,这与溢出的大小、内存的当前布局等有关。