技术心得:分析Windows的死亡蓝屏(BSOD)机制

简介: 技术心得:分析Windows的死亡蓝屏(BSOD)机制

  这篇文章本来是投Freebuf的,结果没过。就贴到博客里吧,图懒得发上来了  


  对于Windows系统来说,被人们视为洪水猛兽的蓝屏也是一种有利于系统稳定的机制。蓝屏其实是Windows系 统的一种自查机制,一但系统发现自己哪里有些不对劲后就立即抛出蓝屏,来阻止错误蔓延。倘若没有蓝屏机制,那么可能很小的一个错误最后会不断的酝酿导致系 统数据损坏的严重后果。而事实上因为Windows系统自身导致的蓝屏其实是少之又少的,更多的蓝屏诱因是各种驱动程序,因为作者个人对Rootkit类 程序感兴趣,因此在平时的学习过程中深感各种不良的内核HOOK或者过滤驱动是诱发蓝屏的小能手。当然不符合微软规定的编程方式或是软件BUG,比如常见 的IRQL错误和违反PatchGuard也会触发蓝屏。当我们理解了Windows蓝屏机制的重要意义之后,一个新的问题被提出来了,就是Windows蓝屏究竟是如何产生的呢?


首先我们可以做一个触发蓝屏的实验,用Windbg和VMware虚 拟机进行双机调试,首先打开被调试虚拟机。虚拟机停留在启动菜单选项时选择以调试模式启动,其实这内核提供的一个功能,如果以这种配置启动 Windows,内核会通过串口向外寻找远端调试器,因为是虚拟机双机调试所以是启用的虚拟串口。所谓的启动菜单其实就是bootmgr程序,这个程序是 //代码效果参考:http://www.lyjsj.net.cn/wz/art_23560.html

由MBR直接启动的。而且这可能是整个Windows系统中最奇葩的PE程序了,这个程序的一部分是实模式的指令一部分是保护模式的指令。bootmgr 会先执行实模式的部分,启动实模式的指令会把CPU状态转到保护模式,于是程序的保护模式指令开始启动,之后bootmgr会启动winload.exe 来进行系统内核的加载。

Windbg成功挂载到内核后,内核会自动中断到Windbg调试器,可能很多人都只是输入G直接继续执行了。但是其实这里是很有搞头的,我们可以在Windbg中输入K指令来看一下栈回溯。


如图,此时内核其实是很初始的阶段,我们看到KiSystemStartup这个函数是内核初始化的主要函数,然后是初始化内核核心和内核执行体。如 果是只接触过linux内 核的朋友可能会有疑问,什么叫内核核心和内核执行体?其实这种划分来自于微软的定义。内核核心是内核中较低层的部分,实现基本的 功能。而内核执行体则是内核中较为上层的部分,我们常接触的就是这部分,各种管理器比如对象管理器、进程管理器也都在这部分。通过栈回溯我们看到中断时内 核处于刚刚初始化的阶段,而此时我们有一个绝佳的机会去跟踪内核的启动流程,如果有机会我会写一篇调试Windows内核初始化的文章。


我们回到正题,我们的目的是触发一次蓝屏然后跟踪蓝屏的产生流程。那么如何触发一次蓝屏呢?写一个驱动可以达到这个目的,但是太麻烦了,而且很多读者可能并没有接触过驱动开发。其实Windbg的一条命令就可以实现触发蓝屏,而且甚至MSDN都给//代码效果参考:http://www.lyjsj.net.cn/wz/art_23558.html

出了方法

我们在Windbg中输入G,让虚拟机继续执行。等系统启动完毕后,用Windbg的Ctrl+Break抛出断点使系统中断到Windbg中。


在Windbg调试器中输入.crash,系统就会触发蓝屏。如图


没错,这个就是当前最“时尚潮流“的蓝屏,与以前传统的蓝屏相比简直就是高富帅和屌丝的差别,但是其实无论是高富帅蓝屏还是屌丝蓝屏其实内部流程都是一样的,只是绘制出的图形不一样而已。


通过.crash命令触发的蓝屏会导致系统重启,我们是不能在调试器中获得通知的,这个时候就需要使用崩溃转储分析了。当你的Windows发生蓝屏崩溃后,系统会自动的储存一份转储文件在你的硬盘中,这份转储与我们通常调试程序时建立的dump文件是相似的,如图就是我用.crash命令触发蓝屏后 形成的转储文件。


注意转储文件的命名是以月日年-排号的顺序来命名的。我是在4月17写的这篇文章,而这是今天的第一个崩溃转储,所以命名就是041716-01,Mini代表 迷你转储。转储文件其实就是崩溃发生时内存状态的一个备份,系统把它封装成一定的格式然后保存起来。Window提供了三种不同的类型的转储,其中Mini转储的体积是最小的,当然内容也是最少的。Mini转储中只包含了当前线程的内核模式内存的转储。崩溃转储文件的优点是可以用Windbg直接打开,就像调试内核一样进行调试!并且是支持使用Windbg命令的。


我们这里使用了!analyze -v命令,这个命令是用来自动分析出错原因的。我们可以在图中看到错误码是e2。


这时候如果你输入栈回溯指令“K”就可以看到触发蓝屏的过程。如图所示


通过栈回溯我们可以猜测函数的执行流程。如果你足够敏感,你会发现KiTrap03这一行。


我们都知道int 3是个断点指令,但是对底层不了解的人可能不知道int 3是怎么处理的。这其实涉及到Windows内核对异常的处理方式,Windows内核通过IDT表来查找处理例程,而KiTrap03正是int 3在IDT中对应的处理例程。这说明,Windbg是使用了int 3来触发蓝屏的。


一个int 3是怎么导致蓝屏的?我们可以在栈回溯中看到nt!KiDispatchException,这是个内核异常分发函数,它的上面是nt!KdpTrap一个沟通内核调试器函数。就是说Windbg通过在内核模式下触发一个异常使内核沟通到调试器,然后执行了KdpCauseBugCheck触发了蓝屏,这个函数中真正起作用的其实是KeBugCheckEx。接下来这篇文章的重点就是分析这个函数。但是 我们该怎样去获知这个的具体操作流程呢?一种常见的方法就是通过反汇编。然而我并不打算通过反汇编的形式来研究这个函数,原因很简单: 反汇编代码并不容易理解,而且当没有符号文件的情况下更是令人蛋疼。


众所周知的是,Windows是一个不开源的系统,然而我们还是可以通过一些特殊的手段看到Windows的源代码。比如可以借助React OS,一个致力于实现与Windows相同环境的开源系统。Windows内核方面的经典著作《Windows内核情景分析》就是基于React OS的,虽然React OS并不是Windows,但是根据我个人的经验来说,React OS代码与Windows代码并没有本质的区别。另一个途径就是WRK了,WRK的全称是“Windows Research Kernel”,它是微软为高校提供的操作系统教学平台。它给出了Windows操作系统内核的大部分代码,可以对其进行修改、编译,并且可以用这个内核启动Windows操作系统。虽然WRK并不是真正的运行在我们电脑上的操作系统代码,但它是我们能接触到的最近真实代码的源码了。下面我就以最常见的WRK1.2版本来进行操作。我们这里用VS2015打开从网上下载WRK1.2工程,使用VS自带的搜索功能就可以找到KeBugCheck函数,整个过程比较慢,因为WRK内容实在是太大了。我们找到KeBugCheck函数后,会发现这个函数只是简单的对KeBugCheck2函数的封装,


可见真正的工作都在KeBugCheck2中完成。而KeBugCheck2是一个相当复杂的函数,呃,至少在代码量上来看是这样的,应该有接近900行。我们跟进这个函数,我们先把注意力放在KeBugCheck2的参数上,第一个参数是BugCheckCode,这个参数实际上就是输出在蓝屏上的“神奇”的代码,其实这个代码一点也不神奇。因为微软已经给出了他们的官方解释,你可以在MSDN上找到它们。


对Windows驱动开发有所了解朋友自然对WDK不会陌生,在WDK中也可找到它们的解释。我们跟进这个函数来一探究竟。


1 VOID


2 KeBugCheck2 (


3 in ULONG BugCheckCode,


4 in ULONG_PTR BugCheckParameter1,


5 in ULONG_PTR BugCheckParameter2,


6 in ULONG_PTR BugCheckParameter3,


7 in ULONG_PTR BugCheckParameter4,


8 in_opt PKTRAP_FRAME TrapFrame


9 )


10


11


12 {


13


14


15 if (BugCheckCode == POWER_FAILURE_SIMULATE)


16 {


17 KiScanBugCheckCallbackList();


18 HalReturnToFirmware(HalRebootRoutine);


19 }


首先面对的这么一段代码,可见这是对错误代码为POWER_FAILURE_SIMULATE的情况的特殊处理,怎么处理的呢?使用HalReturnToFirmware函数,这个函数实质上是Hal.dll的例程。可见我们真的已经足够底层了,再往下挖就到硬件了:)


这个函数的作用是调用BIOS例程实现重启,虽然很少有人听过这个函数,但是却可能有很多人用过这个函数。因为据说PCHunter(原XueTr)的暴力重启就是使用这个函数实现的。


1 switch (BugCheckCode) {


2


3 case SYSTEM_THREAD_EXCEPTION_NOT_HANDLED:


4 case KERNEL_MODE_EXCEPTION_NOT_HANDLED:


5 case KMODE_EXCEPTION_NOT_HANDLED:


6 PssMessage = KMODE_EXCEPTION_NOT_HANDLED;


7 break;


8


9 case DATA_BUS_ERROR:


10 case NO_MORE_SYSTEM_PTES:


11 case INACCESSIBLE_BOOT_DEVICE:


12 case UNEXPECTED_KERNEL_MODE_TRAP:


13 case ACPI_BIOS_ERROR:


14 case ACPI_BIOS_FATAL_ERROR:


15 case FAT_FILE_SYSTEM:


16 case DRIVER_CORRUPTED_EXPOOL:


17 case THREAD_STUCK_IN_DEVICE_DRIVER:


18 PssMessage = BugCheckCode;


19 break;


20


21 case DRIVER_CORRUPTED_MMPOOL:


22 PssMessage = DRIVER_CORRUPTED_EXPOOL;


23 break;


24


25 case NTFS_FILE_SYSTEM:


26 PssMessage = FAT_FILE_SYSTEM;


27 break;


28


29 case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE:


30 PssMessage = BUGCODE_PSS_MESSAGE_SIGNATURE;


31 break;


32 default:


33 PssMessage = BUGCODE_PSS_MESSAGE;


34 break;


35 }


这是根据错误码来获取最终的错误编码,而这个错误编码就是最终会显示在蓝屏界面上的神秘“乱码”。


我们接着往下看


1 switch (BugCheckCode) {


2


3 case FATAL_UNHANDLED_HARD_ERROR:


4 case IRQL_NOT_LESS_OR_EQUAL:


5 case ATTEMPTED_WRITE_TO_READONLY_MEMORY:


6 case ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY:


7 case KERNEL_MODE_EXCEPTION_NOT_HANDLED:


8 case DRIVER_LEFT_LOCKED_PAGES_IN_PROCESS:


9 case DRIVER_USED_EXCESSIVE_PTES:


10 case PAGE_FAULT_IN_NONPAGED_AREA:


11 case THREAD_STUCK_IN_DEVICE_DRIVER:


12 }


又是一个以BugCheckCode为条件的switch语句,这个switch语句中针对不同的错误代码进行了详细的设置,比如这行代码


ExecutionAddress = (PVOID)BugCheckParameter4;


就是用来设置导致崩溃发生的指令地址的,我们在Windbg调试崩溃转储文件时就会看到这个值。Windbg会显示,异常可能是因为XX地址的XX指令导致的,这个XX地址就是由这个ExecutionAddress得来的。


再看看这行代码


KiBugCheckDriver = &DataTableEntry->BaseDllName;


这个值就是保存导致崩溃的模块的名称的,后面会经常使用到这个值,这个值会被写入崩溃转储文件,同样的Windbg也会输出这个值。接着往下看


1 if ((BugCheckCode != MANUALLY_INITIATED_CRASH) && (KdDebuggerEnabled)) {


2


3 DbgPrint("\n* Fatal System Error: 0x%08lx\n"


4 " (0x%p,0x%p,0x%p,0x%p)\n\n",


5 (ULONG)KiBugCheckData【0】,


6 KiBugCheckData【1】,


7 KiBugCheckData【2】,


8 KiBugCheckData【3】,


9 KiBugCheckData【4】);


这个就是当检测到调试器后就输出错误编码,这时候前面设置的代码就派上了用处,注意这里的条件是不能是MANUALLY_INITIATED_CRASH,而我们用.crash触发的就是这个,所以想看到这个只能去触发一个真正的异常了。如图


我这里触发了一个真正的异常,果然出现DbgPrint的结果。


之后会马上调用如下函数


// Freeze execution of the system by disabling interrupts and looping.


KeDisableInterrupts();


KeRaiseIrql(HIGH_LEVEL, &OldIrql);


微软的官方注释已经说明了它的作用:禁用除了当前进程以为其他的一切活动。我来说明这个是怎么实现的,对于CPU来说有一个重要的值叫做IRQL值,高的IRQL值可以屏蔽低的IRQL值。而线程切换是运行于DPC级的IRQL级别上的,而这个函数把IRQL级别提升到了HIGH_LEVEL也就是高于DPC级从而让所有的线程无法切换,实现了屏蔽线程分发。禁用中断则是针对多处理器来说的,屏蔽了多处理器总线。这样一来就保证了,只会有这个处理蓝屏的线程在运行。


接下来继续往下看,会找到这个函数


1 //代码效果参考:http://www.lyjsj.net.cn/wz/art_23556.html

&nbs
相关文章
|
3月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
68 0
|
3月前
|
C# Windows 开发者
当WPF遇见OpenGL:一场关于如何在Windows Presentation Foundation中融入高性能跨平台图形处理技术的精彩碰撞——详解集成步骤与实战代码示例
【8月更文挑战第31天】本文详细介绍了如何在Windows Presentation Foundation (WPF) 中集成OpenGL,以实现高性能的跨平台图形处理。通过具体示例代码,展示了使用SharpGL库在WPF应用中创建并渲染OpenGL图形的过程,包括开发环境搭建、OpenGL渲染窗口创建及控件集成等关键步骤,帮助开发者更好地理解和应用OpenGL技术。
242 0
|
3月前
|
iOS开发 Android开发 MacOS
从零到全能开发者:解锁Uno Platform,一键跨越多平台应用开发的神奇之旅,让你的代码飞遍Windows、iOS、Android、macOS及Web,技术小白也能秒变跨平台大神!
【8月更文挑战第31天】从零开始,踏上使用Uno Platform开发跨平台应用的旅程。只需编写一次代码,即可轻松部署到Windows、iOS、macOS、Android及Web(通过WASM)等多个平台。Uno Platform为.NET生态带来前所未有的灵活性和效率,简化跨平台开发。首先确保安装了Visual Studio或VS Code及.NET SDK,然后选择合适的项目模板创建新项目。项目结构类似传统.NET MAUI或WPF项目,包含核心NuGet包。通过简单的按钮示例,你可以快速上手并构建应用。Uno Platform让你的技术探索之旅充满无限可能。
68 0
|
3月前
|
Kubernetes Cloud Native 开发者
探索云原生技术:Kubernetes入门与实践探索Windows操作系统的隐藏功能
【8月更文挑战第31天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性、效率和可靠性的关键。本文将带你了解云原生的核心组件之一——Kubernetes(K8s),通过浅显易懂的语言和实际代码示例,引导你步入这一强大工具的世界。无论你是初学者还是有经验的开发者,本篇都将为你打开一扇通向高效资源管理与自动化部署的大门。
|
3月前
|
Windows
【Azure 环境】在Windows环境中抓取网络包(netsh trace)后,如何转换为Wireshark格式以便进行分析
【Azure 环境】在Windows环境中抓取网络包(netsh trace)后,如何转换为Wireshark格式以便进行分析
|
5月前
|
存储 IDE 开发工具
【读书笔记】 玩转虚拟机基于Vmware+Windows 虚拟化技术
【读书笔记】 玩转虚拟机基于Vmware+Windows 虚拟化技术
|
3天前
|
监控 安全 网络安全
Windows Server管理:配置与管理技巧
Windows Server管理:配置与管理技巧
18 3
|
7天前
|
存储 安全 网络安全
Windows Server 本地安全策略
由于广泛使用及历史上存在的漏洞,Windows服务器成为黑客和恶意行为者的主要攻击目标。这些系统通常存储敏感数据并支持关键服务,因此组织需优先缓解风险,保障业务的完整性和连续性。常见的威胁包括勒索软件、拒绝服务攻击、内部威胁、恶意软件感染等。本地安全策略是Windows操作系统中用于管理计算机本地安全性设置的工具,主要包括用户账户策略、安全选项、安全设置等。实施强大的安全措施,如定期补丁更新、网络分段、入侵检测系统、数据加密等,对于加固Windows服务器至关重要。
|
1月前
|
边缘计算 安全 网络安全
|
1月前
|
数据安全/隐私保护 Windows
安装 Windows Server 2019
安装 Windows Server 2019