内核漏洞学习—熟悉HEVD

一直以来内核漏洞安全给很多人的印象就是:难,枯燥。但是内核安全是否掌握是衡量一个系统安全工程师水平的标准之一,也是安全从业人员都应该掌握的基本功。本文通过详细的实例带领读者走进内核安全的大门。难度系数:四颗星

原文地址:https://hshrzd.wordpress.com/2017/06/05/starting-with-windows-kernel-exploitation-part-2/

由prison翻译整理,首发i春秋。

 

本文默认读者已经配置好了基本实验环境,因为环境配置网络上有大量详细资源,在此编者不再单独成文介绍环境。此文章供参考:https://hshrzd.wordpress.com/201 … setting-up-the-lab/

1.本文所使用的环境:

§  环境配置 the previous part

§  HackSys Extreme Vulnerable Driver(HEVD) – prebuild version + the source code

§  OSR Driver Loader

§  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

1.png

安装驱动程序首先,我们将在被调试方(靶机)上下载预构建包(驱动程序+exploit)安装并且进行测试。我们可以在GitHub上找到HackSysTem,https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/releases

这个包包含两个易受攻击的版本的驱动程序,我们将选一个比较脆皮的进行构建(

32位i386)

2.png

我们选择自启动。然后我们点击[RegisterService]成功后点击[StartService]

我们应该可以在调试方的WinDbg和靶机的WbgView上看到打印出的HEVD

添加标识符HEVD的预编译包带有符号(sdb文件),我们还可以将其添加到调试器中。首先,我们通过发送一个中断信号来停止调试器,并查看所有已加载的模块。

我们可以设置一个过滤器来找到HEVD模块

lm m H*

我们可以看到它没有任何标识符,我们可以很容易的将其固定。首先,打开:

!sym_noisy

–用来打印所有和WinDbg引用路径的信息以找到标识符。然后试着重载这些标识符:

.reload

再试着再次提引用。你将看到路径,我们可以复制pdb文件。在将pdb文件移动到调试器机器上的适当位置之后,再次重载标识符。你可以试着打印HEVD的所有功能来进行测试。

x HEVD!*利用测试同一个包中还包含了一组专用的漏洞。我们可以通过执行一个适当的命令来运行它们。我们来尝试进行一些部署,并执行cmd.exe

3.png

池溢出利用部署:

4.png

如果利用成功,请求的应用程序将会被部署到更高的权限。

通过命令:

whoami

我们可以确定,它的权限升高。

5.png

与此同时,我们可以调试机上看到由expolit打印的调试字符串:

6.png

所有的攻击,除了双重取回,都应该在一个核心上运行良好。如果我们想要利用这个,则需要在调试器机器上启用两个核心。

警告:有些exp并不是100%可靠的,我们在部署它们之后可能会遇到系统崩溃。别担心,一切不以格盘为目的的崩溃信息都是纸老虎。

与驱动进行一场扎心的交流就像在用户层的情况下一样,在内核方面的开发利用中,从寻找点开始,我们可以为程序提供一个输入。然后,我们需要找到能够破坏执行的输入(与用户层相反——在内核层面上,崩溃将直接导致蓝屏!)最后,我们将尝试以一种控制脆弱程序执行的方式来构造输入。

为了与来自用户模式的驱动程序进行通信,我们将发送一些IOCTL-输入-输出控制。IOCTL允许我们从用户向驱动程序发送一些内容到缓冲区。这是我们可以尝试利用的点。

HEVD包含各种各样的漏洞的演示。每一个都可以使用不同的IOCTL触发,并被所提供的缓冲区所利用。有些(但不是全部)会导致系统在触发时崩溃。

查找设备名称和IOCTL

在我们尝试与设备连接之前,我们需要知道两件事:

驱动程序创建的设备(如果它不创建任何东西,我们将无法进行通信)

驱动程序接受的IOCTL(输入-输出控制)列表

HEVD是开源的,因此我们可以直接从源代码中读取所有必需的数据。在黑盒测试过程中,我们可能要通过逆向驱动程序来读取所需数据。

让我们来看看HEVD创建一个设备的代码片段。

https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/blob/master/Driver/HackSy**tremeVulnerableDriver.c#L79

7.png

上面提到了这个设备的名称。

现在,我们通过从IRP数组来找IOCTL列表

连接到IRP_MJ_DEVICE_CONTOL的函数将会分配IOCTL发送给驱动程序。所以,我们需要看一下这个函数。

https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/blob/master/Driver/HackSy**tremeVulnerableDriver.c#L193

9.png

这里有一个switch循环,调用一个特定的处理IOCTL的函数,我们可以对这些switch的case进行转换以获取我们所需要的IOCTL列表。常量的值在header中被忽视。

https://github.com/hacksysteam/HackSy**tremeVulnerableDriver/blob/master/Driver/HackSy**tremeVulnerableDriver.h#L57

10.png

编写一个客户端应用程序

好的,我们现在已经搞到了所有需要用到的数据,我们可以用我们自己的程序与驱动程序进行通信。我们可以把它们放在头文件中,也就是: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中定义的宏所创建的。

13.png

如果你引用了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!\n");
        system("pause");
        return -1;
    }
 
    send_ioctl(dev, HACKSYS_EVD_IOCTL_STACK_OVERFLOW);
 
    close_device(dev);
    system("pause");
    return 0;
}

尝试编译这个程序并将其部署到靶机上。启动DebugView观察由驱动程序打印的调试字符串。

你可能看到类似输出:

12.png

你懂得~

以下为作者福利:

练习,让我们一起搞波事情~

我在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

请注意,触发相同的漏洞可能会给您带来不同的输出,这取决于崩溃的直接原因,这与溢出的大小、内存的当前布局等有关。

posted @ 2017-08-16 18:32  i春秋  阅读(2136)  评论(0编辑  收藏  举报