Windows内核漏洞利用

xiaohui 漏洞 2019年11月28日发布
Favorite收藏

导语:当堆栈上存在的缓冲区获取的数据超出其存储容量时(例如,在一个16字节缓冲区中复制20个字节,就可以是字符数组或类似对象),其余数据将写入附近位置,从而有效覆盖或破坏堆栈。

堆栈缓冲区溢出

在第一部分中,我们会从HackSysExtremeVulnerableDriver中的普通堆栈缓冲区溢出开始。

当堆栈上存在的缓冲区获取的数据超出其存储容量时(例如,在一个16字节缓冲区中复制20个字节,就可以是字符数组或类似对象),其余数据将写入附近位置,从而有效覆盖或破坏堆栈。

其核心思想是控制此溢出,这样我们可以覆盖堆栈上保存的返回地址,并且在执行当前(易受攻击的)函数后,它将返回我们的覆盖值,其中包含我们的shellcode。

注意:执行完shellcode后,代码执行必须返回到应用程序,在本例中是内核,否则将破坏应用程序。通常,应用程序崩溃了,我们可以重新启动它,但是如果内核内存损坏,内核会发出内核恐慌,并且将显示“蓝屏死机”,这是我们最不希望发生的事情。

为了解决这个问题,我们需要恢复执行路径,以便在执行完shellcode之后,它返回到执行易受攻击的函数后应该返回的函数。

易受攻击性

现在我们已经清除了它,让我们看一下易受攻击的代码(位于StackOverflow.c中的函数TriggerStackOverflow)。最初,该函数创建可容纳512个成员元素的ULONG数组(在common.h头文件中,BufferSize设置为512)。

1.png

易受攻击性的函数

然后,内核检查缓冲区是否驻留在用户区域中,然后在非页面缓冲池中为其分配内存。

一旦完成,内核便将数据从用户模式缓冲区复制到内核模式KernelBuffer,该模式实质上是一个ULONGs数组。

2.png

溢出

注意RtlCopyMemory的第三个参数,它本质上是memcpy,Size参数是用户模式缓冲区的大小,而不是内核模式缓冲区的大小,这正是缓冲区溢出发生的地方。

漏洞验证

现在,要验证错误是否真正存在,我们将编写一个函数,该函数调用函数StackOverflowIoctlHandler的IOCTL。 IOCTL代码在Exploit / common.h文件中给出。

注意:我们可以从编译的驱动程序本身获取IOCTL代码,既然我们有自己的优势,所以为什么不使用它?

什么是IOCTL代码?

I/O控制代码(IOCTL)用于用户模式应用程序和驱动程序之间的通信,或用于堆栈中驱动程序内部的通信,I/O控制代码是使用IRP发送的。

基本上,如果驱动程序具有与之关联的IOCTL代码,则可以直接在该驱动程序中调用内核函数。要使用IOCTL代码,我们使用DeviceIoControl函数,函数可在此处找到。

DeviceIoControl函数的原型为:

3.png

DeviceIoControl的原型

我用C++编写了一个函数,该函数调用DeviceIoControl来调用StackOverflowIoctlHandler,后者依次调用TriggerStackOverflow,这是易受攻击的函数。

由于我们知道缓冲区有512个ULONG,因此可以肯定的是,此后,我们将添加Metasploit框架中pattern_create.rb生成的100字节模式。

最后,将此缓冲区发送到HEVD,看看会发生什么?

注意:此函数在标头文件StackOverflow.h中,主函数调用它,你可以在我的代码库中找到整个代码。

4.png

利用堆栈溢出的POC

5.png

利用堆栈溢出的POC

在Win7设备上编译并执行二进制文件后,我们可以在WinDbg中获得它:

6.png

在WinDbg中崩溃

我们可以看到有一个访问冲突,EIP指向31624130。

在此模式上使用Metasploit的pattern_offset.rb之后,我们找到它的偏移量32,让我们继续进行开发。

利用溢出

现在剩下的就是使用HEVD中提供的TokenStealingPayloadWin7 shellcode覆盖保存的返回地址,然后完成。

注意:你可能需要稍微修改shellcode,以免崩溃。

获取Shell

首先验证我们是否是普通用户:

7.png

普通用户

可以看出,我只是一个普通的用户。运行漏洞利用程序后,我成为了NT权限/系统。

8.png

NT Authority/SYSTEM Shell

类型混淆

在第二部分中,我们将绕过常见的内存损坏漏洞(在我们正在利用的驱动程序中,内存损坏漏洞占大多数)。因为利用起来很容易,我还要使其成为本文的第一部分。

什么是类型混淆?

类型混淆是一个漏洞,其中应用程序不验证对象的类型(函数,数据类型等),然后按预期方式对其进行处理,但传递的对象是其他对象。

漏洞

现在我们已经清除它,让我们看一下易受攻击的代码(位于TypeConfusion.c中的函数TriggerTypeConfusion)。

内核首先检查缓冲区是否驻留在用户域中,然后在非页面缓冲池中为其分配内存。完成此操作后,内核将用户模式缓冲区中的ObjectID分配给内核模式缓冲区,并对对象类型执行相同的操作。

9.png

分配ObjectID和ObjectType

完成之后,内核在对象(内核模式而不是用户模式)上调用TypeConfusionInitializer函数。

10.png

在对象上调用TypeConfusionInitializer

让我们来看看这个函数:

11.png

函数类型ConfusionObjectInitializer

该函数接收对象并调用对象内部存在的函数指针,下面,让我们看一下TypeConfusion.h标头文件中存在的KERNEL_TYPE_CONFUSION_OBJECT的结构(本质上是一个结构)。该标头文件包含用户模式对象以及内核模式对象的定义,这使得利用这个漏洞比利用堆栈溢出更容易。

12.png

对象原型

首先,让我们看看用户模式对象包含什么。用户模式对象是一个包含2个成员的结构:

1.对象ID;

2.对象类型。

对于内核模式对象,它也是一个包含2个成员的结构:

1.对象ID;

2.第二个成员是UNION,它本身包含:

2.1对象类型;

2.2回调(函数指针);

现在,如果你还记得,一个UNION一次可以容纳一个成员,在这里它可以是Object Type或指向由TypeConfusionInitializer函数调用的函数的指针。

当函数TriggerTypeConfusion函数无法验证第二个成员是ObjectType还是Callback时,就会发生混淆。

利用混淆情况

要利用混淆情况,我们所要做的就是发送一个结构,该结构的第二个成员是我们要从内核领域调用的函数的地址。

在漏洞已经被利用的情况下,它将成为我们的令牌窃取Shellcode的地址,并替换我们进程的令牌,因此当创建一个新进程时,将使用该令牌创建它。

但是有一个问题,HEVD附带的shellcode(TokenStealingPayloadWin7无法正常工作并导致设备崩溃)。

修改shellcode

由于函数TypeConfusionInitializer会像调用函数一样调用回调指针,因此我们需要设置函数序言和结尾,并将ret 8更改为ret。

注意:我会将shellcode函数编译为裸函数,但如果你不这样做,则可以直接使用提供的shellcode。我只是不希望编译器将额外的代码添加到我的shellcode中。

你可以点此,找到漏洞利用代码。

获得Shell

首先验证我们是否是一个普通用户。

13.png

普通用户

可以看出,我只是一个普通用户。运行漏洞利用程序后,我会拥有NT权限/系统

14.png

通过利用Type Confusion开发SYSTEM Shell

整数溢

在这一部分中,我们将利用HackSysExtremeVulnerableDriver中的整数溢出。

什么是整数溢出?

对于那些不知道整数溢出的人,他们可能会想到整数如何溢出?实际的整数不会溢出。 CPU会将整数存储在固定大小的内存分配中。如果你熟悉C / C++编程语言或类似语言,则可能会想起数据类型以及每种数据类型具有特定的固定大小。

在大多数设备和操作系统上,char是1个字节,int是4个字节长。这意味着char数据类型可以容纳8位大小的值,范围从0到255,或者在有符号值的情况下从-128到127。整数也是如此,在int大小为4字节的设备上,它可以保存0到232 – 1之间的值(无符号值)。

现在,让我们考虑使用一个无符号整数,其最大值可以是232 – 1或0xFFFFFFFF。如果加1时会发生什么?由于所有的32位都设置为1,因此加1将使它成为33位的值,但是由于存储区只能容纳32位,因此将这32位设置为0。

在执行操作时,CPU通常将数字加载到32位寄存器中(这里说的是x86),添加1将设置Carry标志,寄存器保存值0,因为所有32位现在都是0。

现在,如果进行大小检查,则该值是否大于(例如10),则检查将失败,但是如果没有大小限制,则比较操作将返回true。

为了更详细地了解它,让我们看一下该漏洞,看看如何利用HEVD中的整数溢出漏洞来获得在Windows内核中的执行代码。

易受攻击性

现在我们已经清除了它,让我们看一下易受攻击的代码(函数IntegerOverflow.c中的TriggerIntegerOverflow)。

最初,该函数创建可容纳512个成员元素的ULONG数组(在common.h头文件中,BufferSize设置为512)。

15.png

IntegerOverflow.c中的漏洞函数

然后,内核检查缓冲区是否驻留在用户区域中,然后为我们打印一些信息,这对整数溢出很有帮助。

完成此操作后,内核将检查数据大小(以及终止符的大小,终止符为4字节)是否大于KernelBuffer的大小。如果是这,则它退出时不会在kernel-land缓冲区中复制user-land缓冲区。

16.png

大小检查

但是,如果不是这种情况,那么它将继续进行,然后将数据复制到内核缓冲区。

这里要注意的另一件事是,如果它在用户区域缓冲区中遇到BufferTerminator,它将停止复制并继续前进。因此,我们需要将BufferTerminator放在用户模式缓冲区的末尾。

17.png

将用户模式数据复制到内核模式函数堆栈

溢出

IntegerOverflow.c的第100行中的问题在于,如果我们将size参数设置为0xFFFFFFFC,然后添加BufferTerminator的大小(为4个字节),则有效大小为– 0xFFFFFFFC + 4 = 0x00000000,小于KernelBuffer的大小。因此,我们通过了数据大小检查,然后将缓冲区复制到内核模式。

漏洞验证

现在,为了验证这一点,我们将缓冲区发送到HEVD,但是要传递0xFFFFFFFC作为缓冲区的大小。现在,我们将不会放置巨大的缓冲区并使内核崩溃,而只是发送一个小缓冲区并进行确认。

18.png

触发整数溢出的PoC

因为我们知道缓冲区有512个ULONG,所以我们将发送此数据,并查看内核会做什么?

注意:这里的重点是DeviceIoControl的第4个参数,而不是实际的数据。

最后,将此缓冲区发送到HEVD,看看会发生什么。

19.png

成功触发整数溢出漏洞

如你在图片中看到的,UserBuffer Size显示为0xFFFFFFFC,但我们仍然设法绕过了大小有效性检查并触发了整数溢出。 

我们确认了通过放置0xFFFFFFFC,可以绕过检查大小,现在剩下的就是在UserBuffer之后放置一个模式(唯一的模式),然后在其后放置终止符以查找保存的返回指针覆盖。

如果你不知道如何执行此操作,请阅读本文的第1部分。

利用溢出漏洞

剩下的就是使用HEVD中提供的TokenStealingPayloadWin7 shellcode覆盖保存的返回地址,然后完成就可以了。

注意:你可能需要稍微修改shellcode,以免崩溃。

获取shell

首先验证我们是否是普通用户:

20.png

普通用户

可以看出,我只是普通用户。

运行漏洞利用程序后,我成为了NT权限/系统。

21.png

成功利用整数溢出漏洞

你可以在我的代码库中,找到整个代码。

本文翻译自:https://pwnrip.com/windows-kernel-exploitation-part-1-stack-buffer-overflows/ https://pwnrip.com/windows-kernel-exploitation-part-2-type-confusion/ https://pwnrip.com/windows-kernel-exploitation-part-3-integer-overflow/如若转载,请注明原文地址: https://www.4hou.com/vulnerable/21736.html
点赞 5
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论