时时勤拂拭,勿使惹尘埃

TOC

Categories

漏洞分析(三)WinRAR路径穿越漏洞


0x0 背景

近日,Check Point的安全研究团队检测发现WinRAR的四个安全漏洞,分别为ACE文件验证逻辑绕过漏洞(CVE-2018-20250)、ACE文件名逻辑验证绕过漏洞(CVE-2018-20251)、ACE/RAR文件越界写入漏洞(CVE-2018-20252)以及LHA/LZH文件越界写入漏洞(CVE-2018-20253。
漏洞攻击者利用上述漏洞,通过诱使用户使用WinRAR打开恶意构造的压缩包文件,将恶意代码写入系统启动目录或者写入恶意dll劫持其他软件进行执行,实现对用户主机的任意代码执行攻击。
发现漏洞并编写报告的研究员Nadav Grossman很好地描述了他的分析流程,包括他发现的问题,以及他用来分析漏洞的工具。建议任何有兴趣分析漏洞或有关漏洞的人详细阅读他的报告。
另外经腾讯玄武实验室阿图因系统检测发现,除WinRAR软件外,共计38款共计 63 个版本的软件受此漏洞影响。

0x1 Fuzz过程

0x11 Fuzz思路

Check Point的安全研究团队使用WinAFL工具对WinRAR进行fuzz,以下是他们使用的fuzz策略:
  1. 在WinRAR主函数内部创建hareness,使其能够模糊任何存档类型,而无需为每种格式拼接特定harness。这是通过修补WinRAR可执行文件来完成的。
  2. 消除需要用户交互的消息框和对话框等GUI元素。这也是通过修补WinRAR可执行文件来完成。
  3. 使用奥卢大学2005年左右进行的一项有趣研究的巨型语料库
  4. 使用WinRAR命令行开关使用WinAFL对程序进行模糊处理,强制WinRAR解析“已损坏的存档”并设置默认密码(“-p”表示密码,“ - kb”表示保留损坏的解压缩文件)。
通过以上操作后,研究人员发现了 RAR、LZH、ACE 等压缩格式的崩溃,这些存档格式是由内存损坏漏洞引起的,例如Out-of-Bounds Write
并且,在解析 ACE 格式的崩溃时,WinRAR 使用名为 unacev2.dll 来解析 ACE 格式文件的dll引起了研究者的注意,而这个 dll 是2006年生成的没有保护机制的旧版dll。

0x12 创建hareness

就WinRAR而言,只要存档文件具有.rar扩展名,它就会根据文件的魔术字节处理它,此例子中是ACE格式。
为了提高模糊器的性能,并增加相关dll的覆盖范围,需要为unacev2.dll创建一个特定的hareness。
为此,需要了解unacev2.dll如果工作的。在对unacev2.dll解析ACE压缩格式部分代码进行逆向之后,可以发现两个导出函数:
  1. 名为ACEInitDll的初始化函数:
    INT __stdcall ACEInitDll(unknown_struct_1 *struct_1);
    
    • struct_1:指向未知结构的指针
  2. 名为ACEExtract的提取函数:
    INT __stdcall ACEExtract(LPSTR ArchiveName, unknown_struct_2 *struct_2);
    
    • ArchiveName:指向要提取的ace文件路径的字符串指针
    • struct_2:指向未知结构的指针
由于这两个函数都需要未知的结构,有两种方法可以尝试:逆向和调试WinRAR,或查找使用这些结构的开源项目。
第一种选择更耗时,因此选择第二种选择。在github上搜索了导出的函数ACEInitDll,找到了一个名为FarManager的项目(此项目也是WinRAR的创建者创建的),它使用了这个dll并包含了一个未知结构的详细头文件。
在将头文件加载到IDA之后,更容易理解两个函数(ACEInitDll和ACEExtract)之前的“未知结构” ,因为IDA为每个结构成员显示了正确的名称和类型。
从FarManager项目中找到的头文件中,可以找到以下定义:
INT __stdcall ACEInitDll(pACEInitDllStruc DllData);

INT __stdcall ACEExtract(LPSTR ArchiveName, pACEExtractStruc Extract);
为了模仿WinRAR使用unacev2.dll的方式,我们定义了与WinRAR一样的结构成员。
开始fuzz这种特定的harness,但没有发现新的崩溃,并且在fuzz测试的前几个小时内没有扩展新的覆盖分支。所以需要了解这种限制的原因。
首先查找有关ACE归档格式的信息。

0x13 了解ACE格式

虽然没有找到该格式的RFC,但在互联网上找到了其他重要信息。
创建ACE存档受专利保护。唯一允许创建ACE存档的软件是WinACE。该软件的最后一个版本是在2007年11月编制的。该公司的网站自2017年8月以来一直处于关闭状态。但是,提取ACE档案不受专利保护。
acefile 这个维基百科页面)中提到了一个名为纯Python的项目。它最有用的功能是:
  • 可以提取ACE档案。
  • 包含有关ACE文件格式的简要说明。
  • 有一个非常有用的功能,打印文件格式标题和解释。
要理解ACE文件格式,让我们创建一个简单.txt文件(名为simple_file.txt),并使用它进行压缩WinACE。然后我们将使用检查ACE文件的标题acefile
这是 simple_file.txt(压缩前的文件):
这些是选择WinACE创建示例的选项:
在所选的提取目录\users\nadavgr\Documents下创建子目录,并将simple_file.txt提取到该相对路径。
simple_file.ace
使用WinACE的store压缩选项生成的simple_file.ace
使用acefile项目里的acefile.py脚本来解析ACE文件头,添加--header标志来显示有关存档的头信息:
acefile.py头解析输出如下:
关键信息如下:
  • hdr_crc(标记为粉红色):
    两个CRC字段存在于2个标头中。如果CRC与数据不匹配,则提取
    被中断。这就是为什么模糊器找不到更多路径的原因。这里使用acefile.py修补了unacev2.dll所有的CRC 校验。
  • filename(标记为绿色):
    包含文件的相对路径。。其filename长度由黑色框标记的2个字节(小端)定义。
  • advert(以黄色标记)
    WinACE如果使用未注册的版本创建存档,则在创建ACE存档期间会自动添加WinACE未注册的广告字段。
  • 文件内容:
    • “ origsize” - 内容的大小。内容本身位于定义文件的标题之后(“hdr_type”字段== 1)。
    • “ hdr_size” - 标题大小。灰色框标记。
    • 在第二个标题的偏移70(0x46)处,可以找到文件内容:“Hello From Check Point!”
因为文件名字段包含文件的相对路径,所以我们对该字段进行了一些手动修改尝试,以查看它是否容易受到路径穿越的影响。例如,我们将简单的路径穿越符号\ .. \添加到文件名字段,以及使用更复杂的路径穿越技巧,但都没有成功。
在修补所有结构检查(例如CRC验证)之后,再次激活了fuzz工具。在短时间的模糊测试之后,进入了主要的模糊测试目录,发现了一些奇怪的东西。首先描述一下模糊机器以获得一些必要的背景。

0x14 模糊器

为了提高模糊器性能并防止 I\O 瓶颈,我们使用了一个可用ImDisk工具包的RAM磁盘驱动器来运行模糊器。
Ram磁盘映射到驱动器R:\,文件树如下所示:

0x15 检测路径穿越错误

启动模糊器后不久,在驱动器R的根目录中找到了一个名为sourbe的新文件夹,这是在模糊测试期间创建的意外文件夹:
在sourbe文件夹中,找到了一个名为RED VERSION_¶以下内容的文件:
这个是由模糊器在输出意外路径R:\sourbe\REDVERSION_时,生成的文件的十六进制转储:
对此测试用例进行了一些小的更改(例如调整CRC)以使其可解析acefile
解析来自acefile.py的输出,用于由意外路径中的模糊器生成的文件:
由此可知:
  1. 模糊器将“ advert”字段的一部分复制到其他字段
    • 压缩文件的内容为SIO,以橙色框标记。它是广告字符串* UNREGISTERED VER SIO N *的一部分
    • 文件名字段包含字符串RED VERSION *,它是广告字符串* UNREGISTE RED VERSION *的一部分
  2. filename字段中的路径在提取过程中用作“绝对路径”,而不是目标文件夹的相对路径(反斜杠是驱动器的根目录)
  3. 提取文件名是REDVERSION_¶。似乎filename字段中的星号被转换为下划线,并且\x14\值在提取文件名中表示为。该filename字段的其他内容被忽略,因为在\x14\值之后有一个null char终止字符串。
为了找到导致它忽略目标文件夹的约束并filename在提取过程中将该字段用作绝对路径,根据假设进行了以下尝试。
第一个假设是该filename字段的第一个字符’\’触发了漏洞。不幸的是,经过快速检查后我们发现事实并非如此。经过额外检查后,得出了以下结论:
  1. 第一个字符应该是’/‘或’\’。
  2. filename应该至少包含'*'一次; 与位置无关。
filename触发错误的字段示例:\some_folder\some_file*.exe将被解压缩到C:\some_folder\some_file_.exe,并且星号将转换为下划线_

0x2 漏洞分析

0x21 WinRAR验证器

WinRAR对提取的filename内容进行了验证,验证部分代码如下:
“ SourceFileName”表示将提取的文件的相对路径
该功能执行以下检查:
  1. 第一个字符不等于“ \”或“ /”。
  2. 文件名不以以下字符串“ ..\”或“ ../” 开头,这些字符串用于路径穿越
  3. 字符串中不存在以下路径穿越符号:
    1. “ ..\”
    2. “ ../”
    3. “ /../”
    4. “ /..\”
WinRAR 中的unacv2.dll调用StateCallbackProc中的提取函数,并将filenameACE格式的字段作为要提取的相对路径传递。
WinRAR验证器检查相对路径,验证器返回ACE_CALLBACK_RETURN_CANCEL 到dll,(因为该filename字段以反斜杠“\”开头)并且文件创建被中止。
以下字符串传递给WinRAR回调的验证器:
\sourbe\RED VERSION_
注意:这是带有字段\sourbe\RED VERSION*¶的原始文件名,unacev2.dll用下划线替换*

0x22 创建文件原因

由于dll(“ unacev2.dll”)中的错误,即使ACE_CALLBACK_RETURN_CANCEL从回调中返回,filename也会由dll创建相对路径(ACE归档中的字段)中指定的文件夹。
样做的原因是unacev2.dll在创建文件夹之前调用外部验证器(回调),但是在创建文件夹之后它会过早地检查回调的返回值。因此,在调用WriteFile API 之前,它会在将内容写入提取的文件之前中止提取操作
它实际上创建了提取的文件,而没有向其写入内容。它调用CreateFile API ,然后检查回调函数的返回码。如果返回代码是 ACE_CALLBACK_RETURN_CANCEL,它实际上删除了以前通过调用CreateFile API创建的文件。
另外:
  • 绕过删除文件的方法,但它只允许我们创建空文件。可以通过在文件的末尾添加:来绕过文件删除,该文件被视为备用数据流。如果回调返回ACE_CALLBACK_RETURN_CANCEL,则dll会尝试删除文件的备用数据流而不是文件本身。
  • 如果相对路径字符串以\(斜杠)开头,则dll代码中还有另一个过滤函数会中止提取操作。在调用任何其他过滤器函数之前,这发生在第一个提取阶段。 但是,通过添加*或?`字符(通配符)到压缩文件的相对路径(文件名字段),将跳过此检查,代码流可以继续并(部分)触发Path Traversal漏洞。

0x23 小结

  • 在unacev2.dll中找到了一个Path Traversal漏洞。它使我们的线束能够将文件提取到任意路径,并完全忽略目标文件夹,并将提取的文件相对路径视为完整路径。
  • 两个约束导致Path Traversal漏洞:
    1. 第一个char应该是/\
    2. *应filename至少包含一次,位置无关
  • WinRAR部分容易受到Path Traversal的攻击:
    • unacev2.dll从WinRAR回调(ACE_CALLBACK_RETURN_CANCEL)获取中止代码后不会中止操作。由于延迟检查WinRAR回调的返回代码,因此会创建漏洞利用文件中指定的目录。
    • 提取的文件也是在exploit文件中指定的完整路径上创建的(没有内容),但在从回调中检查返回的代码(在调用WriteFile API之前)之后立即删除它。
    • 找到了绕过删除文件的方法,但它允许我们只创建空文件。

0x24 漏洞原因

现在需要弄清楚WinRAR解压时为什么忽略目标文件夹,而是将归档文件(filename 字段)的相对路径视为完整路径
为了实现这个目标,可以使用静态分析和调试,但此处使用更快的方法。使用DynamoRio来记录unacev2.dll解压常规ACE文件和触发该错误的漏洞利用文件的代码覆盖率。然后使用IDA的lighthouse插件对比差异。
在“Coverage Overview”窗口中,我们可以看到单个结果。这意味着在第一次尝试中仅执行了一个基本块(在A中标记),在第二次尝试时未到达(在B中标记)
lighthouse插件用蓝色标记了差异基本块的背景,如下图所示:
从代码覆盖率结果中,可以理解漏洞利用文件不是通过diffed基本块(标记为蓝色),而是采用相反的基本块(错误条件,用红色箭头标记)。
如果代码流经过错误条件(红色箭头),则绿色框内的行用””(空字符串)替换目标文件夹,然后调用sprintf函数,将目标文件夹连接到提取的相对路径文件。
受到对名为GetDevicePathLen (在红框内)的函数的调用的影响,分别用绿色和红色箭头标记的真实和错误条件的代码流程。
如果调用的结果GetDevicePathLen等于0,则sprintf如下所示:
sprintf(final_file_path, "%s%s", destination_folder, file_relative_path);
若不为0则:
sprintf(final_file_path, "%s%s", "", file_relative_path);
后者sprintf为触发Path Traversal漏洞的错误代码。
这意味着相对路径实际上将被视为应写入/创建文件/目录的完整路径。
再来看一下GetDevicePathLen函数:
提取文件的相对路径传递给GetDevicePathLen后,它会检查设备或驱动器名称前缀是否出现在Path参数中,并返回该字符串的长度,如下所示:
  • 该函数返回3则路径为:
    C:\some_folder\some_file.ext
  • 该函数返回1则路径为:
    \some_folder\some_file.ext
  • 该函数返回15则路径为:
    \LOCALHOST\C$\some_folder\some_file.ext
  • 该函数返回21则路径为:
    \?\Harddisk0Volume1\some_folder\some_file.ext
  • 该函数返回0则路径为:
    some_folder\some_file.ext
如果GetDevicePathLen返回值大于0,则提取文件的相对路径将被视为完整路径,因为在调用期间目标文件夹被空字符串替换并传递给 sprintf,然后导致Path Traversal漏洞。
但是,有一个函数可以“清除”提取文件的相对路径,这是一个名为“CleanPath”的清除路径函数伪代码:
该函数遗漏了一些简单的Path Traversal序列,如:\..\(它只检测了路径的开头的..\),同时也遗漏了驱动符号,如:C:\C:,并且由于未知原因C:\C: 可用。
请注意,它不关心第一个字母; 以下字符也将被遗漏:_:\_:_:\_:(在这种情况下,下划线表示任何值)。

0x3 漏洞利用

0x31 构造路径

目标:构造解压到系统启动文件夹的漏洞利用文件
首先应该绕过两个过滤函数来触发bug
要触发空字符串与压缩文件的相对路径的串联,而不是目标文件夹,则应触发以下路径:

sprintf(final_file_path, "%s%s", "", file_relative_path);
而非:

sprintf(final_file_path, "%s%s", destination_folder, file_relative_path);
这时GetDevicePathLen函数的结果应大于0.
这取决于相对路径的内容(“ file_relative_path”)。如果相对路径以这种方式启动设备路径:
  • 选项1:C:\some_folder\some_file.ext
  • 选项2 : \some_folder\some_file.ext (第一个斜杠代表当前驱动器)
但是,在unacev2.dll命名CleanPath中有一个过滤函数,用于检查相对路径是否以C:\开头,并在调用之前将其从相对路径字符串中删除GetDevicePathLen。
它省略了选项1字符串中的C:\序列,但没有从选项2字符串中省略\序列。
为了克服这个限制,可以向选项1添加另一个C:\序列,它将被CleanPath 漏过,并保留字符串的相对路径,如想要的一个C:\,可以如下:
  • 选项1’:C:\C:\some_folder\some_file.ext => C:\some_folder\some_file.ext
但是,WinRAR代码中有一个回调函数,它用作验证器/过滤器。在提取过程中,unacev2.dll调用驻留在WinRAR代码中的回调函数。
回调函数验证压缩文件的相对路径。如果找到黑名单序列,则将中止提取操作。
回调函数进行的检查之一是以\(斜杠)开头的相对路径。
但它没有检查C:\ 。因此,可以使用选项1来利用Path Traversal漏洞!
我们还发现了一个SMB攻击媒介,它可以连接到任意IP地址,并在SMB服务器上的任意路径中创建文件和文件夹。
示例:
C:\\\10.10.10.10\smb_folder_name\some_folder\some_file.ext => \\10.10.10.10\smb_folder_name\some_folder\some_file.ext

0x32 简单示例

将.ace扩展名更改为.rar扩展名,因为WinRAR会根据文件内容检测格式,而不是扩展名
简单构造漏洞利用文件的acefile.py输出的头文件如下:
无论目标文件夹的路径是什么,该存档都将解压缩到 C:\some_folder\some_file.txt

0x33 完整利用

目前可以通过从ACE存档中提取压缩的可执行文件到其中一个启动文件夹来获得代码执行。驻留在Startup文件夹中的任何文件都将在引导时执行。
以下路径中至少有2个Startup文件夹:
  1. C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp
  2. C:\Users\\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
Startup文件夹的第一个路径需要高权限/高完整性级别(如果启用了UAC)。但是,WinRAR默认以中等完整性级别运行。
Startup文件夹的第二个路径要求知道用户的名称。
可以尝试通过创建一个包含精心设计的压缩文件的ACE归档文件来克服它,其中任何一个都包含Startup文件夹的路径但具有不同的路径。
通过filename在ACE存档中使用以下字段:
C:\C:C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe
它被CleanPath 函数转换为以下路径:
C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe
因为该CleanPath函数删除了C:\C:序列。
此外,此目标文件夹将被忽略,因为GetDevicePathLen函数返回2将为最后一个C:序列。
最后的路径:
序列C:由Windows转换为正在运行的进程的“当前目录”。在本次例子中,它是WinRAR的当前路径。
如果从其文件夹执行WinRAR,则“当前目录”将是此WinRAR文件夹: C:\Program Files\WinRAR
但是,如果通过双击存档文件或右键单击存档文件中的“extract”来执行WinRAR,则WinRAR的“当前目录”将成为存档所在文件夹的路径。
例如,如果存档位于用户的“下载”文件夹中,则WinRAR的“当前目录”将为:
C:\Users\<user name>\Downloads
如果存档位于Desktop文件夹中,则“当前目录”路径将为:
C:\Users\<user name>\Desktop
要从Desktop或Downloads文件夹到Startup文件夹,应该用../返回到“user folder”文件夹,并连接到启动目录的相对路径:AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\置于C:../后面
这是最终结果: C:../AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\some_file.exe
请记住,有两个针对路径穿越序列的检查:
  • 在CleanPath 跳过这样的序列
  • 在WinRAR的回调函数中,它中止了提取操作
CleanPath 检查以下路径穿越模式:\..\
WinRAR的回调函数检查以下模式:
  1. “..\”
  2. “../”
  3. “/../”
  4. “/..\”
因为第一个斜杠或反斜杠不是序列C:../的一部分,所以可以绕过路径穿越验证,但是只能返回一个文件夹。不过这也足够将在不知道用户名的情况下将文件解压缩到Startup文件夹了。
注意:如果想要返回多个文件夹,应该连接以下序列/../。例如,C:../..//../序列将被捕获为回调验证器函数,并且将中止提取。

0x34 公开poc

github上公开了该漏洞的poc,acefile - WinRAR 解压代码执行漏洞 POC:
https://github.com/Ridter/acefile
$ file *
#poc文件,解压会在`c:\c2\demo123\`目录生成`demo2.txt`文件
1.rar:      ACE archive data version 20, from Win/32, version 20 to extract, solid
README.md:  ASCII text
#解析ace文件格式脚本
acefile.py: Python script text executable, ASCII text
执行脚本获取poc头信息如下:
$ python3 acefile.py --headers 1.rar
volume
    filename    1.rar
    filesize    180
    headers     MAIN:1 FILE:2 others:0
header
    hdr_crc     0xd3f1
    hdr_size    44
    hdr_type    0x00        MAIN
    hdr_flags   0x8100      V20FORMAT|SOLID
    magic       b'**ACE**'
    eversion    20          2.0
    cversion    20          2.0
    host        0x02        Win32
    volume      0
    datetime    0x4e556ccf  2019-02-21 13:38:30
    reserved1   30 93 66 76 4e 20 00 00
    advert      b''
    comment     b''
    reserved2   b'\x00\x00\x00H5U\x03]6 \x9e\x10\xfd\xc9\xfbErs'
header
    hdr_crc     0xd0f9
    hdr_size    57
    hdr_type    0x01        FILE32
    hdr_flags   0x8001      ADDSIZE|SOLID
    packsize    6
    origsize    6
    datetime    0x4e5554f4  2019-02-21 10:39:40
    attribs     0x00000020  ARCHIVE
    crc32       0xf68d2c9e
    comptype    0x00        stored
    compqual    0x03        normal
    params      0x000a
    reserved1   0x9e20
    filename    b'Users\\vs\\Desktop\\demo1.txt'
    comment     b''
    ntsecurity  b''
    reserved2   b''
header
    hdr_crc     0x894b
    hdr_size    57
    hdr_type    0x01        FILE32
    hdr_flags   0x8001      ADDSIZE|SOLID
    packsize    4
    origsize    4
    datetime    0x4e5554fc  2019-02-21 10:39:56
    attribs     0x00000020  ARCHIVE
    crc32       0x1b99f8dc
    comptype    0x00        stored
    compqual    0x03        normal
    params      0x000a
    reserved1   0x9e20
    filename    b'c:\\c:\\c2\\demo123\\demo2.txt'
    comment     b''
    ntsecurity  b''
    reserved2   b''
关键在于filename:
    filename    b'c:\\c:\\c2\\demo123\\demo2.txt'
如果python3由于环境变量导致执行错误,可以先执行unset PYTHONPATH

0x4 更多

同时,研究者还发现WinACE 也创建了一个linux版类似于unacev2.dll 的提取实用程序,称为unace-nonfree(使用Watcom编译器编译)。
Windows的源代码(由其unacev2.dll 构建)也包含在内,但它比上一版本unacev2.dll更旧 ,并且无法为Windows编译/构建。
CVE汇总如下:
CVE-2018-20250
CVE-2018-20251
CVE-2018-20252
CVE-2018-20253
而WinRAR决定从“5.70 beta 1”版本开始删除UNACEV2.dll,即从此版本开始不再支持ACE格式的解析。

🤔🤔🤔

漏洞利用比较简单,漏洞原理也比较好理解,重点再于发现漏洞的思路和过程,所以才整理了这篇blog。
Fuzz工具原理虽然简单,但使用却很需要技巧,比如怎样让fuzzer工具能正确执行,并覆盖更多的代码分之。
这次漏洞的发现比较幸运,及时观察到了生成的奇特路径下的文件,通常这种可能就会直接被忽略掉了。
另外对于这个漏洞存在19年之久,想想也是后怕,说不定已经被在野APT中使用过无数次了。