R3蓝屏的多种方式及原理分析(一)

VSole2021-10-22 16:29:52

前言

蓝屏死机(英语:Blue Screen of Death,缩写:BSoD)指的是微软Windows操作系统在无法从一个系统错误中恢复过来时所显示的屏幕图像。蓝屏有它存在的理由,在遇到非常严重的严重错误时,为了避免更严重的错误,立马中止系统的所有操作,顺便给个提示,让你知道错误的原因,对于一个专业人员来说,这些机制确实是非常重要的。但当你正在写论文还未保存,眼前却一片蓝色的时候,我想你一定也会黯然神伤吧。这个让普通人恨之入骨,专业人员爱恨交织的东西,究竟有怎样的魔力?本文只探讨由R3引起的BSoD,明眼人都知道研究R0的蓝屏纯属脱裤子放屁。

涉及到的知识点:

  1. 调用未公开的API实现Ring3的BSoD行为
  2. 通过对比Reactos源码挖掘NT内核的更多秘密
  3. 借助IDA的静态分析探究BSoD的流程
  4. windbg的Local Kernel Debugging和双机调试的各种技巧

下图为经典的Win10蓝屏

背景

相信很多人都对R3下的蓝屏很好奇,我也不例外,简单讲几个用处,可以用于自己程序的反调试手段,不过蓝屏比起其他反调试手段显然太过暴力,属于杀敌一千自损八百的情况,当然这种手段也比较好破解。对于各安全厂商而言,这应该是需要特别注意的,如何连这都拦截不了,后面的防护也只是痴人说梦。在网上一搜索了一大圈,大部分文章都是介绍API的,里面各种蓝屏方式,其实都是一个套路,讲解原理的确实少之又少。后来寻了半天终于找到一篇,Ring3触发BSOD代码实现及内核逆向分析,我也是受到启发,写下了本文,上文主要对Process进行了探究,本文将从各个方面更加深入的探究它的原理机制。

Ring3蓝屏的方式

简单的可以分成3大类,如果还有其他种类的欢迎大家补充,网上各大文章里的手段基本是第二类的衍生,但本质还是利用它是Critical Process,但是无论哪种最后调用的一定是由Ring0调用nt!KeBugCheckEx

第一类蓝屏:NtRaiseHardError

特别注意:需要SeShutdownPrivilege权限,因此需要利用RtlAdjustPrivilege提权,但是无需绕过UAC(无需以管理员身份运行)

NTSTATUSNTAPINtRaiseHardError(    IN NTSTATUS   ErrorStatus,            // 错误代码    IN ULONG      NumberOfParameters,    // 指定了Parameters指针数组包含的指针个数    IN PUNICODE_STRING  UnicodeStringParameterMask OPTIONAL,    // 该参数的各个二进制位与Paramenters指针数组一一对应,如果某一位为1,则说明对应的参数指针指向的是一个UNICODE_STRING,否则是一个整数    IN PVOID      *Parameters,            // 参数    IN HARDERROR_RESPONSE_OPTION ResponseOption,    // 枚举类型 本文使用6号,具体含义见下面的代码    OUT PHARDERROR_RESPONSE      Response            // 返回值);

以下是RtlAdjustPrivilege可获取的各种权限,单独提出来的2个是需要用到的

1.SeCreateTokenPrivilege 0x22.SeAssignPrimaryTokenPrivilege 0x33.SeLockMemoryPrivilege 0x44.SeIncreaseQuotaPrivilege 0x55.SeUnsolicitedInputPrivilege 0x06.SeMachineAccountPrivilege 0x67.SeTcbPrivilege 0x78.SeSecurityPrivilege 0x89.SeTakeOwnershipPrivilege 0x910.SeLoadDriverPrivilege 0xa11.SeSystemProfilePrivilege 0xb12.SeSystemtimePrivilege 0xc13.SeProfileSingleProcessPrivilege 0xd14.SeIncreaseBasePriorityPrivilege 0xe15.SeCreatePagefilePrivilege 0xf16.SeCreatePermanentPrivilege 0x1017.SeBackupPrivilege 0x1118.SeRestorePrivilege 0x12    19.SeShutdownPrivilege 0x13    20.SeDebugPrivilege 0x1421.SeAuditPrivilege 0x1522.SeSystemEnvironmentPrivilege 0x1623.SeChangeNotifyPrivilege 0x1724.SeRemoteShutdownPrivilege 0x1825.SeUndockPrivilege 0x1926.SeSyncAgentPrivilege 0x1a27.SeEnableDelegationPrivilege 0x1b28.SeManageVolumePrivilege 0x1c29.SeImpersonatePrivilege 0x1d30.SeCreateGlobalPrivilege 0x1e31.SeTrustedCredManAccessPrivilege 0x1f32.SeRelabelPrivilege 0x2033.SeIncreaseWorkingSetPrivilege 0x2134.SeTimeZonePrivilege 0x2235.SeCreateSymbolicLinkPrivilege 0x23

我们可以在自己的电脑上查看一下自己的权限

普通用户权限

绕过UAC时的权限

第二类蓝屏:Critical Process/Thread

如果对于系统启动过程非常熟悉的话,应该听说过一些系统的关键进程如csrss.exe挂了的话,则整个系统必然挂,这就是属于这一类蓝屏的特色了,只要某个线程或者进程挂了,整个系统就会蓝屏,所以你可能会想,那我只要把这种进程线程关了不就可以蓝吗?那确实,但是你的想法肯定也早就在微软的考虑中,对于那些系统线程,在EHTREAD的SystemThread位置将会是1,这下好了,在关闭线程的函数里判断SystemThread的值,如果是,我就不关,这你不就没办法了吗,对于这部分在后面的逆向部分会提到。咱们先来验证一下是否能关闭系统进程

那不如换种思路,既然无法将系统的Critical Process/Thread关掉,那我把自己设置成Critical Process/Thread“身份”,然后把自己关掉总可以了吧。

我们需要以下API来改变自己的“身份”,以下均为导出但未文档化的函数(在ntdll中),因此可以使用GetProcAddress直接获取,不必搜索特征码,倒是省了一个大麻烦。但是需要SeDebugPrivilege权限,上面可以看到,那是通过UAC之后才有的权限,因此必须绕过UAC才能使用以下API

NtSetInformationProcess

NtSetInformationThread

RtlSetProcessIsCritical 实际是对NtSetInformationProcess的封装

RtlSetThreadIsCritical 实际是对NtSetInformationThread的封装

第三类蓝屏:系统资源耗尽

将物理内存占满应该可以实现蓝屏,读者可以自行测试,不是本文讲解的重点

NtRaiseHardError逆向分析

0.实现代码

先直接上蓝屏代码,通过代码来进行分析

#include #include // 需要的Shutdown权限,至于为什么是19看上面RtlAdjustPrivilege的介绍const ULONG SE_SHUTDOWN_PRIVILEGE = 19;
typedef struct _UNICODE_STRING{    USHORT Length;    USHORT MaximumLength;    PWCH   Buffer;}UNICODE_STRING, *PUNICODE_STRING;
typedef enum _HARDERROR_RESPONSE_OPTION{    OptionAbortRetryIgnore,    OptionOk,    OptionOkCancel,    OptionRetryCancel,    OptionYesNo,    OptionYesNoCancel,    OptionShutdownSystem} HARDERROR_RESPONSE_OPTION, *PHARDERROR_RESPONSE_OPTION;
typedef enum _HARDERROR_RESPONSE{    ResponseReturnToCaller,    ResponseNotHandled,    ResponseAbort,    ResponseCancel,    ResponseIgnore,    ResponseNo,    ResponseOk,    ResponseRetry,    ResponseYes} HARDERROR_RESPONSE, *PHARDERROR_RESPONSE;
// 函数指针typedef NTSTATUS(NTAPI *NTRAISEHARDERROR)(    IN NTSTATUS             ErrorStatus,    IN ULONG                NumberOfParameters,    IN PUNICODE_STRING      UnicodeStringParameterMask OPTIONAL,    IN PVOID                *Parameters,    IN HARDERROR_RESPONSE_OPTION ResponseOption,    OUT PHARDERROR_RESPONSE Response    );
typedef BOOL(NTAPI *RTLADJUSTPRIVILEGE)(ULONG, BOOL, BOOL, PBOOLEAN);
HARDERROR_RESPONSE_OPTION ResponseOption = OptionShutdownSystem;HARDERROR_RESPONSE Response;
NTRAISEHARDERROR NtRaiseHardError;RTLADJUSTPRIVILEGE RtlAdjustPrivilege;
int main(){    // 任何进程都会自动加载ntdll,因此直接获取模块地址即可,不必再LoadLibrary    HMODULE  NtBase = GetModuleHandle(TEXT("ntdll.dll"));    if (!NtBase) return false;
    // 获取各函数地址    NtRaiseHardError = (NTRAISEHARDERROR)GetProcAddress(NtBase, "NtRaiseHardError");    RtlAdjustPrivilege = (RTLADJUSTPRIVILEGE)GetProcAddress(NtBase, "RtlAdjustPrivilege");    // 提权    BOOLEAN B;    if (!RtlAdjustPrivilege(SE_SHUTDOWN_PRIVILEGE, TRUE, FALSE, &B) == 0)    {        printf("提权失败");        getchar();        return 0;    }    NTSTATUS status = NtRaiseHardError(0xC0000217, 0, NULL, NULL, OptionShutdownSystem, &Response);    return 0;}

开启双击调试,直接蓝,连调试的机会都不给你,可以说确实很无解了,至少咱们后面的分析的Critical Thread是可以被windbg断下来的。这个函数本来是用来显示错误信息的,现在却被用来干这种事,世事难料,安全的对抗是永无止境的。

1.栈回溯-观察整体

先通过栈回溯观察下程序的运行流程,便于后续的分析,在KeWaitForSingleObject返回地址处下个断点,直接蓝了,说明该函数就是蓝屏的“元凶”,这个函数非常复杂,它具体干了咱们不用管,在这个例子中大致就是等待服务程序插入线程来处理它的关机请求。

2.IDA分析NtRaiseHardError

分析基于20H1版本的内核,在win7 x64以上这些函数基本没啥变化。

为了方便理解,下文用PX来代指NtRaiseHardError的参数,a代表当前函数的参数,P1就是NtRaiseHardError的第一个参数,a5则为当前函数的第五个参数。

由于第四个参数为0因此直接跳过中间大部分步骤,直接开始调用ExpRaiseHardError(),P1 P2 P3 a5分别是NtRaiseHardError传进来参数1、2、3、5,Dst和v26是一个局部变量数组int64[5],v22则用于返回Response至a6。当然如果你在R0调用,则PreviousMode=0会走下面那个分支调用ExRaiseHardError()

下面让我们看看ExpRaiseHardError干了什么

3.ExpRaiseHardError

不要看上面的参数,是错的,流程却是对的,IDA的F5果然还是不靠谱,算了,还是手动分析参数吧。

事实证明,千万不能信任IDA的F5,重要环节还得自己来,用windbg验证一下,bp nt!ExpSystemErrorHandler下个断点,64位函数使用rcx rdx r8 r9来传递前四个参数的值,因此rcx rdx r8 r9应该分别是P1 P2 P3 v26的值,可以发现完全符合,证明我们分析的参数是正确的。

4.ExpSystemErrorHandler

这函数没做什么事,直接调用了ExpSystemErrorHandler2(),依旧不需要看上面的参数顺序,并不正确,经过分析跟ExpSystemErrorHandler的参数完全一样

ExpSystemErrorHandler2(P1, P2, P3, v26数组地址, a5);

5.ExpSystemErrorHandler2

ExpSystemErrorHandler2进行一些列字符串的操作,然后调用了PoShutdownBugCheck,第三个参数是最初的P1(错误码)。我们通过栈回溯可以知道,并不会执行下面的KeBugCheckEx,因为调用PoShutdownBugCheck时就已经蓝屏了。

6.PoShutdownBugCheck

没做什么有用的事,将函数分发给了ZwInitiatePowerAction,大家也不要看见KeBugCheckEx就兴奋,下面的KeBugCheckEx依旧没有执行的机会。

Nt是给R0调用的,Nt系列的更底层,Zw系列的函数是给R3调用的,需要做一些检查,不管怎么说最后调用的一定是Nt的,所以咱们直接分析Nt的。

7.NtInitiatePowerAction

前面一大堆加锁、去锁,咱们不管它,看关键步骤,执行到这里,调用KeWaitForSingleObject

然后等待system线程挂靠,导致蓝屏

总结

整个流程大致如下图所示

由此看来,NtRaiseHardError的蓝屏“旅程”并不复杂,后面的进程线程蓝屏才是真正的挑战,由于文章篇幅限制,只能放到下一节内容来进行讲解,后续的分析会比这个更加深入,更加曲折,更加刺激!

函数调用蓝屏
本作品采用《CC 协议》,转载必须注明作者和本文链接
近日,Cyber​​Ark安全研究员Eviatar Gerzi发现Windows终端和基于Chrome的网页浏览器上存在多个高危DoS(拒绝服务)漏洞。
概述在windows系统上,涉及到内核对象的功能函数,都需要从应用层权限转换到内核层权限,然后再执行想要的内核函数,最终将函数结果返回给应用层。本文就是用OpenProcess函数来观察函数从应用层到内核层的整体调用流程。OpenProcess函数,根据指定的进程ID,返回进程句柄。NTSTATUS Status; //保存函数执行状态。OBJECT_ATTRIBUTES Obja; //待打开对象的对象属性。HANDLE Handle; //存储打开的句柄。CLIENT_ID ClientId; //进程、线程ID. dwDesiredAccess, //预打开进程并获取对应的权限。ObjectNamePresent = ARGUMENT_PRESENT ; //判断对象名称是否为空
CVE-2021-24086漏洞分析
2022-07-19 16:41:30
漏洞信息2021年,Microsoft发布了一个安全补丁程序,修复了一个拒绝服务漏洞,编号为CVE-2021-24086,该漏洞影响每个Windows版本的IPv6堆栈,此问题是由于IPv6分片处理不当引起的。
Win32k组件最初的设计和编写是完全建立的用户层上的,但是微软在 Windows NT 4.0 的改变中将 Win32k.sys 作为改变的一部分而引入,用以提升图形绘制性能并减少 Windows 应用程序的内存需求。窗口管理器(User)和图形设备接口(GDI)在极大程度上被移出客户端/服务端运行时子系统(CSRSS)并被落实在它自身的一个内核模块中。
IDA故障参考
2023-07-10 10:08:00
IDA故障排除过程记录由于IDA闭源,又加上其十分无效的官方文档。因此如果出现任何错误,都需要进行分析和查错,这一过程很麻烦。按下确定之后,IDA就随风而逝了。你气急败坏的不断导入文件,但是IDA就是屹然不动。观察故障的表和log,也没有任何反馈。简称,用IDA调试IDA。首先你要禁用所有plugin。使用WinDBG打开导出的dump文件:windbg大名鼎鼎,其威名必不用说。
MITM Fuzz下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或,往往预示着该驱动程序可能存在内核漏洞。
在所有函数调用发生时,向栈帧内压入一个额外的随机 DWORD,随机数标注为“SecurityCookie”。在函数返回之前,系统将执行一个额外的安全验证操作,被称做 Security check。
API(Application Programming Interface),我们调用时只需提供正确的参数以及接收返回值就可以判断API执行是否成功或者通过GetLastError获得错误原因.
VulFi,即“漏洞发现者”,它是一个IDA Pro插件,可以帮助广大研究人员在二进制文件中查找漏洞。它的的主要目标是在一个单一视图中给研究人员提供包含了各种函数交叉引用的相关信息。需要注意的是,搜索函数时需要精确匹配。这意味着or 不会被检测为标准函数,因此在寻找潜在漏洞时不会被考虑。除此之外,VulFi将尽最大努力来过滤所有明显的误报。
VSole
网络安全专家