介绍
在本文中,我们将研究DLL劫持的概念以及如何将其用于在Windows系统上实现用户态持久性。 MITER ATT&CK下的“拦截DLL搜索顺序(T1038) ”中介绍了此方法。
攻击者可以将DLL欺骗用于许多不同的目的,但是本文将重点介绍如何通过自动启动应用程序来实现弹性。例如,由于Slack和Microsoft Teams是在引导时启动的(默认情况下),因此这些应用程序之一中的DLL欺骗将使攻击者能够在用户登录时获得对其目标的强大访问权限。
在介绍了DLL的概念,DLL查找顺序和DLL欺骗之后,我将引导您完成自动执行DLL侦听检测的过程。本文将讨论在Slack,Microsoft Teams和Visual Studio Code中检测DLL拦截路径。
最后,我发现了不同应用程序使用的多个DLL拦截路径,调查了根本原因,并发现使用某些Windows API调用的应用程序如果不从运行,则很容易发生DLL拦截
C:\Windows\System32\
。
我要感谢我的同事Josiah Massari(
@Airzero24
)率先发现了这些DLL挂钩,解释了它们的方法,并启发了我使检测自动化。
什么是DLL?
DLL是一个包含可被多个程序同时使用的代码和数据的库。(源)
DLL的功能可以由Windows应用程序使用以下功能之一使用
LoadLibrary*
。应用程序可以引用专门为这些应用程序设计的DLL,也可以引用System32磁盘上已存在的Windows DLL。开发人员可以从System32加载DLL,以使用Windows在其应用程序中已经实现的功能,而不必从头开始编写此功能。
例如,需要发出HTTP请求的开发人员可以使用WinHTTP(
winhttp.dll
)库,而不是使用原始套接字实现HTTP请求。
DLL搜索顺序和拦截
由于DLL以文件形式存在于磁盘上,因此您可能想知道应用程序如何知道从何处加载DLL?Microsoft已在此处详细记录了DLL查找顺序。
从Windows XP SP2开始,默认情况下(
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
)启用DLL安全搜索模式。启用安全模式后,DLL搜索顺序如下:
- 从中加载应用程序的目录。
- 系统目录。使用GetSystemDirectory函数获取此目录的路径。
- 16位系统目录。没有提供该目录路径的函数,但会对其进行搜索。
- Windows目录。使用GetWindowsDirectory函数获取此目录的路径。
- 当前目录。
- , PATH. , , App Paths. App Paths DLL.
一个系统可以包含同一DLL的多个版本。应用程序可以通过指定完整路径或使用其他机制(如清单)来控制DLL加载位置的选择。 (源)
如果应用程序未指定从何处加载DLL,Windows将使用上面显示的默认DLL查找顺序。攻击者会对DLL搜索顺序中的第一位置(加载应用程序的目录)感兴趣。
如果应用程序开发人员打算从以下位置加载DLL:
C:\Windows\System32
,但没有在应用程序中明确编写它,因此将从System32加载到合法DLL之前的应用程序目录中放置的恶意DLL。加载恶意DLL称为DLL欺骗(或拦截),攻击者使用它来将恶意代码加载到受信任/已签名的应用程序中。
使用DLL欺骗来实现弹性
启动易受攻击的应用程序/服务并将恶意DLL放置在易受攻击的位置时,DLL欺骗可用于实现弹性。我的一位同事
@Airzero24
在Microsoft OneDrive,Microsoft Teams和Slack中发现了DLL欺骗userenv.dll
。
正是这些程序成为了拦截的目标,因为默认情况下,它们被配置为在Windows启动时启动。可以在下面的任务管理器中看到:
配置为自动启动的Windows应用程序
为了测试DLL欺骗,我创建了一个DLL shellcode加载器,该加载器启动了Cobalt Strike Beacon。我将恶意DLL重命名为
userenv.dll
并将其复制到受影响的应用程序目录。我启动了该应用程序,并看到了新的Beacon回调。
通过
使用DLL拦截拦截钴打击信标进程浏览器,我可以检查我的恶意DLL是否确实由易受攻击的应用程序加载。
进程资源管理器显示已加载的恶意DLL
自动检测DLL拦截潜力
在确认了先前已知的DLL劫持之后,我想看看是否可以找到其他可以利用的DLL欺骗功能。
我的结帐中使用的代码可以在这里找到。
以Slack为例
要开始此过程,我运行了带有以下过滤器的过程监视器(ProcMon):
- 流程名称-
slack.exe
- 结果包含
NOT FOUND
- 路径以结尾
.dll
。
在ProcMon中查找丢失的DLL。
然后,我启动了Slack,并检查了ProcMon中是否有Slack正在寻找但找不到的DLL。
ProcMon发现的可能的DLL拦截路径
我将这些数据从ProcMon导出为CSV文件,以便在PowerShell中更轻松地进行解析。
使用当前的shellcode加载器DLL,我无法轻松找出Slack成功加载的DLL名称。我创建了一个新的DLL,它是用来
GetModuleHandleEx
,并GetModuleFileName
确定加载的DLL的名称,并把它写入一个文本文件。
我的下一个目标是解析列表中DLL路径的CSV文件,查看该列表,将我的测试DLL复制到指定的路径,启动目标进程,停止目标进程以及删除测试DLL。如果测试DLL成功加载,它将名称写入结果文件。
完成此过程后,我将获得一个可能的DLL劫持列表(希望)写入文本文件的列表。
我的DLLHijackTest项目中的所有魔术都是通过PowerShell脚本完成的。它接受ProcMon生成的CSV文件的路径,恶意DLL的路径,要运行的进程的路径以及要传递给该进程的所有参数。
Get-PotentialDLLHijack参数
Get-PotentialDLLHijack.ps1
几分钟后,我检查了“恶意” DLL中列出的文本文件,以查找可能的DLL劫持事件。我发现了Slack的以下可能拦截路径:
PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\slack\slack.exe"
C:\Users\John\AppData\Local\slack\app-4.6.0\WINSTA.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\LINKINFO.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\ntshrui.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\srvcli.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\cscapi.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\KBDUS.DLL
以Microsoft Teams为例
我们再次执行上述过程:
- 使用ProcMon识别潜在的DLL拦截路径,并将此数据导出为CSV文件。
- 确定开始该过程的路径。
- 定义要传递给流程的任何参数。
Get-PotentialDLLHijack.ps1
使用适当的参数运行。
我发现了Microsoft团队的以下可能拦截路径:
PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\Microsoft\Teams\Update.exe" -ProcessArguments '--processStart "Teams.exe"'
C:\Users\John\AppData\Local\Microsoft\Teams\current\WINSTA.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\LINKINFO.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\ntshrui.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\srvcli.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\cscapi.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\WindowsCodecs.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\TextInputFramework.dll
注意:我必须对PowerShell脚本进行一些小的更改才能完成Teams.exe
,因为我的脚本正试图终止尝试启动的进程,在本例中为Update.exe
。
以Visual Studio Code为例
通过重复上述过程,我发现了Visual Studio Code的以下潜在拦截路径:
PS C:Users\John\Desktop> Get-PotentialDLLHijack -CSVPath .\Logfile.CSV -MaliciousDLLPath .\DLLHijackTest.dll -ProcessPath "C:\Users\John\AppData\Local\Programs\Microsoft VS Code\Code.exe"
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\WINSTA.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\LINKINFO.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\ntshrui.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\srvcli.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\cscapi.dll
共享DLL
我注意到Slack,Microsoft Teams和Visual Studio Code共享以下DLL:
WINSTA.dll
LINKINFO.dll
ntshrui.dll
srvcli.dll
cscapi.dll
我发现这很有趣,并且想了解是什么导致了这种现象。
方法:了解拦截共享DLL的方法
我看着麦蒂栈农闲时试图负载
WINSTA.dll
,LINKINFO.dll
,ntshrui.dll
,srvcli.dll
和cscapi.dll
。
延迟加载的DLL
在加载,和时
WINSTA.dll
,我在Tracy堆栈中发现了相似之处。当Code.exe试图加载堆栈跟踪,当一个堆栈跟踪试图装入,堆栈跟踪农闲时尝试加载
一个堆栈跟踪不断包含一呼叫,接着。对于所有三个应用程序,此行为都是相同的。
我已确定此行为与延迟DLL加载有关。从启动时的跟踪堆栈LINKINFO.dll
ntshrui.dll
srvcli.dll
WINSTA.dll
Teams.exe
LINKINFO.dll
ntshrui.dll
_tailMerge_<
dllname>_dll
delayLoadHelper2
LdrResolveDelayLoadedAPI
WINSTA.dll
我可以看到负责此延迟加载的模块是wtsapi32.dll
。
我
wtsapi32.dll
在吉德拉(Ghidra)开张,习惯了Search -> For Strings -> Filter: WINSTA.dll
。双击找到的行将带您到其在内存中的位置。
该行“
WINSTA.dll
”中wtsapi32.dll
通过在存储器中的位置单击鼠标右键,就可以找到这个地址的任何引用。
链接到
WINSTA.dll
以下链接,我们可以看到该字符串
WINSTA.dll
已传递到名为的结构ImgDelayDescr
。查看此结构的文档,我们可以确认它与延迟DLL加载有关。
typedef struct ImgDelayDescr {
DWORD grAttrs; //
RVA rvaDLLName; // RVA dll
RVA rvaHmod; // RVA
RVA rvaIAT; // RVA IAT
RVA rvaINT; // RVA INT
RVA rvaBoundIAT; // RVA IAT
RVA rvaUnloadIAT; // RVA IAT
DWORD dwTimeStamp; // 0, ,
// O.W. / DLL, (Old BIND)
} ImgDelayDescr, * PImgDelayDescr;
可以将此结构传递给
__delayLoadHelper2
,它将使用LoadLibrary
/GetProcAddress
加载指定的DLL,并在延迟加载导入地址表(IAT)中修复导入的函数地址。
FARPROC WINAPI __delayLoadHelper2(
PCImgDelayDescr pidd, // ImgDelayDescr
FARPROC * ppfnIATEntry // IAT
);
通过查找对我们结构的其他引用
ImgDelayDescr
,我们可以找到一个调用__delayLoadHelper2
,然后调用ResolveDelayLoadedAPI
。我已将函数名称,类型和变量重命名,以使其更易于理解。
__delayLoadHelper2
并ResolveDelayLoadedAPI
在Ghidra
好极了!这与我们在Slack尝试加载时在ProcMon堆栈跟踪中看到的一致
WINSTA.dll
。
__delayLoadHelper2
并ResolveDelayLoadedAPI
在ProcMon中。
这种行为是对均匀
WINSTA.dll
,LINKINFO.dll
,ntshrui.dll
和srvcli.dll
。每个延迟加载DLL之间的主要区别是“父” DLL。在所有三个应用程序中:
wtsapi32.dll
延迟加载WINSTA.dll
shell32.dll
懒加载LINKINFO.dll
LINKINFO.dll
延迟加载ntshrui.dll
ntshrui.dll
延迟加载srvcli.dll
你有没有发现有趣的事情?看起来像是
shell32.dll
下载LINKINFO.dll
,然后下载ntshrui.dll
,最后下载srvcli.dll
。这将我们带到了最后一个潜在的DLL欺骗选项- cscapi.dll
。
NetShareGetInfo和NetShareEnum中的DLL替换
当Slack试图加载
cscapi.dll
并看到一个LoadLibraryExW
显然来自的调用时,我正在看堆栈跟踪srvcli.dll
。
我在Ghidra
引导
cscapi.dll
中
打开了堆栈跟踪并使用了。双击找到的行,然后单击链接将导致预期的呼叫。调用LoadLibrary以 重命名包含调用并跟随链接的函数,我在两个地方使用了该函数:
srvcli.dll
Search -> For Strings -> Filter: cscapi.dll
LoadLibrary
srvcli.dll
cscapi.dll
LoadLibrary
NetShareEnum下载cscapi.dll
NetShareGetInfo下载
cscapi.dll
我用调用
NetShareEnum
和的PoC程序对此进行了检查NetShareGetInfo
:
NetShareEnum.exe
downloads cscapi.dll
NetShareGetInfo.exe
downloadscscapi.dll
结果
Slack中提供了以下DLL欺骗路径:
C:\Users\John\AppData\Local\slack\app-4.6.0\WINSTA.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\LINKINFO.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\ntshrui.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\srvcli.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\cscapi.dll
C:\Users\John\AppData\Local\slack\app-4.6.0\KBDUS.DLL
Microsoft团队中提供了以下DLL欺骗路径:
C:\Users\John\AppData\Local\Microsoft\Teams\current\WINSTA.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\LINKINFO.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\ntshrui.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\srvcli.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\cscapi.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\WindowsCodecs.dll
C:\Users\John\AppData\Local\Microsoft\Teams\current\TextInputFramework.dll
Visual Studio代码中提供了以下DLL欺骗路径:
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\WINSTA.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\LINKINFO.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\ntshrui.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\srvcli.dll
C:\Users\John\AppData\Local\Programs\Microsoft VS Code\cscapi.dll
此外,由于硬编码的调用,我发现程序使用
NetShareEnum
并NetShareGetInfo
提供了覆盖形式的DLL的功能。我已经使用Ghidra和PoC验证了此行为。cscapi.dll
LoadLibrary
结论
提醒一下,DLL拦截是一种方法,攻击者可以通过该方法来干扰已签名/受信任的应用程序中的代码执行。我创建了一些工具来帮助自动化DLL拦截路径检测。使用此工具,我在Slack,Microsoft Teams和Visual Studio Code中发现了DLL拦截路径。
我注意到这三个应用程序的DLL拦截路径重叠并调查了原因。我强调了我理解这种巧合的方法。我了解了延迟加载DLL的知识,并发现了两个API调用,它们使得可以在调用它们的任何程序中拦截DLL:
NetShareEnum
负载cscapi.dll
NetShareGetInfo
负载cscapi.dll
感谢您抽出宝贵的时间阅读本文,希望您对Windows API,Ghidra,ProcMon,DLL和DLL拦截有所了解。
链接
谢谢同事Daniel Heinsen(
@hotnops
),Lee Christensen(@tifkin_
)和Matt Hand(@matterpreter
)向Ghidra / ProcMon提供帮助!
检查公共PoC以用于渗透测试