重新审视CVE-2025-50165:Windows映像组件中的一个关键缺陷

重新审视CVE-2025-50165:Windows映像组件中的一个关键缺陷

ESET研究人员研究了CVE-2025-50165,这是一个严重的Windows漏洞,描述为只需打开一个专门制作的JPG文件即可实现远程代码执行——这是最广泛使用的图像格式之一。该漏洞由Zscaler ThreatLabz发现并记录,引起了我们的兴趣,因为Microsoft评估其严重性为关键,但认为其可利用性较低。我们的根本原因分析使我们能够准确定位错误代码的位置并重现崩溃过程。我们认为,剥削的情景比看起来更为艰难。

这篇博客文章的要点:

  • ESET研究人员对CVE-2025-50165漏洞进行了深入分析,并以伪代码片段进行展示。
  • 我们提供了使用简单12位或16位JPG图像重现崩溃的方法,并检查了最初发布的补丁。
  • CVE-2025-50165 是 JPG 图像编码和压缩过程中的一个缺陷,而非解码中的缺陷。
  • 我们的结论探讨并重新评估了该漏洞的可利用性及攻击场景。

概述

11月20日th2025年,Zscaler ThreatLabz发表了一篇文章,记录了发现WindowsCodecs.dll中存在的高影响力远程代码执行漏洞CVE-2025-50165。该库是Windows的主要接口库,负责处理大多数常见的图像文件格式,如JPG、PNG、GIF、BMP等。Zscaler的发现以及Microsoft提供的描述显示,该缺陷源于WindowsCodecs.dll中未初始化的函数指针被取消引用,该指针属于Windows成像组件。前者成功追踪到了jpeg_finish_compress函数中的去引用问题,该函数在压缩和(重新)编码JPG图像流时被调用。谈到图像处理漏洞,首先会想到的是解析和解码图像渲染时出现的错误。因此,这条相当具体的漏洞代码路径让我们产生了一些想要解答的问题:

  • 具体需要哪些条件才能选择导致未初始化函数指针被取消引用的易受攻击代码路径?
  • jpeg_finish_compress什么时候被叫?
  • 为什么函数指针没有初始化?

鉴于 JPG 图片在网络上非常常见,我们想要答案,于是先调查导致崩溃的代码。

坠毁现场

根据CVE-2025-50165条目描述,WindowsCodecs.dll版本受影响,10.0.26100.00.4946之前。我们分析了易受攻击的版本 10.0.26100.4768(SHA-1: 5887D96565749067564BABCD3DC5D107AB6666BD),并与第一个已修补的版本 10.0.26100.4946(SHA-1: 4EC1DC0431432BC318E78C520387911EC44F84FC)进行了二进制比较。下载对应符号后,我们查看了崩溃功能,jpeg_finish_compress。根据WindowsCodecs.dll 中的一个版本字符串——libjpeg-turbo 3.0.2(构建 20250529)——DLL 依赖于较早的 libjpeg-turbo 库实现(于 1 月 24 日发布)th,2024年)以处理JPG图像。公开的仓库使我们能够将大多数相关的二进制代码和结构映射到其源代码对应文件。例如,jpeg_finish_compress的编译版本与这里提供的源代码对应版本非常相似,如图1所示。

Figure 1. Hex-Rays IDA decompiler output of the jpeg_finish_compress function
图1。Hex-Rays IDA 反编译器对 jpeg_finish_compress 函数的输出与引用的源代码相似

根据Zscaler的发现,崩溃发生在jpeg_finish_compress+0xCC,即图1中的第48行,当取消引用位于未知结构(pub)偏移0x10的函数指针时。根据libjpeg-turbo source代码,这对应一个名为compress_data_12的函数指针。为了到达这条特定路径,jpeg_compress_struct的data_precision成员需要设置为12。这对应于位深,换句话说,就是用来描述颜色的位数。本质上,WindowsCodecs.dll在尝试编码12位精度的JPG图像时会崩溃。

补丁差异与根本原因分析

利用Diaphora,一种二进制微分工具,我们对易受攻击的版本10.0.26100.4768与补丁后的10.0.26100.4946进行了差异,如图2所示。

图2。两个库之间的部分匹配和非匹配函数
图2。Diaphora 成功突出显示了两个库之间部分匹配和未匹配的功能

令人惊讶的是,文章中提到jpeg_finish_compress崩溃功能并未出现。不过,两个与编码相关的函数被更改了:rawtransencode_master_selection和jinit_c_rawtranscode_coef_controller_turbo。易受攻击版本与已补丁rawtransencode_master_selection版本之间的差异见图3。

图3。rawtransencode_master_selection 版本的漏洞和已补丁版本的区别
图3。漏洞版本(左版)和已修补版(右版)之间的差异rawtransencode_master_selection

唯一重要的区别似乎是,原本在函数rawtransencode_master_selection体中内嵌的函数jinit_c_rawtranscode_coef_controller_turbo现在被分离了。查看补丁后的jinit_c_rawtranscode_coef_controller_turbo函数版本,发现之前未初始化的结构成员 compress_data_12 现在被设置为指向一个名为 rawtranscode_compress_output_16 的函数,如图4所示。

Figure 4. Patched version of jinit_c_rawtranscode_coef_controller_turbo
图4。jinit_c_rawtranscode_coef_controller_turbo的补丁版本

注意,在受漏洞版本中同样未初始化的字段compress_data_16,在补丁版本中也被设置为指向rawtranscode_compress_output_16。这个函数其实是一个调用rawtranscode_compress_output的存根函数,这可能表明没有专门的代码来处理12位或16位精度的JPG图像。

重现坠机过程

正如Zscaler文章中提到的,可以编译Microsoft(https://learn.microsoft.com/en-us/windows/win32/wic/-wic-codec-jpegmetadataencoding#jpeg-re-encode-example-code)提出的代码片段来解码并重新编码JPG图像。

该程序编译完成后,可以通过提供12位或16位的JPG文件来重现崩溃。通过从libjpeg-turbo仓库中的样本库,https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/testimages/testorig12.jpg 可以下载一张12位精度的样本图像。将这张图片输入重新编码示例应用时,结果在Zscaler文章中提到的完全相同的位置崩溃了。图5展示了调试会话中崩溃的上下文。

Figure 5. The re-encoding example application crashes
图5。重新编码示例应用在处理12位JPG图像时,在压缩程序中崩溃

内存地址指向的重复十六进制值0xBAADF00D程序调用HeapAlloc分配内存时,C运行时(CRT)堆使用的魔力值。它标记内存为未初始化(见 https://www.nobugs.org/developer/win32/debug_crt_heap.html)。

如前所述,两个分析的WindowsCodecs.dll版本似乎都能处理16位精度的JPG图像。但在测试此类图像时,重新编码应用在取消引用compress_data_16函数指针时崩溃,如图6所示。

Figure 6. The re-encoding example application crashes during the compression routine
图6。重新编码示例应用在处理16位JPG图像时,在压缩程序中崩溃

在重现了崩溃后,我们怀疑这个特定漏洞是否也存在于libjpeg-turbo库的源代码中。

探索源代码

查看 libjpeg-turbo 的提交显示,类似问题在 12 月 18 日已解决th2024年,提交E0E18de,推出3.1.1版本。本质上,提交确保结构是零初始化的,并且如果指针是 NULL,则会引发错误。事实证明,该提交引入的所有零初始化和检查在易受攻击且已补丁的WindowsCodecs.dll版本中都不存在。

补丁消息还暗示了其他潜在的易受攻击的代码路径,更重要的是,在处理JPG图像时,解压过程中也可能发生崩溃,如图7所示的jdapistd.c文件差异

图7。减压程序的差异
图7。jdapistd.c 中实现的解压程序差异

正如提交描述明确规定的,调用应用程序只有在调用jpeg_start_compress或jpeg_start_decompress例程后错误更改data_precision字段时,才会崩溃(原因是未初始化的函数指针被取消引用)。这造成了一个相当具体且很可能不现实的场景,即使用WindowsCodecs.dll的应用程序会改变内部结构的状态。虽然可能存在此类应用,但 Windows 映像组件 API 似乎不允许此类行为。

可利用性

正如我们的根本原因分析所揭示的,CVE-2025-50165的核心问题在于WindowsCodecs.dll对具有非传统标准8位数据精度值的JPG图像的处理。两个针对精度的函数指针(compress_data_12 和 compress_data_16)在压缩过程中未初始化,导致两条脆弱的代码路径似乎只有在重新编码 JPG 图像时才能访问。仅仅打开,从而解码和渲染,专门制作的图像不会触发漏洞。然而,如果图片被保存,或者主机应用程序(如Microsoft Photos应用程序)创建图片缩略图(如图8所示),则可以调用jpeg_finish_compress的漏洞函数。

Figure 8. The vulnerable jpeg_finish_compress function is called
图8。在为图像创建缩略图时调用易受影响的jpeg_finish_compress函数

为了让程序被视为易受攻击,它需要具备以下特征:

  • 利用了WindowsCodecs.dll的脆弱版本,
  • 解码12位或16位JPG文件时不会崩溃或中止,
  • 允许图像被重新编码。

此外,正如Zscaler研究人员所说,利用该漏洞必须有地址泄露和对堆的充分控制。

结论

尽管JPG更古老、广泛使用,且可能是模糊测试中最受欢迎的数字图像格式,但某些编解码器仍存在漏洞。这项对CVE-2025-50165的研究也强调了在使用第三方库时保持安全更新的重要性。

根本原因分析与贴片差异法结合,成为我们解答最初问题的强大组合。我们发现,当WindowsCodecs.dll编码12位或16位精度的JPG流时,可能会触发该漏洞,因为这两个精确度特定函数指针在被取消引用前既未初始化也未检查。此外,我们发现这个过程发生在保存此类图片或由图片创建缩略图时。

这项调查使我们得出了与Microsoft对该漏洞可利用性得出的结论相似。事实上,作为库,WindowsCodecs.dll 如果允许 JPG 图像被(重新)编码,则该托管应用程序被视为易受攻击,且只有在攻击者对应用有足够控制权(地址泄露、堆作)时才可被利用。综合来看,剥削的可能性确实不大。

最后值得一提的是,截至本文撰写及我们的测试,较新版本的WindowsCodecs.dll(如10.0.22621.6133,SHA-1: 3F3767D05E5A91184005D98427074711F68D9950)实现了libjpeg-turbo提交中提到的不同变更,有效解决了初始化和函数指针验证的缺失问题。