总结:DLL劫持是一种用于执行恶意代码payload的流行技术,此文列出了将近300个可执行文件,它们容易受到Windows10(1909)上相对路径DLL劫持的影响。并展示了如何通过几行VBScript代码绕过UAC以提升特权执行某些DLL劫持。
DLL劫持
首先,让我们弄清楚DLL劫持的定义。从广义上来讲,DLL劫持是欺骗合法/受信任的应用程序以加载任意DLL。如DLL搜索顺序劫持、DLL加载顺序劫持、DLL欺骗、DLL注入和DLL侧加载(也就是常说的白加黑)等技术经常被错误的用来表示相同的内容。但这样的描述是不准确的,因为上面列举的几种技术只能说是描述了DLL劫持的具体情况。所以综上所述,准确的来说DLL劫持会从合法DLL接管DLL。
攻击者使用DLL劫持的方式和原因各不相同。其动机包括执行(通过受信任的可执行文件执行恶意代码不太容易被杀软查杀,且在某些情况下可以绕过应用程序白名单,如AppLocker)、获取持久化(如果目标应用程序已经预先安装并定期运行,那么恶意代码也能随着软件正常运行)和权限提升(如果目标应用程序在提升的权限下运行,那么恶意代码也会运行)。
有多种方法可供选择,成功与否取决于应用程序如何配置以加载所需的dll。可能的方法包括:
(1) DLL替换:用恶意的DLL替换掉合法的DLL,可以将其与DLL代理结合使用,以确保原DLL的所有功能均保持不变。
(2) DLL搜索顺序劫持:当应用程序加载DLL的时候,如果没有带指定DLL的路径,那么程序将会以特定的顺序依次在指定的路径下搜索待加载的DLL。通过将恶意DLL放在真实DLL之前的搜索位置,就可以劫持搜索顺序,劫持的目录有时候包括目标应用程序的工作目录。
(3) 虚拟DLL劫持:释放一个恶意的DLL来代替合法应用程序加载的丢失的/不存在的DLL。
(4) DLL重定向:更改DLL搜索的路径,比如通过编辑%PATH%环境变量或 .exe.manifest/.exe.local文件以将搜索路径定位到包含恶意DLL的地方。
(5) WinSxS DLL替换:将目标DLL相关的WinSxS文件夹中的恶意DLL替换为合法的DLL。此方法通常也被称为DLL侧加载。
(6) 相对路径DLL劫持:将合法的应用程序复制(并有选择地重命名)与恶意的DLL一起放入到用户可写的文件夹中。在使用方法上,它与(签名的)二进制代理执行有相似之处。它的一个变体是(有点矛盾地称为)“自带LOLbin”,其中合法的应用程序带有恶意的DLL(而不是从受害者机器上的合法位置复制)。
查找易受到攻击的可执行文件
DLL劫持的关键是找到可以在默认用户权限下被利用的易受攻击的可执行文件。当目标是预先安装的Windows系统可执行文件时,通常会排除第一个选项,而选项2和3中的任何文件夹都必须是用户可写的,就像选项4和5中的文件和文件夹一样。通常情况并非如此。
这就给了我们第六种选择,一种非常弱的方式来实现DLL劫持。虽然该方式不能很好的获得持久化和权限提升,但经常可以在野外利用的样本中看到这种方法的实现。比如海莲花(APT32)在2019年底的攻击中使用了rekeywiz.exe与一个恶意的duser.dll,此时通过上诉的LOLbin,恶意软件会嵌入合法软件并释放到磁盘上。此外,如果可执行文件没有打过补丁,还可以从system32文件夹中合法复制该可执行文件。
为了防止在未来出现基于此方法的新攻击,我们有必要找到这些易受到DLL劫持的可执行文件。这将为红队研究人员提供更新的执行手段,更重要的是,可以使得防御方可以采取适当的措施进行侦查和预防。
方案
为了方便实验,我们将对默认情况下会出现的C:Windowssystem32文件夹进行分析。本次实验的操作系统版本为Windows10 v1909,目标文件夹下一共包含了616个可执行文件,如果只考虑有签名的应用程序,则有613个。
为了监视每个进程都尝试加载哪些DLL,我们将使用Procmon工具进行行为监测。因此,采取的方法是:
(1) 将受信任的可执行文件复制到用户可写的位置。
(2) 运行复制的可执行文件。
(3) 使用Procmon识别在用户可写位置中寻找的DLL
通过这样的方法,我们可以很好的识别每个应用程序查询的每个DLL,它们都将是潜在的可劫持DLL的候选对象。但并不是所有的这些DLL都会自动加载,所以正确找出加载了哪些DLL最可靠的方法是编译我们自己的DLL文件,并在成功加载后将其写入到一个唯一的文件。如果我们对所有目标可执行文件和DLL重复上诉的方法,就能够得到一个文件结合,告诉我们哪些文件容易遭受DLL劫持攻击。
编译自定义DLL将比听起来要更复杂一些,因为如果缺少进程或入口点,可执行文件将无法正常的加载此dll,注入DLL Export Viewer之类的工具可用于枚举程序所有外部函数名称和合法DLL的序数。以确保我们可以编译出相同格式的DLL,能够被目标应用程序正常加载。
总而言之,我们采用的方法是:
可以在GitHub 上找到具有更详尽的技术说明的完整代码。
确认DLL劫持候选
下表列出在Windows10 v1909的C:WindowsSystem32文件夹下易受到DLL劫持攻击的所有可执行文件,每个可执行文件旁边是一个或多个可以被劫持的DLL,以及被调用的过程。如前一节所说,这些不仅仅是理论上的目标,它们是经过测试并且确认有效的,该列表中包括了287个可执行文件和263个唯一的DLL:(由于插件原因,更详细的列表请到原文地址查看~)
一些说明:
(1) 测试是通过简单的运行每个可执行文件来完成的,在测试过程中没有使用任何参数,也没有进行用户交互。这也就解释了为什么xwizard.exe在此列表中不能进行DLL劫持。因为它至少需要两个参数才能正常工作。
(2) 有些程序附带了GUI,或者其他一些显示二进制文件执行情况的可视化元素,这还包括错误信息:所需的DLL可能丢失,而被劫持的DLL显然就会缺少原始的功能,通常来说,攻击者也不太可能将此类应用程序作为DLL劫持的目标。
(3) 使用c++编写的dll暂时未在测试范围内。
完整列表的CVS版本可在GitHub上找到。
结合UAC绕过
找到所有这些可执行文件后,我们就有可能通过受新人的程序执行代码,但是,如果结合UAC绕过技术,那么还可能提升在系统中的权限。
Windows Vista开始引用了用户账户控制(UAC)作为一项安全功能,它要求用户通过提示符进行确认,然后才能将以普通权限运行的进程提升为更高权限运行。在UAC绕过滥用之后,微软在Windows7中引用了自动提升功能,如果它们位于受信任的目录(比如C:Windowssystem32)中,它将自动提升某些进程的权限。
考虑到这一点,您可以尝试通过使用标记为自动提升的可执行文件来尝试以提升的特权运行任意代码,该可执行文件也容易受到DLL劫持的影响。如前一节所示,大约有35个这样的可执行文件需要克服受信任目录的问题:自动提升可执行文件和自定义DLL都需要位于受信任目录中,但这些都不是用户可写的。
关于UAC绕过已经有一些比较成熟和出色的研究,我最喜欢的技术之一是使用尾随空格来模拟受信任目录。我强烈建议阅读完整的博客文章。用户可以创建(c:windows system32)(请注意第一个文件夹之后的空格),并且可以自动将此文件夹中的可执行文件提升为信任。
这是否可以作为一个安全漏洞还值得商榷,因为微软给出的解释是它不算是一个漏洞,而是一个bug。因为大多数(非企业)Windows计算机都默认使用Administrator账户。
无论哪种方式,都为我们提供了一种极好的方法,让我们更好的利用DLL劫持。需要注意的是,在Windows中无法通过传统的方法创建带尾随空格的文件夹,您可以编写一些C语言代码来完成此操作。不过VBScript也可以完成此类工作,代码如下:
Set oFSO = CreateObject("Scripting.FileSystemObject")
Set wshshell = wscript.createobject("WScript.Shell")
' Get target binary and payload
WScript.StdOut.Write("System32 binary: ")
strBinary = WScript.StdIn.ReadLine()
WScript.StdOut.Write("Path to your DLL: ")
strDLL = WScript.StdIn.ReadLine()
' Create folders
Const target = "c:windows "
target_sys32 = (target & "system32")
target_binary = (target_sys32 & strBinary)
If Not oFSO.FolderExists(target) Then oFSO.CreateFolder target End If
If Not oFSO.FolderExists(target_sys32) Then oFSO.CreateFolder target_sys32 End If
' Copy legit binary and evil DLL
oFSO.CopyFile ("c:windowssystem32" & strBinary), target_binary
oFSO.CopyFile strDLL, target_sys32
' Run, Forrest, Run!
wshshell.Run("""" & target_binary & """")
' Clean files
WScript.StdOut.Write("Clean up? (press enter to continue)")
WScript.StdIn.ReadLine()
wshshell.Run("powershell /c ""rm -r """"\?" & target & """""""") 'Deletion using VBScript is problematic, use PowerShell instead
下面的屏幕截图显示了脚本的执行情况。
在上表中,在第一列中标记了自动提升成功的所有可执行文件/ DLL组合。超过160种可能的组合,有很多选择。
预防与检测
防止DLL劫持发生的一种简单方法是使用应用程序始终使用绝对路径而不是相对路径,尽管某些应用程序(尤其是可一直的应用程序)不能很好地做到这一点,但是位于system32文件夹中并依赖这些DLL的应用程序是可以避免的。更好的选择(似乎只有很少的Windows可执行程序可以这样做)是在加载所有DLL前都进行验证,比如检查签名。这种做法可以很好的解决DLL劫持的问题。
然而,正如我们所看到的,攻击者仍然能够携带合法/受信任的应用程序的旧版本,从而受到攻击。因此,即使每个应用程序从现在开始在加载它们之前开始检查它们的dll,我们仍然必须处理这个问题。
因此,让我们将重心放在检测上,您可以从一些特殊路径(特别是临时路径,如%appdata%)中查找前面提到的任意dll的创建或加载。毕竟,加载dll的应用程序的名称可以更改,但是dll的文件名却总是固定的。在这里可以找到一个实例样本Sigma的规则。它成功检测了我们的DLL劫持,尽管如您所见,它的扩展性并不是很好而且容易出现误报。所以,您可以采用一种更通用的方法来实现检测,比如在指定的路径查找微软签名的二进制文件的存在,或是通过此类微软签名的二进制文件从特殊路径加载DLL。
最后,通过查找/windows/文件夹中的任何活动,或者查找以空格结尾的任何文件夹中的任何活动,可以轻松可靠地检测到演示的UAC绕过技术。如前所述,带有尾随空格的Windows文件夹不能通过正常方式创建,因此应该很少,而且总是可疑的。将您的UAC模式设置为“总是通知”,比默认值高一级,将阻止此和其他类似的UAC绕过技术成功。