调用 NtUserXXX 引发系统 BSOD 的问题分析

这篇文章通过一次在 Windows XP 和 Windows 7 操作系统内核中分别调用同一个 NtUserXxx 系统调用产生不同现象的问题,对其做了简单分析。

最近在驱动中需要实现在一些 HOOK 处理函数中调用如 NtUserBuildHwndList 这样的 API 对目标样本进程的窗口状态(是否存在窗口等)进行判定。NtUserBuildHwndList 是用来根据线程 ID 生成与线程信息结构体 tagTHREADINFO 关联的 tagDESKTOP 桌面对象中存在的窗口对象句柄列表的 USER 系统调用,其函数声明如下:


NTSTATUS
NtUserBuildHwndList (INHDESKhdesk,INHWND hwndNext,INBOOL fEnumChildren,INDWORDidThread,INUINT cHwndMax,OUT HWND*phwndFirst,OUT PUINTpcHwndNeeded); 

实现代码在 Windows 7 下一切正常,但在 Windows XP 中的部分进程上下文中调用时会产生的偶发 BSOD 异常。为了解决该问题,通过内核调试进行分析。

分析

挂上 WinDBG 内核调试模式启动 Windows XP 的虚拟机镜像,加载驱动并执行样本进程。幸运的是很快触发预期的异常。


Access violation - code c0000005 (!!! second chance !!!)
win32k!InternalBuildHwndList+0x1a:
bf835e26 8b402cmov eax,dword ptr [eax+2Ch]
kd> dc eax+2Ch l 1
0000002c???????? ????
kd> r eax
eax=00000000
kd> kv
ChildEBP RetAddrArgs to Child
ee609c04 bf835d37 e12dc350 bc6bc8c8 0000000a win32k!InternalBuildHwndList+0x1a (FPO: [Non-Fpo])
ee609c1c bf835fa7 bc6bc8c8 0000000a e2610870 win32k!BuildHwndList+0x4f (FPO: [Non-Fpo])
ee609c60 ede0b2aa 00000000 00000000 00000000 win32k!NtUserBuildHwndList+0xd8 (FPO: [Non-Fpo])
ee609ca8 ede0b3f3 85e45da0 862845a0 c0000001 MyDriver!MyCallOfNtUserBuildHwndList+0x10a (FPO: [Non-Fpo]) 

根据信息显示,是在 win32k!InternalBuildHwndList 函数中触发了异常。根据栈回溯可知,在我们的驱动模块调用 win32k!NtUserBuildHwndList 例程之后,实际调用 win32k!BuildHwndList 函数,随后进入 win32k!InternalBuildHwndList 例程中。最终在 InternalBuildHwndList 中发生了异常。


win32k!InternalBuildHwndList:
bf835e10 8bffmov edi,edi
bf835e12 55pushebp
bf835e13 8becmov ebp,esp
bf835e15 56pushesi
bf835e16 57pushedi
bf835e17 8b7d0cmov edi,dword ptr [ebp+0Ch]
bf835e1a 85fftestedi,edi
bf835e1c 74e8jewin32k!InternalBuildHwndList+0x94 (bf835e06)
bf835e1e 8b7508mov esi,dword ptr [ebp+8]
bf835e21 a118ae9abfmov eax,dword ptr [win32k!gptiCurrent (bf9aae18)]
bf835e26 8b402cmov eax,dword ptr [eax+2Ch] <- ACCESS VIOLATION, eax=0x00000000
bf835e29 8b8894010000mov ecx,dword ptr [eax+194h]
bf835e2f 8b460cmov eax,dword ptr [esi+0Ch]

win32k!BuildHwndList:
bf835d08 8bffmov edi,edi
bf835d0a 55pushebp
bf835d0b 8becmov ebp,esp
bf835d0d a174949abfmov eax,dword ptr [win32k!pbwlCache (bf9a9474)]
bf835d12 85c0testeax,eax
bf835d14 74cdjewin32k!BuildHwndList+0x17 (bf835ce3)
bf835d16 832574949abf00and dword ptr [win32k!pbwlCache (bf9a9474)],0
bf835d1d 53pushebx
bf835d1e 8b5d0cmov ebx,dword ptr [ebp+0Ch]
bf835d21 53pushebx
bf835d22 ff7508pushdword ptr [ebp+8]
bf835d25 8d4810lea ecx,[eax+10h]
bf835d28 894804mov dword ptr [eax+4],ecx
bf835d2b 8b4d10mov ecx,dword ptr [ebp+10h]
bf835d2e 50pusheax
bf835d2f 89480cmov dword ptr [eax+0Ch],ecx
bf835d32 e8d9000000callwin32k!InternalBuildHwndList (bf835e10) <- CALL InternalBuildHwndList
bf835d37 8b4804mov ecx,dword ptr [eax+4]
bf835d3a 3b4808cmp ecx,dword ptr [eax+8]

win32k!NtUserBuildHwndList:
bf835f21 6a14push14h
bf835f23 68e8d798bfpushoffset win32k!`string'+0x550 (bf98d7e8)
bf835f28 e8dbacfcffcallwin32k!_SEH_prolog (bf800c08)
bf835f2d 6a02push2
bf835f2f 5fpop edi
bf835f30 e825acfcffcallwin32k!EnterCrit (bf800b5a)
bf835f35 a158aa9abfmov eax,dword ptr [win32k!gpsi (bf9aaa58)]
bf835f3a f6400208testbyte ptr [eax+2],8
bf835f3e 0f8547ffffffjne win32k!NtUserBuildHwndList+0x1f (bf835e8b)
bf835f44 8b4d0cmov ecx,dword ptr [ebp+0Ch]
bf835f47 33dbxor ebx,ebx
bf835f49 3bcbcmp ecx,ebx
bf835f4b 0f8542ffffffjne win32k!NtUserBuildHwndList+0x2b (bf835e93)
bf835f51 33c0xor eax,eax
bf835f53 395d14cmp dword ptr [ebp+14h],ebx
bf835f56 0f84e0000000jewin32k!NtUserBuildHwndList+0x69 (bf83603c)
bf835f5c ff7514pushdword ptr [ebp+14h]
bf835f5f e89439feffcallwin32k!PtiFromThreadId (bf8198f8)
bf835f64 8bf0mov esi,eax
bf835f66 3bf3cmp esi,ebx
bf835f68 0f8423010000jewin32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f6e 8b463cmov eax,dword ptr [esi+3Ch]
bf835f71 3bc3cmp eax,ebx
bf835f73 0f8418010000jewin32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f79 8b4004mov eax,dword ptr [eax+4]
bf835f7c 8b4008mov eax,dword ptr [eax+8]
bf835f7f 8b4038mov eax,dword ptr [eax+38h]
bf835f82 395d08cmp dword ptr [ebp+8],ebx
bf835f85 0f85dd000000jne win32k!NtUserBuildHwndList+0x70 (bf836068)
bf835f8b 895de4mov dword ptr [ebp-1Ch],ebx
bf835f8e 3bc3cmp eax,ebx
bf835f90 0f84ad000000jewin32k!NtUserBuildHwndList+0xaa (bf836043)
bf835f96 395d10cmp dword ptr [ebp+10h],ebx
bf835f99 0f8572ffffffjne win32k!NtUserBuildHwndList+0xca (bf835f11)
bf835f9f 56pushesi
bf835fa0 57pushedi
bf835fa1 50pusheax
bf835fa2 e861fdffffcallwin32k!BuildHwndList (bf835d08) <- CALL BuildHwndList
bf835fa7 8bf0mov esi,eax
bf835fa9 8975e0mov dword ptr [ebp-20h],esi 

发生异常时 eax 寄存器值为零。根据 InternalBuildHwndList 函数的指令序列得知 eax 寄存器存储的是 win32k!gptiCurrent 的值,win32k!gptiCurrent 是一个临界变量。在 NtUserBuildHwndList 函数中通过调用 win32k!EnterCrit 进入临界区,用来确保 USER 相关的各种全局资源能够独占访问。win32k!EnterCrit 通过调用 KeEnterCriticalRegion 进入临界区并通过 ExAcquireResourceExclusiveLite 函数对 gpresUser 资源实施共享锁定之后,调用 PsGetThreadWin32Thread 获取当前线程的线程信息结构体 tagTHREADINFO 指针并赋值给 win32k!gptiCurrent 变量。


kd> u win32k!EnterCrit
win32k!EnterCrit:
bf800b5a ff1524cb98bfcalldword ptr [win32k!_imp__KeEnterCriticalRegion (bf98cb24)]
bf800b60 6a01push1
bf800b62 ff3520ab9abfpushdword ptr [win32k!gpresUser (bf9aab20)]
bf800b68 ff159ccb98bfcalldword ptr [win32k!_imp__ExAcquireResourceExclusiveLite (bf98cb9c)]
bf800b6e ff1560cb98bfcalldword ptr [win32k!_imp__PsGetCurrentThread (bf98cb60)]
bf800b74 50pusheax
bf800b75 ff15f4d098bfcalldword ptr [win32k!_imp__PsGetThreadWin32Thread (bf98d0f4)]
bf800b7b a318ae9abfmov dword ptr [win32k!gptiCurrent (bf9aae18)],eax
bf800b80 c3ret 

PsGetThreadWin32Thread 函数的指令非常简单:


kd> u PsGetThreadWin32Thread
nt!PsGetThreadWin32Thread:
8052883a 8bffmov edi,edi
8052883c 55pushebp
8052883d 8becmov ebp,esp
8052883f 8b4508mov eax,dword ptr [ebp+8]
80528842 8b8030010000mov eax,dword ptr [eax+130h]
80528848 5dpop ebp
80528849 c20400ret 4 

获取当前线程 KTHREAD + 0x130 位置的域的值并作为返回值返回。根据 Windows XP 的定义,该偏移位置存储的是 Win32Thread 指针。


kd> dt _KTHREAD
nt!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x010 MutantListHead : _LIST_ENTRY +0x018 InitialStack : Ptr32 Void +0x01c StackLimit : Ptr32 Void ... +0x12c CallbackStack: Ptr32 Void +0x130 Win32Thread: Ptr32 Void 

然而在 InternalBuildWndList 函数中对 win32k!gptiCurrent 指针变量进行操作之前,并未判断该指针是否为空,直接操作则必然引发异常。事实上,在 Windows XP 操作系统中,Win32k 中的很多例程其默认为在调用自己之前,gptiCurrent 已经是一个有效的值,所以并不进行必要的判断。

然而如果当前线程不是 GUI 线程,如控制台应用程序进程的线程,它们的 Win32Thread 域始终是空值,如果不进行判断就直接在内核中调用 NtUserBuildWndList 等函数,就将直接引发前面提到的 BSOD 异常。幸运的是,用户层进程在通过系统服务调用位于 Win32k.sys 中的系统例程时,其通常通过 User32.dll 或 Gdi32.dll 等动态库模块中的函数来进行,此时该线程应已在内核通过 PsConvertToGuiThread 等函数将其转换成 GUI 线程。

在 Windows 中,所有的线程作为非 GUI 线程启动。如果某线程访问任意 USER 或 GDI 系统调用(调用号 >= 0x1000),Windows 将提升该线程为 GUI 线程(nt!PsConvertToGuiThread)并调用进程和线程呼出接口。

这样一来,通过常规方式从用户层到内核层的标准系统调用来调用 User 或 GDI 的系统服务时,操作系统负责处理相关的初始化和转换操作。但像在我们的驱动程序中执行全局的调用时,就需要对调用的环境(进程和线程)进行必要的判断,而不能轻易地擅自直接进行调用。

网络安全成长路线图

这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成hei客大神,这个方向越往后,需要学习和掌握的东西就会越来越多,以下是学习网络安全需要走的方向:

# 网络安全学习方法

上面介绍了技术分类和学习路线,这里来谈一下学习方法:

## 视频学习

无论你是去B站或者是油管上面都有很多网络安全的相关视频可以学习,当然如果你还不知道选择那套学习,我这里也整理了一套和上述成长路线图挂钩的视频教程,完整版的视频已经上传至CSDN官方,朋友们如果需要可以点击这个链接免费领取。网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值