0x00 前言
上一篇研究了一下基础的内核整数溢出漏洞,虽然漏洞利用还有些问题,但是原理上还是比较清楚的,后续有时间继续搞一哈,时间有限,我们继续下一个内核漏洞:未初始化栈变量。
0x01 漏洞原理
顾名思义,即内核中函数栈局部变量未初始化。
分析
1 | typedef struct _UNINITIALIZED_MEMORY_STACK |
很明显可以看到,不安全的版本中,结构体变量UninitializedMemory未进行初始化。栈上的局部变量,未初始化时,则会拥有前调用函数的随机垃圾值。
如果我们传入一个正确MagicValue,它会填充变量UninitializedMemory以及回调成员。如果传递的值不正确那么就不会填充。后面看到Callback检查,但是并没有什么用。
驱动程序拖进IDA看下:
此时数组偏移下标为(0xB)*4
var_C变量为控制码-0x222003,那么IO控制码为0x222003+(0xB)*4 = 0x22202f
找到漏洞函数,我们看到:
查看代码逻辑,比较成功会命中绿色块,在这里变量被填充了适当的值,此后在红色块中当回调函数被调用时也不会出错。
然而如果我们比较失败了,跳过了绿色快,继续下行调用函数的时候,我们栈成员变量无初始化,虽然通过了CallBack检查,但是调用的将是一个内核栈上的垃圾值!
这个值是不固定的,如果你尝试重现,很可能会在Windbg中看到不同的值。在虚拟机BSOD之前,让我们快速的看一下该变量距离当前栈起始位置有多远。
计算方式:`0x8a15ced0 - 0x8a15c9cc = 0x504 (1284 bytes)`
让我们通过回溯流程并对BSOD进行检查。
可以看到,回调指针指向了一个垃圾地址,执行出错。
0x02 漏洞利用:
覆盖内核栈上的整型指针为我们shellcode的地址是最终目标。
参考j00ru的文章。
其中提到了内核栈喷射(Kernel Stack-Spraying)的概念。
在Windows系统中,内核栈不同于用户栈,其为一片共用空间,使用常规的的喷射方法是有很大风险的,比如覆盖到其他关键的地址。而原作者提到的用NtMapUserPhysicalPages的方法,使我们在用户态提权的时候完成对内核栈的操作。
1 | NTSTATUS |
有一个未文档化的函数,NtMapUserPhysicalPages,我们不关心它用于干什么,但它的一部分功能是拷贝输入的字节到内核栈上的一个本地缓冲区。最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节=>4096字节)。
seebug的文章中可以看到NtMapUserPhysicalPages的实现:
1 | NTSTATUS __stdcall NtMapUserPhysicalPages(PVOID BaseAddress, PULONG NumberOfPages, PULONG PageFrameNumbers) |
调用过程
NtMapUserPhysicalPages -> MiCaptureUlongPtrArray -> memcpy。
1 | int __fastcall MiCaptureUlongPtrArray(int a1, unsigned int a2, void *a3)//4*a1为长度 a2为构造的用户缓冲区 a3为内核地址 |
那么在用户态下构造好栈缓冲区,填充好payload地址。在HacksTeam的利用代码中,可以看到这个过程。
整理一下:
exp工作流将如下:(1)将shellcode放在内存任意位置,(2)使用指向shellcode的指针喷射内核栈,(3)触发未初始化变量漏洞 (4)调用执行。
测试一哈,成功提权。

0x03 漏洞反思
安全版本中提到的变量初始化的好习惯可以很好的帮助我们规避这类漏洞。
1 |
|
0x04 链接
j00ru关于NtMapUserPhysicalPages和内核堆栈喷涂技术:https://j00ru.vexillium.org/2011/05/windows-kernel-stack-spraying-techniques/
fuzzsecurity:https://www.fuzzysecurity.com/tutorials/expDev/17.html