DLL劫持自动化检测

DLL劫持自动化检测

简介

本文将介绍动态链接库(DLL)搜索顺序劫持的概念,以及如何实现在Windows上的持久化攻击。MITRE ATT&C++K对DLL搜索顺序劫持的描述详见DLL Search Order Hijacking (T1038)

DLL劫持对攻击者很有用,原因有很多,但本文会重点介绍与开机自启程序结合使用时的持久化攻击。例如,由于Slack和Microsoft Teams开机自启(默认情况下),因此,只要用户登录,这些应用程序中的DLL劫持将允许攻击者能够持久访问攻击目标。

在介绍DLL、DLL搜索顺序和DLL劫持的概念后,我会阐述DLL劫持自动化检测的过程https://github.com/slyd0g/DLLHijackTest。本文将涉及Slack、Microsoft Teams以及Visual Studio Code相关的DLL劫持检测。

最后,我注意到很多DLL劫持会同时作用于多个程序。我调查了底层原因,发现调用某些位于C:WindowsSystem32下的Windows API的程序容易成为DLL劫持的目标。

这里我要感谢我的同事,Josiah Massari (@Airzero24)。是他最早发现的这些DLL劫持案例并解释其方法和原理,促成了我将检测做成自动化。

DLL是什么

DLL是一个包含可被多个程序同时使用的代码和数据的库。(定义源于Microsoft

Windows程序可以调用LoadLibrary*系列函数来使用DLL中的功能。程序可以引用自定义的DLL,也可以引用System32中Windows自带的DLL。开发者可以加载System32中的DLL,使其程序使用Windows的现成功能,而不必重新造轮子。

例如,开发者需要发起HTTP请求时,可以使用WinHTTP库(winhttp.dll),而无需用原始套接字开发并实现HTTP功能。

DLL搜索顺序和劫持

由于DLL也是存储在磁盘上的文件,那么程序是如何知道从哪里加载DLL的呢?Microsoft在详细记录了DLL搜索顺序。

自Windows XP SP2起,安全DLL搜索模式默认开启(HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerSafeDllSearchMode)。当安全模式启用时,搜索顺序如下:

  1. 程序安装目录。
  2. 系统目录。使用GetSystemDirectory获取该目录路径。
  3. 16位系统目录。没有函数可以获取该目录路径,但该目录会被搜索。
  4. Windows目录。使用GetWindowsDirectory获取该目录路径。
  5. 当前目录。
  6. PATH环境变量中的目录。注意,这不包括App Paths注册表键指定的程序路径。App Paths键不会参与DLL搜索路径的计算。

系统可包含动态链接库(DLL)的多个版本。程序可通过指定完整路径或使用比如manifest的其他机制来控制加载特定的DLL。(定义源于Microsoft

如果程序没有加载指定的DLL,Windows会默认执行上述的DLL搜索流程。DLL搜索的第一顺位,即程序的安装目录,是攻击者最感兴趣的。

如果开发者想要从C:WindowsSystem32加载 DLL,但并没有写清楚让程序这么做,那么程序安装目录下的恶意DLL就会优先于System32下的合法DLL被加载。这里恶意DLL指的是DLL劫持,攻击者用来在受信/签名程序中加载恶意代码

DLL劫持持久化利用

当目标程序/服务启动并且恶意DLL被植入到了关键位置,DLL劫持即可用于持久化攻击。我的同事@Airzero24,发现了Microsoft OneDrive、 Microsoft Teams、以及Slack中针对userenv.dll的劫持。

这类程序被盯上的原因是,他们默认都会随着Windows开机启动。任务管理器中可以看到:

747358_FKV9XXNFVS8TY3Y

为了验证DLL劫持,我写了一段运行Cobalt Strike Beacon的DLL Shellcode,命名为userenv.dll,并且复制到目标程序的安装目录。运行程序,我看到肉鸡上线了。

747358_UM5CS4JXJ5N4RDH

查看Process Explorer,可以确认恶意DLL确实被目标程序加载了。

747358_GX7V7CU37U26K5U

DLL劫持自动化检测

确认先前已知的DLL劫持之后,我想看看是否可以找到其他的DLL劫持。

测试代码在此:https://github.com/slyd0g/DLLHijackTest

案例研究:Slack

首先,运行Process Monitor(ProcMon),过滤规则如下:

  • Process Nameslack.exe
  • Result包含NOT FOUND
  • Path.dll结尾
747358_GY8WGMCJ2482C6B

接下来,运行Slack,留意Slack搜索但无法找到的DLL。

747358_AHZG4VZ69VMKV72

从ProcMon中导出数据,方便用PowerShell处理。

用当前加载shellcode的DLL,我无法轻易找出被Slack加载的DLL的名字。我写了一个新的使用了GetModuleHandleExGetModuleFileNameDLL的DLL,输出加载DLL的名字到文本文件

接下来就是解析CSV得到DLL路径列表,遍历路径列表,把我的测试DLL复制到特定路径,运行目标进程,退出目标进程,删除测试DLL。如果测试DLL被成功加载,其文件名会写入结果文件。

上述步骤完成后,会得到一个有效DLL劫持的列表。

在我的DLLHijackTest项目中的PowerShell脚本可以处理所有步骤。脚本的输入参数有:ProcMon生成的CSV文件的路径,恶意DLL的路径,目标程序的路径,以及其他你想要传递给目标程序的参数。

747358_6RZHECPG8Z5WWF7
747358_V4GZKEGESJTZ2QG

几分钟后,打开“恶意”DLL中指定的文本文件,发现以下针对Slack的DLL劫持:

PS C:UsersJohnDesktop> Get-PotentialDLLHijack -CSVPath .Logfile.CSV -MaliciousDLLPath .DLLHijackTest.dll -ProcessPath "C:UsersJohnAppDataLocalslackslack.exe"C:UsersJohnAppDataLocalslackapp-4.6.0WINSTA.dllC:UsersJohnAppDataLocalslackapp-4.6.0LINKINFO.dllC:UsersJohnAppDataLocalslackapp-4.6.0ntshrui.dllC:UsersJohnAppDataLocalslackapp-4.6.0srvcli.dllC:UsersJohnAppDataLocalslackapp-4.6.0cscapi.dllC:UsersJohnAppDataLocalslackapp-4.6.0KBDUS.DLL

案例研究:Microsoft Teams

重复上述过程:

  1. 用ProcMon找出可能的DLL劫持,导出至CSV。
  2. 找出目标进程的路径。
  3. 找出传入进程的参数。
  4. 传入参数,运行Get-PotentialDLLHijack.ps1

发现以下针对Microsoft Teams的DLL劫持:

PS C:UsersJohnDesktop> Get-PotentialDLLHijack -CSVPath .Logfile.CSV -MaliciousDLLPath .DLLHijackTest.dll -ProcessPath "C:UsersJohnAppDataLocalMicrosoftTeamsUpdate.exe"-ProcessArguments '--processStart "Teams.exe"'C:UsersJohnAppDataLocalMicrosoftTeamscurrentWINSTA.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentLINKINFO.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentntshrui.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentsrvcli.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentcscapi.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentWindowsCodecs.dllC:UsersJohnAppDataLocalMicrosoftTeamscurrentTextInputFramework.dll

注意:我这里修改了下PowerShell脚本,终止的进程是Teams.exe,启动的进程是Update.exe

案例研究:Visual Studio Code

重复以上关键步骤,发现针对Visual Studio Code的劫持,如下:

PS C:UsersJohnDesktop> Get-PotentialDLLHijack -CSVPath .Logfile.CSV -MaliciousDLLPath .DLLHijackTest.dll -ProcessPath "C:UsersJohnAppDataLocalProgramsMicrosoft VS CodeCode.exe"C:UsersJohnAppDataLocalProgramsMicrosoft VS CodeWINSTA.dllC:UsersJohnAppDataLocalProgramsMicrosoft VS CodeLINKINFO.dllC:UsersJohnAppDataLocalProgramsMicrosoft VS Codentshrui.dllC:UsersJohnAppDataLocalProgramsMicrosoft VS Codesrvcli.dllC:UsersJohnAppDataLocalProgramsMicrosoft VS Codecscapi.dll

共有DLL劫持

我发现Slack、Microsoft Teams和Visual Studio Code都会出现以下DLL劫持:

  • WINSTA.dll
  • LINKINFO.dll
  • ntshrui.dll
  • srvcli.dll
  • cscapi.dll

我觉得很有趣,尝试挖掘造成此现象的底层原因。

方法论:理解共有DLL劫持

我观察了下Slack加载WINSTA.dllLINKINFO.dllntshrui.dllsrvcli.dllcscapi.dll的栈轨迹。

延迟加载DLL

我注意到当WINSTA.dllLINKINFO.dllntshrui.dllsrvcli.dllcscapi.dll被加载时栈轨迹存在共性。

747358_4NFZGJT23ZZRQDT
747358_WGG78AY5BX4QVYR
747358_FJXE253JAQH75WN

栈轨迹都调用了_tAIlMerge_<dllname>_dlldelayLoadHelper2,之后还调用了LdrResolveDelayLoadedAPI。三个程序都出现了这种情况。

我认为这种情况与延迟加载DLL有关。当WINSTA.dll被加载时,从栈轨迹可以看到,wtsapi32.dll导致了延迟加载。

Ghidra中打开wtsapi32.dll,设置Search -> For StRings -> Filter: WINSTA.dll。双击查找的结果会看到其内存地址。

747358_RHR54JXACQYSNNR

右键点击内存地址,可以找到对该地址的引用。

747358_HYKW2FPC9GJAGWB

我们看到WINSTA.dll被传递至名为ImgDelayDescr的结构体。查看该结构体的说明文档,可以确定其与延迟加载DLL有关。

typedef struct ImgDelayDescr {
     DWORD        gRATtrs;        // attributes
     RVA          rvaDLLName;     // RVA to dll name
     RVA          rvaHmod;        // RVA of module handle
     RVA          rvaIAT;         // RVA of the IAT
     RVA          rvaINT;         // RVA of the INT
     RVA          rvaBoundIAT;    // RVA of the optional bound IAT
     RVA          rvaUnloadIAT;   // RVA of optional copy of original IAT
     DWORD        dwTimeStamp;    // 0 if not bound,
                                  // O.W. date/time stamp of DLL bound to (Old BIND)
     } ImgDelayDescr, * PImgDelayDescr;

该结构体可被传递至__delayLoadHelper2,后者会通过LoadLibrary/GetProcAddress加载特定DLL,并且会修正延迟加载导入地址表(IAT)中导入函数的地址。

FARPROC WINAPI __delayLoadHelper2(
     PCImgDelayDescr pidd,  //Const pointer to a ImgDelayDescr struct
     FARPROC * ppfnIATEntry //A pointer to the slot in delay load IAT
 );

查找其他对ImgDelayDescr结构体的引用,发现__delayLoadHelper2调用,后者之后调用了ResolveDelayLoadedAPI。为方便理解,我重命名了函数名、类型和变量。

747358_N2YM3JJU6D9DVA7

很好!这与我们之前在Slack加载WINSTA.dll时在ProcMon中看到的栈轨迹一致。

747358_UYBEEYT5HHUEM5D

WINSTA.dllLINKINFO.dllntshrui.dllsrvcli.dll的行为是一致的,每个延迟加载的DLL间主要的不同点是“父级”DLL。在三个程序中:

  • wtsapi32.dll延迟加载了WINSTA.dll
  • shell32.dll延迟加载了shell32.dll
  • LINKINFO.dll延迟加载了ntshrui.dll
  • ntshrui.dll延迟加载了srvcli.dll

看到有意思的点了吗?shell32.dll加载了LINKINFO.dllLINKINFO.dll加载了ntshrui.dllntshrui.dll最后加载了srvcli.dll。 最后我们看到共有的针对cscapi.dll的劫持。

NetShareGetInfo和NetShareEnum中的DLL劫持

观察Slack加载cscapi.dll的栈轨迹,看到调用了LoadLibraryExW,后者似乎来源于srvcli.dll

747358_X9FKS54MKF9ZK85

Ghidra中打开srvcli.dll,设置Search -> For Strings -> Filter: cscapi.dll。双击查找的结果并跟踪引用,找到LoadLibrary调用。

747358_T73R9WMM7SMDJ7C

重命名包含LoadLibrary的函数,跟踪引用可以看到两个函数地址:

747358_P6JP6UKA5URUAYT
747358_NE867QCTV637XQY

我使用调用NetShareEnumNetShareGetInfo的PoC程序验证了该过程:

747358_PERWHAJ7T8EDUZK
747358_Y7Q7BE8MM57XR5J

结果

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 Teams中,存在以下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 Code中,存在以下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

另外,我发现使用NetShareEnumNetShareGetInfo的程序会引入cscapi.dll劫持,这是由硬编码的LoadLibrary调用导致的,并且我用Ghidra和PoC确认了该结论。

结论

简要回顾下,DLL劫持是攻击者在签名/受信的程序中执行代码的一种方法。我开发了自动检测DLL劫持的工具。利用此工具,我发现了Slack、Microsoft Teams和Visual Studio Code中的DLL劫持。

我注意到这三个程序存在共有DLL劫持的情况,并调查了根本原因。我重点介绍了研究的方法,并了解了延迟加载DLL,以及定位到了两个API调用,这两个API会引入DLL劫持到调用它们的程序中:

  • NetShareEnum加载cscapi.dll
  • NetShareGetInfo加载cscapi.dll

感谢花时间阅读本文,希望你能学到一些关于Windows API、Ghidra、ProcMon、DLL和DLL劫持的知识!

标签