## 攻击面
Outlook 要播放的声音文件是波形音频文件格式( WAV)。它通过接收声音文件路径的PlaySound函数播放。PlaySound将加载文件、解析它,然后调用soundOpen,后者将调用不同的波形函数,例如waveOutOpen。
WAV 文件充当多个音频编解码器的容器(或包装器)。编解码器是一种对数据流(如图像、视频或音频)进行编码或解码的程序或代码。通常,编解码器将采用脉冲编码调制(PCM) 编码,这是一种表示采样模拟信号的简单方法。
我们可以在三个主要攻击面上搜索漏洞:
-
WAV 格式解析 -
音频压缩管理器 -
不同的音频编解码器
WAV 格式解析
WAV 格式解析是在 winmm.dll(实现 Windows 多媒体 API 的库)中的函数soundInitWavHdr内实现的。那里呈现的攻击面并不大,而且似乎已经过审查;我们没有在那里发现任何漏洞。
什么是音频压缩管理器?
音频压缩管理器 (ACM) 是负责处理 WAV 文件中使用的编解码器不使用简单 PCM 编码的情况的代码,因此需要由自定义解码器进行解码。这些解码器在扩展名为 .acm 的文件中实现。一个常见示例是 MP3 编解码器,它在 l3codeca.acm 中实现。每个编解码器都由一个驱动程序处理(它与内核模式驱动程序不同,但功能相似),该驱动程序通过 ACM 注册。
每当需要转换时,例如将 MP3 转换为 PCM 或反之,ACM 就会开始工作并管理此转换。当我们使用未使用 PCM 的 WAV 文件时,将查询 ACM 以查看文件本身中指定的编解码器是否存在并且可以处理转换(图 1)。
Outlook 要播放的声音文件是波形音频文件格式( WAV)。它通过接收声音文件路径的PlaySound函数播放。PlaySound将加载文件、解析它,然后调用soundOpen,后者将调用不同的波形函数,例如waveOutOpen。
WAV 文件充当多个音频编解码器的容器(或包装器)。编解码器是一种对数据流(如图像、视频或音频)进行编码或解码的程序或代码。通常,编解码器将采用脉冲编码调制(PCM) 编码,这是一种表示采样模拟信号的简单方法。
我们可以在三个主要攻击面上搜索漏洞:
-
WAV 格式解析 -
音频压缩管理器 -
不同的音频编解码器
WAV 格式解析
WAV 格式解析是在 winmm.dll(实现 Windows 多媒体 API 的库)中的函数soundInitWavHdr内实现的。那里呈现的攻击面并不大,而且似乎已经过审查;我们没有在那里发现任何漏洞。
什么是音频压缩管理器?
音频压缩管理器 (ACM) 是负责处理 WAV 文件中使用的编解码器不使用简单 PCM 编码的情况的代码,因此需要由自定义解码器进行解码。这些解码器在扩展名为 .acm 的文件中实现。一个常见示例是 MP3 编解码器,它在 l3codeca.acm 中实现。每个编解码器都由一个驱动程序处理(它与内核模式驱动程序不同,但功能相似),该驱动程序通过 ACM 注册。
每当需要转换时,例如将 MP3 转换为 PCM 或反之,ACM 就会开始工作并管理此转换。当我们使用未使用 PCM 的 WAV 文件时,将查询 ACM 以查看文件本身中指定的编解码器是否存在并且可以处理转换(图 1)。介绍 利用我们在本博客系列第 1 部分中描述的漏洞,我们再次能够在目标上播放自定义声音文件,从而滥用 Outlook 的提醒声音功能。为了利用此功能并将其转变为完整的远程代码执行 (RCE),我们开始在 Windows 上搜索中的漏洞。## 攻击面
Outlook 要播放的声音文件是波形音频文件格式( WAV)。它通过接收声音文件路径的PlaySound函数播放。PlaySound将加载文件、解析它,然后调用soundOpen,后者将调用不同的波形函数,例如waveOutOpen。
WAV 文件充当多个音频编解码器的容器(或包装器)。编解码器是一种对数据流(如图像、视频或音频)进行编码或解码的程序或代码。通常,编解码器将采用脉冲编码调制(PCM) 编码,这是一种表示采样模拟信号的简单方法。
我们可以在三个主要攻击面上搜索漏洞:
-
WAV 格式解析 -
音频压缩管理器 -
不同的音频编解码器
WAV 格式解析
WAV 格式解析是在 winmm.dll(实现 Windows 多媒体 API 的库)中的函数soundInitWavHdr内实现的。那里呈现的攻击面并不大,而且似乎已经过审查;我们没有在那里发现任何漏洞。
什么是音频压缩管理器?
音频压缩管理器 (ACM) 是负责处理 WAV 文件中使用的编解码器不使用简单 PCM 编码的情况的代码,因此需要由自定义解码器进行解码。这些解码器在扩展名为 .acm 的文件中实现。一个常见示例是 MP3 编解码器,它在 l3codeca.acm 中实现。每个编解码器都由一个驱动程序处理(它与内核模式驱动程序不同,但功能相似),该驱动程序通过 ACM 注册。
每当需要转换时,例如将 MP3 转换为 PCM 或反之,ACM 就会开始工作并管理此转换。当我们使用未使用 PCM 的 WAV 文件时,将查询 ACM 以查看文件本身中指定的编解码器是否存在并且可以处理转换(图 1)。## 攻击面
Outlook 要播放的声音文件是波形音频文件格式( WAV)。它通过接收声音文件路径的PlaySound函数播放。PlaySound将加载文件、解析它,然后调用soundOpen,后者将调用不同的波形函数,例如waveOutOpen。
WAV 文件充当多个音频编解码器的容器(或包装器)。编解码器是一种对数据流(如图像、视频或音频)进行编码或解码的程序或代码。通常,编解码器将采用脉冲编码调制(PCM) 编码,这是一种表示采样模拟信号的简单方法。
我们可以在三个主要攻击面上搜索漏洞:
-
WAV 格式解析 -
音频压缩管理器 -
不同的音频编解码器
WAV 格式解析
WAV 格式解析是在 winmm.dll(实现 Windows 多媒体 API 的库)中的函数soundInitWavHdr内实现的。那里呈现的攻击面并不大,而且似乎已经过审查;我们没有在那里发现任何漏洞。
什么是音频压缩管理器?
音频压缩管理器 (ACM) 是负责处理 WAV 文件中使用的编解码器不使用简单 PCM 编码的情况的代码,因此需要由自定义解码器进行解码。这些解码器在扩展名为 .acm 的文件中实现。一个常见示例是 MP3 编解码器,它在 l3codeca.acm 中实现。每个编解码器都由一个驱动程序处理(它与内核模式驱动程序不同,但功能相似),该驱动程序通过 ACM 注册。
每当需要转换时,例如将 MP3 转换为 PCM 或反之,ACM 就会开始工作并管理此转换。当我们使用未使用 PCM 的 WAV 文件时,将查询 ACM 以查看文件本身中指定的编解码器是否存在并且可以处理转换(图 1)。图 1:使用 ACM 加载特殊音频编解码器
每个 ACM 驱动程序都必须实现acmdStreamSize和acmdStreamOpen等函数。第一个函数返回转换输出所需的大小(以字节为单位);第二个函数创建流结构并设置适当的字段,例如解码回调函数。
ACM 的攻击面并不大。然而,我们设法在该代码中找到了漏洞,我们将在本文后面展示。
不同的音频编解码器
对于最后一个攻击面,我们默认安装了不同的音频编解码器。在 WAV 文件中使用两种不同的方式指定编解码器:
-
FORMAT 块中的 wFormatTag -
如果 wFormatTag 是 WAVE_FORMAT_EXTENSIBLE,则 SubFormat 将保存音频编解码器的 GUID。
附录中描述了可用的编解码器列表。
音频信号处理基础
在深入研究代码之前,让我们先熟悉一下音频信号处理的一些基础知识。如果您已经熟悉这些概念,请直接跳至下一部分。
当我们听到声音时,我们实际上听到的是通过传输介质传播的振动。声音是耳朵接收到这些振动波并被大脑感知的结果。
音频信号是连续的波形。要对其进行数字处理,我们需要将其从模拟信号转换为数字信号(例如,使用 ADC 转换器)。这种转换是模拟信号的离散化。为此,我们以均匀间隔的数据点(称为样本)多次对模拟信号进行采样。采样率(也称为采样频率)决定了每秒采集多少个样本。更高的采样率可以捕获更多细节,但也需要更多的存储和处理。
除了采样率之外,我们还有样本大小,即每个样本使用多少位。同样,样本大小越高,样本质量越好(或更接近原始声音)。样本大小通常为每样本 16 位或 24 位。
音频编解码器基于心理声学模型。心理声学是研究声音感知和听力学的科学,即人类听觉系统如何感知各种声音。例如,人类的听力范围在 20 Hz 到 20,000 Hz 之间。因此,为了进一步减小文件大小,音频编解码器可能会删除人类听不到的频率。此外,如果信号的**音量不足以让人类耳朵听到,音频编解码器可以删除这些信号。例如,如果 20 Hz 的声音低于 60 分贝,则听不到。
心理声学模型的例子还有很多;例如,当安静信号在频率或时间上接近响亮信号时,就会被掩盖。
信号的分析、解释和修改都是通过滤波器组进行的,滤波器组将信号划分为子带——不同的分量带,以便对信号的特定部分进行更详细的检查和处理。常用的滤波器组是 DCT 和多相滤波器组。
有了这些基础知识,我们就可以深入研究不同的编解码器。
第一次尝试:越界写入
我们首先尝试研究 MP3,因为与其他编解码器相比,MP3 要复杂得多。大多数其他编解码器仅执行一些简单的转换,而 MP3 在解码过程中有多个步骤(图 2)。图 2:MP3 的解码过程 [https://www.researchgate.net/publication/289674716_A_Robust_Data_Embedding_Method_for_MPEG_Layer_III_Audio_Steganography] MP3 音频数据被组织成一系列帧,每帧代表一小段音频。帧由标头和音频数据组成。音频数据使用霍夫曼编码进行压缩。每帧代表每个通道(单声道/立体声)的 1,152 个频域样本。这被划分为两个称为颗粒的块,每个块有 576 个样本。每帧还包含与其解码相关的信息,称为边信息(图 3)。
MP3 解码过程中执行的大多数操作都很复杂,虽然从理论上讲,它们可能是寻找微妙漏洞的有趣(和酷)的地方,但实际上,许多操作(例如改进的 DCT、多相滤波器组和混叠减少)都是在不断保存 576 个样本值的缓冲区上执行的。因此,在这里找到越界写入漏洞是不切实际的。一个有趣的地方是霍夫曼解码,因为它自然地适用于更动态的数据(而不是 576 个样本缓冲区)。
MP3 霍夫曼解码
使用代码表(而不是二叉树)对颗粒(576 个样本)进行霍夫曼编码。总频率范围从 0 到 22,050(奈奎斯特频率)被划分为五个区域(图 4):
1.、2. 和 3. 三个“大值”区域 - 样本值介于 -8,206 至 8,206 之间
4. “count1 区域” — 四倍值 -1、0 或 1
5.“rzero 区域”——假定较高频率值具有较低振幅,因此不需要编码;这些值等于 0。图 4:576 个样本频率线到不同区域的划分 [http://www.mp3-tech.org/programmer/docs/mp3_theory.pdf]
每个区域都有自己的哈夫曼表(0 区域除外),因此不同频率的样本采用不同的编码。
将样本划分到不同区域依赖于以下变量:
-
big_values — 指定大值区域中的样本总数 -
region0_count 和 region1_count — 将 big_values 划分为子区域;从 big_values 中减去它们的总和可得出 region2 中的样本数量。 -
part2_3length — 指定比例因子(第 2 部分)使用多少位,以及霍夫曼编码(第 3 部分)使用多少位
解码 576 个样本的过程如下:
-
解码 big_values 区域中的样本 -
解码 count1 区域中的样本 -
如果处理的位数大于 part2_3length,那么我们实际上已经解码了所有输入数据,甚至过度读取了数据 -因此,从 total_samples_read 中减去 4(即丢弃这些输入位) -
如果还有样本,则用 0 填充(这形成 0 区域)
表 1 中的该逻辑以伪代码形式显示。
total_samples_read = 0;
// Decode big_values region
[redacted for brevity]
// Decode count1 region [1]
for (int i = 0; i < count1; i++) {
samples[total_samples_read++] = huff_decode(bitstream, count1_huff_table);
}
if (bits_processed > part2_3length) [2] {
// Overread. Throw last 4 samples
total_samples_read -= 4;
}
// Fill rzero region with zeros
for (int i = total_samples_read; i < 576; i++) {
samples[total_samples_read++] = 0; [3]
}
复制
表1:霍夫曼解码逻辑的伪代码
不幸的是,代码遗漏了一个特定的边缘情况,这会导致整数下溢。
-
大值区域大小为 0 -
Count1 区域大小为 0 -
Part2_3长度为0 -
Bits_processed 大于 0
在这个特定情况下,bits_processed 将大于 part2_3length(在 scalefactors 解码期间,它在解码过程之前达到非零值)。因此,代码将“丢弃”最后四个样本。由于代码未处理任何样本,total_samples_read 为 0。这里将出现下溢,代码认为我们处理了 -4 个样本。现在,它将按如下方式填充 0 区域:
-
将缓冲区指针设置为 &samples[total_samples_read]。这指向样本缓冲区前的16 个字节。 -
将写入大小设置为 576 - total_samples_read = 576 - (-4) = 580 个整数。
因此,我们在样本缓冲区之前直接进行了越界写入,值为零。太棒了!图 5:样本缓冲区的越界写入
那么为什么这个漏洞没有收到 CVE?样本缓冲区是结构的一部分,样本缓冲区之前的字段是 scalefactor 数组。这是一个包含我们已经控制的字段的数组,因此我们在这里并没有真正产生有趣的影响。
代码对样本进行反量化后,也会出现同样的漏洞。它再次填充 0 区域,这次是用反量化缓冲区填充的。想猜猜反量化缓冲区之前是什么吗?霍夫曼解码样本缓冲区。这是我们之前看到的相同样本缓冲区,我们也可以控制它。所以,再一次,我们没有受到真正的影响。
这些越界写入仍然存在于 MP3 解码器中(可通过 WAV 和 .mp3 文件访问),据 Microsoft 称,它们可能会在未来得到修复。虽然在编解码器逆向过程中没有发现影响深远的漏洞,但我们相信解码器执行的不同复杂操作中可能隐藏着漏洞。
第二次尝试:IMA ADPCM 编解码器中的整数溢出
我们的下一个尝试是 IMA ADPCM 编解码器,它在 imaadp32.acm 中实现。正如我们现在所知,ACM 将管理来自不同编解码器的转换。要注册编解码器,代码必须实现 ACM 函数。其中一个函数是 acmStreamSize ,它返回目标缓冲区所需的字节数。
IMA ADPCM 编解码器根据输入有效负载的大小 ( cbSrcLength )、对齐 ( nBlockAlign ) 和每块的样本数 ( wSamplesPerBlock;表 2) 计算目标缓冲区大小。
(cbSrcLength / pwfxSrc->nBlockAlign) *(pwfxSrc->wSamplesPerBlock * pwfxDst->nBlockAlign)
复制
表 2:缓冲区大小计算
在进行乘法之前,代码确保计算不会导致整数溢出(表 3)。
SrcNumberOfBlocks = cbSrcLength / pwfxSrc->WaveFormat.nBlockAlign;
v14 = pwfxSrc->wSamplesPerBlock * pwfxDst->nBlockAlign;
if ( 0xFFFFFFFF / v14 < SrcNumberOfBlocks )
return ERROR_OVERFLOW;
IsThereRemainder = cbSrcLength % pwfxSrc->WaveFormat.nBlockAlign;
if ( IsThereRemainder )
++SrcNumberOfBlocks;
DstBufferLengthInBytes = v14 * SrcNumberOfBlocks;
复制
表 3:防止整数溢出的计算检查
显然,这种检查不足以防止溢出。如果cbSrcLength / pwfxSrc->nBlockAlign的除法中有余数,则代码会增加 ( cbSrcLength */ pwfxSrc->nBlockAlign )*的结果,该结果用于乘法。溢出检查不涵盖此增量。因此,我们仍然可以通过指定自定义值来溢出目标缓冲区长度。
我们需要提供cbSrcLength除以**pwfxSrc->nBlockAlign后有余数。
表 4 显示的是导致整数溢出的值的示例。
cbSrcLength = 0x71c71c72
pwfxSrc->nBlockAlign = 8
pwfxSrc->wSamplesPerBlock = 9
pwfxDst->nBlockAlign = 2
复制
表 4:导致整数溢出的值示例
这会导致目标缓冲区为 0xE 字节,而目标缓冲区应该要大得多。
虽然这看起来像是一个可能导致越界写入的整数溢出,但解码函数正确地确保在分配的缓冲区后没有发生写入,并且不会假设缓冲区分配了正确的大小。
因此,尽管我们提供了多个样本,但实际上当目标缓冲区已满时,代码就会停止。AD PCM 编解码器(在msadp32.acm中实现)中也会发生相同的行为。
第三次尝试:ACM 中的整数溢出(CVE-2023-36710)
最后,我们在ACM代码中发现了一个不错的漏洞。在播放WAV文件的过程中,ACM管理器中的mapWavePrepareHeader函数(在**msacm32.drv中实现)被调用。
此函数存在整数溢出漏洞。它调用acmStreamSize,后者调用驱动程序的回调。回想一下,此函数返回目标缓冲区所需的大小。收到此大小后,mapWavePrepareHeader会添加 176 个字节(将进入目标缓冲区的流头的大小),而不检查溢出。此添加的结果将传递给GlobalAlloc(图 6)。图 6:mapWavePrepareHeader 中的易受攻击的代码
这是一个可利用的问题。我们可以通过使acmStreamSize返回 0xffffff50 到 0xffffffff 之间的值,使**GlobalAlloc分配一个非常小的缓冲区,而不是一个很大的缓冲区。在此分配之后,我们可以导致两次越界写入:
-
流标头值,例如结构大小、源和目标缓冲区指针和大小。这些值是部分可控的。 -
编解码器的解码值。这些值是完全受控制的。
要触发漏洞,我们需要提供一个 WAV 样本,其大小在解码后将大于或等于 0xffffff50。虽然这听起来很容易实现,但我们在尝试过程中发现,在某些编解码器中可能无法实现。例如,对于 MP3 编解码器,作为计算的一部分,需要乘以 1,152 或 576(即每帧的样本数)。该计算的结果永远不会在我们想要的范围内。
最后,我们成功利用IMA ADP 编解码器触发了漏洞。文件大小约为1.8 GB。通过对计算结果进行数学限制运算,我们可以得出IMA ADP 编解码器的最小可能文件大小为1 GB。
如果有可用于动态构建漏洞的脚本引擎,利用此类漏洞会变得更加容易。由于 Windows Media Player 没有脚本引擎,因此利用起来可能更具挑战性。这可能仍然是可能的(正如 Chris Evans 在他的“高级漏洞利用:针对 Linux 桌面的无脚本 0day 漏洞利用”博客文章中所展示的那样)。然而,在 Outlook 应用程序上下文(或其他即时通讯应用程序)中成功利用此漏洞的机会更大。
概括
本系列博文涵盖了从在野利用的漏洞开始的研究。(如果您还没有阅读第 1 部分,请阅读。)研究之旅随后继续寻找绕过方法,最终找到了一个伴随漏洞并将其链接起来以实现零点击 RCE 链。虽然这些漏洞已修复,但攻击者仍在继续寻找可以远程利用的类似攻击面和漏洞。
截至目前,我们研究的 Outlook 攻击面仍然存在,并且有新的漏洞可以发现和利用。尽管微软已经修补了 Exchange,以删除包含 PidLidReminderFileParameter 属性的邮件,但我们不能排除绕过此缓解措施的可能性。
原文始发于微信公众号(红云谈安全):利用漏洞实现 Outlook 的 RCE:第 2 部分
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论