CVE-2017-6008浅析-HitmanPro内核池溢出漏洞(Win7)

来源:互联网 发布:批量写淘宝宝贝卖点 编辑:程序博客网 时间:2024/05/21 09:41

介绍

本文将重点放在我们在HitmanPro独立扫描版3.7.15-build281发现的一个漏洞(CVE-2017-6008)。这个工具是HitmanPro.Alert解决方案的一部分,并以SophosClean.exe的形式集成在Sophos的解决方案中。该漏洞已经于2017年2月向Sophos报告。2017年5月发布的版本3.7.20-build286修补了该漏洞。我们使用Ioctlfuzzer发现了第一次崩溃。Ioctlfuzzer是由对I/O请求包(IRP)进行模糊测试的一个伟大而又简单的工具。对于接收的每个IRP,它都会在发送原始的IRP之前地生成几个畸形的IRP。第一次崩溃在扫描的开始发生,在Initialization阶段伴随着BAD_POOL_HEADER错误代码。在继续进行之前,我强烈推荐读者了解更多有关IOCTL和Windows上的IRP的知识。MSDN文档提供了很多信息,你必须知道它们才能完全理解这篇文章。这篇文章将集中于x64架构,因为它比X32架构更难利用。

分析

分析崩溃的数据

首先,我们需要知道BAD_POOL_HEADER错误代码的含义。池是内核中动态分配的常见场所。此代码意味着,在处理池头时有一个问题。池头是位于块开头的一个提供有关块信息的结构。
这里写图片描述
池头可能已经损坏,引发了崩溃。然后使用调试器转储并通过fuzzer生成的日志,我们很快找到存在问题的IRP。

    IOCTL Code: 0x0022e100    Outbuff: 0x03ffe1f4, Outsize: 0x00001050    Inbuff : 0x03ffd988, Insize: 0x00000200    //Device/Hitman Pro 37 [/??/C:/Windows/system32/drivers/hitmanpro37.sys]
  • C:/Windows/system32/drivers/hitmanpro37.sys:即处理IRP的驱动程序。因为池损坏造成崩溃,这个驱动肯定与崩溃有关。
  • IOCTL Code:0x0022e100:该IOCTL代码提供了大量的信息,我稍后会解释。我们也可以通过逆向用它来检索这个IRP是在上述驱动的哪个部分被处理的。
  • Outsize/Insize:这些尺寸用来在池中分配一些缓冲区,并且可能与池损坏有关。
    如果参考MSDN文档,我们可以从IOCTL代码得到下列信息。

    DeviceType = 0x22

    Access = 0x3

    Function = 0x840

    Method = 0x0

Method 0x0 = METHOD_BUFFERED
对于METHOD_BUFFERED这种传输类型,IRP提供一个指针,指向位于Irp->AssociatedIrp.SystemBuffer的缓冲区。该缓冲区代表调用DeviceIoControl和IoBuildDeviceIoControlRequest时的输入缓冲区和输出缓冲区。该驱动使用这个缓冲区传输数据进出。
对于输入数据,由驱动中的IO_STACK_LOCATION结构中的Parameters.DeviceIoControl.InputBufferLength指定缓冲区大小。
对于输出数据,由驱动中的IO_STACK_LOCATION结构中的Parameters.DeviceIoControl.OutputBufferLength指定缓冲区大小。
系统为单个输入/输出缓冲区分配的空间大小是两个值中较大的一个。
最后,我们逆向HitmanPro.exe可执行文件,以找到正常情况下IOCTL是如何发送的。使用IOCTL代码和IDA的搜索工具,我们快速检索函数。
这里写图片描述
传递给DeviceIoControl的Outsize和Insize与崩溃的数据相匹配。通过这种方式,由IRP管理器分配的SystemBuffer正常情况下应该至少长0x1050字节。

逆向驱动

现在,我们有关于崩溃的一大堆的信息,是时候来逆向驱动hitmanpro37.sys并看看我们的IOCTL的句柄了。我们先来看看32位的版本。首先,我们必须找到派遣IRP给予其IOCTL代码的函数。它通常是含有一些switch跳转的一个很大的函数。由于此驱动程序并不大,我们迅速找到分配器。
这里写图片描述
跟随跳转,我们终于达到这个处理存在漏洞的IOCTL的函数。由IRP提供的SystemBuffer首先用作函数IoGetDeviceObjectPointer的ObjectName参数。
这里写图片描述
然后..
这里写图片描述
还记得用于此IOCTL方法么?METHOD_BUFFERED?
系统为单个输入/输出缓冲区分配的空间大小是两个值中较大的一个。
这意味着我们完全控制SystemBuffer的大小,而驱动程序使用一个硬编码的值0x1050调用memset。如果的SystemBuffer小于0x1050,调用memset会破坏池,并引发崩溃。这里的漏洞被称为内核池溢出。话虽这么说,但是我们没有发现任何办法来控制向这个缓冲区写入。它被设置为0,然后被DeviceObject结构中的地址和名称填充,我们没有管理员权限是控制不了的。所以,这个漏洞将只能导致操作系统崩溃。分配给这个漏洞的编号是CVE-2017-6007。

扭转

我们不能在这里放弃,所以我们决定逆向更多的处理程序。我们随机挑了个处理程序,它实际上非常有趣。
这里写图片描述
SystemBuffer(我们的输入)在子函数中使用,如果子函数返回正确的值,就会使用memcpy复制一些东西到systemBuffer。该函数的控制代码是0x00222000。

DeviceType = 0x22Access = 0x0Function = 0x0Method = 0x0

用的是同样的方法:METHOD_BUFFERED。如果我们幸运的话,在这里有可能是同一类型的漏洞。但是这部分的驱动程序很奇怪。我们没有在HitmanPro可执行文件中发现有任何函数使用控制代码0x00222000构建IRP。因此,正如所料,在这部分驱动程序设置断点它永远不会触发!我们无法确定这个函数的功能,但我们发现了一个漏洞,这就足够了。处理驱动,把它写成伪代码。
这里写图片描述
这里写图片描述
该驱动程序将使用SystemBuffer提供的句柄获得FILE_OBJECT。如果FILE_OBJECT不忙,它调用ObQueryNameString获得由FILE_OBJECT指向的文件的名称,并把它放在一个临时缓冲区。然后,它从临时缓冲区复制到名称到SystemBuffer。
该驱动程序调用memcpy的参数如下。
dest=SystemBuffer,我们可以控制它的大小
src=我们给句柄的文件名,我们控制写入和大小
n=src缓冲区的大小
唯一的限制是ObQueryNameString函数:此函数是受保护的,如果对于目标来说源太大则不会复制任何东西。由于目标是一个硬编码为0x400大小的缓冲区,所以我们无法给出比0x400大的一个文件名。当然,0x400已经足够利用缓冲区溢出了。
(这里作者的驱动是以32位为例的,经过分析调试可以发现64位中代码是类似的)

EXP

简介

由于该漏洞是一个内核池溢出,有很多我们可以使用的攻击手法。关于这个问题没有比Tarjei Mandt的这篇文章更好的了。如果你想充分了解接下来会发生什么必须阅读这篇文章。我们在这里使用的攻击方式是进程配额指针覆盖。我们选择这种攻击,因为它是最优雅的手法之一,可以同时在x32或x64体系结构上实现。在这种攻击中,我们必须覆盖下一大块的进程指针。
这里写图片描述
在池头最后4个字节有一个指向一个EPROCESS结构体的指针。当池块被释放时,如果限额位在PoolType(见_POOL_HEADER结构体)中被设置,该指针将被用来减少一些与EPROCESS对象相关的值。

  • 该对象的引用计数(一个进程是一个对象)
  • QuotaBlock字段指向的一个值

但是,因为在减少之前有一些检查,我们不能直接使用对象的ReferenceCount,但我们可以创建一个假的EPROCESS结构体,并在QuotaBlock中放置一个指针递减任意值(即使是在内核地址)!
这里写图片描述

溢出

为了使用进程配额指针覆盖攻击,我们需要用我们的溢出覆盖两个东西。

  • 下个块的PoolType,因为我们想要确保Quota位被设置
  • 下个块的进程指针,使用一个指向伪造的EPROCESS结构体的指针替换它

既然我们已经到达下个块的进程指针,我们无论如何都要覆盖下个块的整个池头。但是,我们不能只是把在池头填充一些随机数据,否则将触发BSOD。我们必须确保以下字段是正确的。

  • BlockSize
  • PreviousSize
  • PoolType

实现这一目标的唯一途径就是要知道我们要溢出到底是哪一个块,这可以使用池喷射实现。我不会在这里仔细解释怎么进行池喷射,因为已经在这个博客上发表过关于它的文章。
溢出前:
这里写图片描述
溢出后:
这里写图片描述

有效载荷

好了,我们可以递减任何地址的任何值。下一步是什么?我们发现了Cesar Cerrudo写的一篇很棒的文章,它介绍了几种提权技术,其中的第二个技巧很有趣。在TOKEN结构体中有这么一个叫做Privileges的部分。
这里写图片描述
此字段是包含几个位掩码的结构体。Enabled掩码定义与之关联的进程可以执行哪些操作。默认情况下,该位掩码设置为0x80000000,也就是具有SeChangeNotifyPrivilege权限。现在考虑从该位掩码中减去1使其成为0x7FFFFFFF的,这是一个很大的权限!MSDN文档提供了在此位掩码可用的权限列表。但是,我们没有_TOKEN结构体的地址,我们也不应该有因为它是一个内核地址。幸运的是,我们可以通过使用众所周知的NtQuerySystemInformation得到任何对象的内核地址。我们可以通过调用函数OpenProcessToken()得到我们的token句柄。如果您想了解更多关于NtQuerySystemInformation()和内核地址一般的泄漏方法,你应该阅读这篇文章。我们获得SeDebugPrivilege权限之后触发漏洞,这让我们获得对系统上所有进程的完全控制,但你可以得到任何你想要的权限。SeDebugPrivilege权限允许我们在系统进程创建一个线程,并生成一个system shell。

结论

请注意,此EXP无法在Windows 8或更高的版本工作,因为微软在缓解内核漏洞上的表现非常出色。但是这并不意味着它不可能被利用。你可以在我的github上找到源代码。
原文地址:http://trackwatch.com/kernel-pool-overflow-exploitation-in-real-world-windows-7/

原创粉丝点击