Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >调用 NtUserXXX 引发系统 BSOD 的问题分析

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

作者头像
稻草小刀
发布于 2022-12-12 08:13:04
发布于 2022-12-12 08:13:04
35600
代码可运行
举报
文章被收录于专栏:小刀志小刀志
运行总次数:0
代码可运行

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NTSTATUS
NtUserBuildHwndList (
    IN  HDESK  hdesk,
    IN  HWND   hwndNext,
    IN  BOOL   fEnumChildren,
    IN  DWORD  idThread,
    IN  UINT   cHwndMax,
    OUT HWND  *phwndFirst,
    OUT PUINT  pcHwndNeeded
    );

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

分析

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Access violation - code c0000005 (!!! second chance !!!)
win32k!InternalBuildHwndList+0x1a:
bf835e26 8b402c          mov     eax,dword ptr [eax+2Ch]
kd> dc eax+2Ch l 1
0000002c  ????????                             ????
kd> r eax
eax=00000000
kd> kv
ChildEBP RetAddr  Args 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 中发生了异常。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
win32k!InternalBuildHwndList:
bf835e10 8bff            mov     edi,edi
bf835e12 55              push    ebp
bf835e13 8bec            mov     ebp,esp
bf835e15 56              push    esi
bf835e16 57              push    edi
bf835e17 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
bf835e1a 85ff            test    edi,edi
bf835e1c 74e8            je      win32k!InternalBuildHwndList+0x94 (bf835e06)
bf835e1e 8b7508          mov     esi,dword ptr [ebp+8]
bf835e21 a118ae9abf      mov     eax,dword ptr [win32k!gptiCurrent (bf9aae18)]
bf835e26 8b402c          mov     eax,dword ptr [eax+2Ch]   <- ACCESS VIOLATION, eax=0x00000000
bf835e29 8b8894010000    mov     ecx,dword ptr [eax+194h]
bf835e2f 8b460c          mov     eax,dword ptr [esi+0Ch]

win32k!BuildHwndList:
bf835d08 8bff            mov     edi,edi
bf835d0a 55              push    ebp
bf835d0b 8bec            mov     ebp,esp
bf835d0d a174949abf      mov     eax,dword ptr [win32k!pbwlCache (bf9a9474)]
bf835d12 85c0            test    eax,eax
bf835d14 74cd            je      win32k!BuildHwndList+0x17 (bf835ce3)
bf835d16 832574949abf00  and     dword ptr [win32k!pbwlCache (bf9a9474)],0
bf835d1d 53              push    ebx
bf835d1e 8b5d0c          mov     ebx,dword ptr [ebp+0Ch]
bf835d21 53              push    ebx
bf835d22 ff7508          push    dword ptr [ebp+8]
bf835d25 8d4810          lea     ecx,[eax+10h]
bf835d28 894804          mov     dword ptr [eax+4],ecx
bf835d2b 8b4d10          mov     ecx,dword ptr [ebp+10h]
bf835d2e 50              push    eax
bf835d2f 89480c          mov     dword ptr [eax+0Ch],ecx
bf835d32 e8d9000000      call    win32k!InternalBuildHwndList (bf835e10)   <- CALL InternalBuildHwndList
bf835d37 8b4804          mov     ecx,dword ptr [eax+4]
bf835d3a 3b4808          cmp     ecx,dword ptr [eax+8]

win32k!NtUserBuildHwndList:
bf835f21 6a14            push    14h
bf835f23 68e8d798bf      push    offset win32k!`string'+0x550 (bf98d7e8)
bf835f28 e8dbacfcff      call    win32k!_SEH_prolog (bf800c08)
bf835f2d 6a02            push    2
bf835f2f 5f              pop     edi
bf835f30 e825acfcff      call    win32k!EnterCrit (bf800b5a)
bf835f35 a158aa9abf      mov     eax,dword ptr [win32k!gpsi (bf9aaa58)]
bf835f3a f6400208        test    byte ptr [eax+2],8
bf835f3e 0f8547ffffff    jne     win32k!NtUserBuildHwndList+0x1f (bf835e8b)
bf835f44 8b4d0c          mov     ecx,dword ptr [ebp+0Ch]
bf835f47 33db            xor     ebx,ebx
bf835f49 3bcb            cmp     ecx,ebx
bf835f4b 0f8542ffffff    jne     win32k!NtUserBuildHwndList+0x2b (bf835e93)
bf835f51 33c0            xor     eax,eax
bf835f53 395d14          cmp     dword ptr [ebp+14h],ebx
bf835f56 0f84e0000000    je      win32k!NtUserBuildHwndList+0x69 (bf83603c)
bf835f5c ff7514          push    dword ptr [ebp+14h]
bf835f5f e89439feff      call    win32k!PtiFromThreadId (bf8198f8)
bf835f64 8bf0            mov     esi,eax
bf835f66 3bf3            cmp     esi,ebx
bf835f68 0f8423010000    je      win32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f6e 8b463c          mov     eax,dword ptr [esi+3Ch]
bf835f71 3bc3            cmp     eax,ebx
bf835f73 0f8418010000    je      win32k!NtUserBuildHwndList+0x65 (bf836091)
bf835f79 8b4004          mov     eax,dword ptr [eax+4]
bf835f7c 8b4008          mov     eax,dword ptr [eax+8]
bf835f7f 8b4038          mov     eax,dword ptr [eax+38h]
bf835f82 395d08          cmp     dword ptr [ebp+8],ebx
bf835f85 0f85dd000000    jne     win32k!NtUserBuildHwndList+0x70 (bf836068)
bf835f8b 895de4          mov     dword ptr [ebp-1Ch],ebx
bf835f8e 3bc3            cmp     eax,ebx
bf835f90 0f84ad000000    je      win32k!NtUserBuildHwndList+0xaa (bf836043)
bf835f96 395d10          cmp     dword ptr [ebp+10h],ebx
bf835f99 0f8572ffffff    jne     win32k!NtUserBuildHwndList+0xca (bf835f11)
bf835f9f 56              push    esi
bf835fa0 57              push    edi
bf835fa1 50              push    eax
bf835fa2 e861fdffff      call    win32k!BuildHwndList (bf835d08)   <- CALL BuildHwndList
bf835fa7 8bf0            mov     esi,eax
bf835fa9 8975e0          mov     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 变量。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kd> u win32k!EnterCrit
win32k!EnterCrit:
bf800b5a ff1524cb98bf    call    dword ptr [win32k!_imp__KeEnterCriticalRegion (bf98cb24)]
bf800b60 6a01            push    1
bf800b62 ff3520ab9abf    push    dword ptr [win32k!gpresUser (bf9aab20)]
bf800b68 ff159ccb98bf    call    dword ptr [win32k!_imp__ExAcquireResourceExclusiveLite (bf98cb9c)]
bf800b6e ff1560cb98bf    call    dword ptr [win32k!_imp__PsGetCurrentThread (bf98cb60)]
bf800b74 50              push    eax
bf800b75 ff15f4d098bf    call    dword ptr [win32k!_imp__PsGetThreadWin32Thread (bf98d0f4)]
bf800b7b a318ae9abf      mov     dword ptr [win32k!gptiCurrent (bf9aae18)],eax
bf800b80 c3              ret

PsGetThreadWin32Thread 函数的指令非常简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
kd> u PsGetThreadWin32Thread
nt!PsGetThreadWin32Thread:
8052883a 8bff            mov     edi,edi
8052883c 55              push    ebp
8052883d 8bec            mov     ebp,esp
8052883f 8b4508          mov     eax,dword ptr [ebp+8]
80528842 8b8030010000    mov     eax,dword ptr [eax+130h]
80528848 5d              pop     ebp
80528849 c20400          ret     4

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 的系统服务时,操作系统负责处理相关的初始化和转换操作。但像在我们的驱动程序中执行全局的调用时,就需要对调用的环境(进程和线程)进行必要的判断,而不能轻易地擅自直接进行调用。

  • THE END -

文章链接: https://cloud.tencent.com/developer/article/2191132

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
iOS KVC和KVO
无论是在我们的今后的工作当中还是面试找工作当中,这两个知识点是十分重要的,有些同学们对这方面的知识还是不是很了解,概念模糊,这里我整理下相关的内容知识分享给大家。
conanma
2021/10/28
8960
kvo深入浅出举例
一,概述 KVO,即:Key-Value Observing,它提供一种机制,当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。 二,使用方法 系统框架已经支持KVO,所以程序员在使用的时候非常简单。 1. 注册,指定被观察者的属性    2. 实现回调方法 谁是观察者 这个回调方法就写在哪 3. 移除观察  最好在dealloc中写 三,实例: 假设一个场景,股票的价格显示在当前屏幕上,当股票价格更改的时候,实时显示更新其
用户1219438
2018/02/01
6380
面试驱动技术 - KVO && KVC
what?怎么跑出来一个NSKVONotifying_MNPerson?person的class 不是MNPerson 吗?
小蠢驴打代码
2019/03/15
1.2K0
KVC & KVO
        KVC和KVO看上去又是两个挺牛的单词简写,KVC是Key-Value Coding的简写,是键值编码的意思。KVO是Key-Value  Observing的简写,是键值观察的意思。那么我们能拿KVC和KVO干些什么事呢?这两个缩写单词不能否认听起来挺高端的样子。这两个方法都是runtime方法,我们先来介绍KVC。 1.KVC(Key-Value Coding)键值编码             为了测试我们建立两个测试类                   测试类一: 1 2
lizelu
2018/01/11
9030
KVC/KVO 本质
2. 若没有找到Set方法,会调用对象的类方法+ (BOOL)accessInstanceVariablesDirectly;此方法返回YES时(默认返回YES),会按照_key,_iskey,key,iskey的顺序搜索成员,然后赋值。
用户1941540
2019/02/15
6430
KVC/KVO 本质
程序员面试闪充 -- KVC&KVO
一、键值编码KVC kvc&kvo视频讲解 1、介绍 由于oc的语言特性,使得开发者根本不必进行任何操作就可以进行属性的动态读写,这种方式就是Key Value Coding(简称KVC)。 KVC的操作方法由NSKeyValueCoding协议提供,而NSObject就实现了这个协议,也就是说OC中几乎所有的对象都支持KVC操作,常用的KVC操作方法如下: 动态设置:setValue:属性值 forKey:属性名用于简单路径;setValue:属性值 forKeyPath:属性路径用于复合路径,例如Pe
谦谦君子修罗刀
2018/05/02
7840
[Objective-C] KVC 和 KVO
KVC是一种用间接方式访问类的属性的机制。比如你要给一个类中的属性赋值或者取值,可以直接通过类和点运算符实现,当然也可以使用KVC。不过对于私有属性,点运算符就不起作用,因为私有属性不暴露给调用者,不过使用KVC却依然可以实现对私有属性的读写。
wOw
2018/09/18
7450
ios KVO及实现原理
概述 KVO全称KeyValueObserving,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件。由于KVO的实现机制,所以对属性才会发生作用,一般继承自NSObject的对象都默认支持KVO。
赵哥窟
2018/09/13
7010
iOS中KVC与KVO的应用解析 原
KVC键值编码是Object-C为我们提供的一种对成员变量赋值的方法。在探讨其方法之前,我们先来看一个小例子:
珲少
2018/08/15
3700
编码篇-精析OC史诗级技术之KVC
不得不承认KVC在开发过程中是神器一般的存在。如果正确灵活使用kvc,会使得整个开发过程轻松很多。简单而强大。
進无尽
2018/09/12
1.4K0
编码篇-精析OC史诗级技术之KVC
KVC 使用方法详解及底层实现你要知道的KVC、KVO、Delegate、Notification都在这里
你要知道的KVC、KVO、Delegate、Notification都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本系列文章主要通过讲解KVC、KVO、Delegate、Notification的使用方法,来探讨KVO、Delegate、Notification的区别以及相关使用场景,本系列文章将分一下几篇文章进行讲解,读者可按需查阅。 KVC 使用方法详解及底层实现 KVO 正确使用姿势进阶及底层实现 Protocol与Dele
WWWWDotPNG
2018/04/10
1.3K0
KVC 使用方法详解及底层实现你要知道的KVC、KVO、Delegate、Notification都在这里
iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式)
KVO 是 Objective-C 对 观察者模式(Observer Pattern)的实现。当被观察对象的某个属性发生更改时,观察者对象会获得通知。有意思的是,你不需要给被观察的对象添加任何额外代码,就能使用 KVO 。这是怎么做到的?
陈满iOS
2018/09/10
2.2K0
iOS开发·KVO用法,原理与底层实现: runtime模拟实现KVO监听机制(Blcok及Delgate方式)
# iOS中的KVO底层实现
KVO是Key-Value-Observer的缩写,使用的是观察者模式。底层实现机制都是isa-swizzing,就是在底层调用object_setClass函数,将对象的isa指向的Class偷偷换掉。
Haley_Wong
2019/03/29
1.3K0
iOS KVO实现原理及使用
KVO(key-value observe)是在KVC的基础上实现的一种用于监听属性变化的设计模式;如果对某个类的某个属性设置了KVO,那么当这个属性发生变化时,就会触发监听方法,从而知道属性变化了。如果本类一个属性的改变会影响到其他多个属性的变化,我们也会经常自己重写这个属性的set方法,用来监听他的变化,但是如果不是本类的属性,我们就没办法重写其set方法了,这个时候KVO就可以上场了,其实KVO本质上也是重写set方法,而整个过程依赖于runtime才能实现。
iOSSir
2023/03/19
5530
iOS KVO实现原理及使用
iOS进阶_KVC(&KVC赋值取值过程分析&KVC自定义&异常处理)
在WTPerson.m中我们让accessInstanceVariablesDirectly返回NO,则程序直接崩溃。
编程怪才-凌雨画
2020/09/18
8920
OC底层探索20-KVO中的isa-swizzling分析OC底层探索20-KVO中的isa-swizzling分析
猜测NSKVONotifying_LGPerson这个类是系统动态进行添加,所以需要分析它的进行关系。获取LGPerson的子类
用户8893176
2021/08/09
6680
OC底层探索20-KVO中的isa-swizzling分析OC底层探索20-KVO中的isa-swizzling分析
iOS kvc
今天,他们遇到了kvc第二次去学习它,在网上看了很多博客,这似乎不符合我的口味,为了提取一些以下的。总结自己的。
全栈程序员站长
2022/07/06
2590
iOS 知识点回顾(一)
温故而知新 目录 一个NSObject对象占用多少内存? 对象的isa指针指向哪里? OC的类信息存放在哪里? iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?) KVC Category +load方法和+initialize方法 Block _ _weak 1. 一个NSObject对象占用多少内存? 系统分配了16个字节给NSObject对象(通过malloc_size函数获得),但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInst
且行且珍惜_iOS
2019/12/30
6540
iOS - 关于 KVC 的一些总结
我们可以使用setter方法为currentBalance属性赋值,这是直接的,但缺乏灵活性。
师大小海腾
2020/04/16
2K0
iOS_KVC:Key-Value Coding-1(使用)
以上2个方法如果Key值不对(即该属性不存在),则会触发valueForUndefinedKey:方法,默认会抛出NSUndefinedKeyException异常,导致crash。
mikimo
2022/07/20
4190
iOS_KVC:Key-Value Coding-1(使用)
相关推荐
iOS KVC和KVO
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验