Windows应急响应 - 22 DLL劫持与侧加载

Windows应急响应/22-DLL劫持与侧加载

tags:: #Windows应急响应 #持久化 #DLL劫持 #DLLHijacking #SideLoading
category:: 持久化与后门检测
difficulty:: 高级
platform:: Windows 7/10/11, Server 2012-2022

概述

DLL Hijacking(DLL劫持)和DLL Side-Loading(DLL侧加载)利用Windows DLL搜索机制的弱点实现代码执行

攻击者将恶意DLL放置在特定位置,使合法程序加载执行恶意代码

MITRE ATT&CK:

T1574.001 - Hijack Execution Flow: DLL Search Order Hijacking

T1574.002 - Hijack Execution Flow: DLL Side-Loading

优势: 恶意代码在合法签名进程上下文中执行,绕过白名单和EDR

Linux对比: 19-LD_PRELOAD劫持 是Linux下的类似机制

一、Windows DLL搜索顺序

1.1 标准搜索顺序(SafeDllSearchMode启用时)

Windows加载DLL时按以下顺序搜索(6步):

1
2
3
4
5
6
7
8
1. DLL重定向 / API Set / SxS Manifest(.local / manifest)
2. Known DLLs(HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs)
3. 应用程序所在目录(EXE同目录)
4. 系统目录 C:\Windows\System32
5. 16位系统目录 C:\Windows\System
6. Windows目录 C:\Windows
7. 当前工作目录 (CWD)
8. PATH环境变量中的目录

SafeDllSearchMode = 1(默认启用): CWD在System32之后搜索

SafeDllSearchMode = 0: CWD在System32之前搜索(更危险)

注册表控制:

1
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

1.2 Known DLLs保护机制

Windows维护一个”已知DLL”列表,这些DLL只从System32加载:

1
2
# 查看Known DLLs列表
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"

常见Known DLLs包括: kernel32.dll, user32.dll, ntdll.dll, advapi32.dll等

Known DLLs不受搜索顺序影响,始终从System32加载

: 不在Known DLLs列表中的DLL仍然可以被劫持

1.3 DLL加载API差异

不同的加载API使用不同的搜索策略:

1
2
3
4
5
6
7
8
9
10
// LoadLibrary - 使用标准搜索顺序
LoadLibrary("helper.dll");

// LoadLibraryEx with LOAD_WITH_ALTERED_SEARCH_PATH
// 从指定目录开始搜索
LoadLibraryEx("C:\\App\\helper.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

// SetDllDirectory - 修改搜索顺序
SetDllDirectory("C:\\custom\\path");
LoadLibrary("helper.dll"); // 会优先搜索custom\path

二、DLL劫持攻击手法

2.1 应用程序目录劫持(Application Directory Hijacking)

原理: 将恶意DLL放在目标EXE的同目录下

如果该DLL不在Known DLLs中,且EXE在Step 3时找到,则加载恶意版本

常见可劫持目标:

1
2
3
4
5
6
7
8
# 第三方软件常见的可劫持DLL
version.dll — 几乎所有程序都会加载
dbghelp.dll — 很多程序导入
wtsapi32.dll — Terminal Service API
dwmapi.dll — Desktop Window Manager
uxtheme.dll — Theme related
winmm.dll — Multimedia API
crypt32.dll — 不在所有版本的KnownDLLs中

示例: 劫持某应用的version.dll:

1
2
3
4
5
6
7
8
9
# 确认目标应用加载version.dll
# 使用Process Monitor筛选:
# Process Name = target.exe
# Operation = CreateFile
# Path contains = .dll
# Result = NAME NOT FOUND (这些就是可劫持的)

# 将恶意DLL放在应用目录
Copy-Item evil.dll "C:\Program Files\TargetApp\version.dll"

2.2 DLL侧加载(Side-Loading)

DLL Side-Loading是DLL劫持的一种特殊形式:

攻击者将签名的合法程序恶意DLL一起部署

合法程序启动时自动加载同目录的恶意DLL

因为宿主进程有合法签名,所以能绕过白名单

经典案例:

1
2
3
4
# 使用合法的Windows Defender组件
C:\Users\Public\Staging\
MsMpEng.exe ← 合法的Windows Defender EXE(已签名)
mpsvc.dll ← 恶意DLL(MsMpEng.exe会加载这个DLL)

常被APT使用的合法程序+DLL对:

1
2
3
4
5
6
7
8
9
程序                          劫持的DLL
──────────────────────────────────────────
MsMpEng.exe mpsvc.dll
vmtoolsd.exe vmtools.dll
Notepad++\updater.exe libcurl.dll
GoogleUpdate.exe goopdate.dll
WinSCP.exe DragExt.dll
7zFM.exe 7z.dll
python.exe python3.dll

2.3 Phantom DLL劫持

利用程序尝试加载不存在的DLL的行为

程序设计时引用了某个DLL,但该DLL在当前系统上不存在

Windows按搜索顺序逐一查找,最终失败(NAME NOT FOUND)

攻击者在搜索路径的某个位置放置同名DLL

发现Phantom DLL的方法:

1
2
3
4
5
6
7
# 使用Process Monitor筛选
# Operation: CreateFile
# Result: NAME NOT FOUND
# Path: ends with .dll

# 自动化发现(需要procmon命令行版本)
# 运行目标程序后分析PML日志

常见的Phantom DLL:

1
2
3
4
5
6
# Windows系统中常见的不存在但会被搜索的DLL
WptsExtensions.dll — Explorer.exe
IKEEXT.dll — svchost.exe (IKEEXT服务)
wlbsctrl.dll — svchost.exe (某些网络服务)
Tsmsisrv.dll — SessionEnv服务
TSVIPSrv.dll — SessionEnv服务

2.4 DLL代理(DLL Proxying)

恶意DLL将合法DLL的导出函数转发(proxy),同时执行恶意代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// evil_version.dll — 代理version.dll的所有导出函数
// 使用pragma comment实现导出转发

#pragma comment(linker, "/export:GetFileVersionInfoA=C:\\Windows\\System32\\version.GetFileVersionInfoA")
#pragma comment(linker, "/export:GetFileVersionInfoByHandle=C:\\Windows\\System32\\version.GetFileVersionInfoByHandle")
#pragma comment(linker, "/export:GetFileVersionInfoExA=C:\\Windows\\System32\\version.GetFileVersionInfoExA")
#pragma comment(linker, "/export:GetFileVersionInfoExW=C:\\Windows\\System32\\version.GetFileVersionInfoExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExA=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExA")
#pragma comment(linker, "/export:GetFileVersionInfoSizeExW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeExW")
#pragma comment(linker, "/export:GetFileVersionInfoSizeW=C:\\Windows\\System32\\version.GetFileVersionInfoSizeW")
#pragma comment(linker, "/export:GetFileVersionInfoW=C:\\Windows\\System32\\version.GetFileVersionInfoW")
#pragma comment(linker, "/export:VerFindFileA=C:\\Windows\\System32\\version.VerFindFileA")
#pragma comment(linker, "/export:VerFindFileW=C:\\Windows\\System32\\version.VerFindFileW")
#pragma comment(linker, "/export:VerInstallFileA=C:\\Windows\\System32\\version.VerInstallFileA")
#pragma comment(linker, "/export:VerInstallFileW=C:\\Windows\\System32\\version.VerInstallFileW")
#pragma comment(linker, "/export:VerLanguageNameA=C:\\Windows\\System32\\version.VerLanguageNameA")
#pragma comment(linker, "/export:VerLanguageNameW=C:\\Windows\\System32\\version.VerLanguageNameW")
#pragma comment(linker, "/export:VerQueryValueA=C:\\Windows\\System32\\version.VerQueryValueA")
#pragma comment(linker, "/export:VerQueryValueW=C:\\Windows\\System32\\version.VerQueryValueW")

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID reserved) {
if (reason == DLL_PROCESS_ATTACH) {
// 恶意payload在此执行
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MaliciousFunction, NULL, 0, NULL);
}
return TRUE;
}

这样原始程序功能不受影响,不会崩溃,更加隐蔽

三、检测方法

3.1 Process Monitor实时监控

Process Monitor是发现DLL劫持的最佳工具:

1
2
3
4
5
6
7
8
9
设置过滤器:
Operation = CreateFile
Path ends with .dll
Result = SUCCESS

关注:
- 从非标准路径加载的系统DLL
- 从用户可写目录加载的DLL
- 加载失败(NAME NOT FOUND)后从其他位置成功加载

命令行使用Procmon:

1
2
3
4
5
Procmon.exe /AcceptEula /Quiet /Minimized /BackingFile C:\procmon.pml
:: 运行一段时间后
Procmon.exe /Terminate
:: 转换为CSV分析
Procmon.exe /OpenLog C:\procmon.pml /SaveAs C:\procmon.csv

3.2 sigcheck验证DLL签名

检查特定目录下所有DLL的签名:

1
2
3
4
5
:: 检查Program Files下所有未签名DLL
sigcheck.exe -u -e -s "C:\Program Files" > unsigned_dlls.txt

:: 检查特定应用目录
sigcheck.exe -u -e "C:\Program Files\TargetApp\*.dll"

PowerShell批量检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 扫描常见安装目录中的未签名DLL
$paths = @(
"C:\Program Files",
"C:\Program Files (x86)",
"C:\ProgramData"
)

foreach ($basePath in $paths) {
Get-ChildItem $basePath -Recurse -Filter "*.dll" -ErrorAction SilentlyContinue | ForEach-Object {
$sig = Get-AuthenticodeSignature $_.FullName -ErrorAction SilentlyContinue
if ($sig.Status -ne 'Valid') {
[PSCustomObject]@{
Path = $_.FullName
SigStatus = $sig.Status
LastWriteTime = $_.LastWriteTime
Size = $_.Length
}
}
}
} | Sort-Object LastWriteTime -Descending | Format-Table -AutoSize -Wrap

3.3 对比分析

将DLL与已知正常版本进行Hash对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 对比某应用目录下的DLL与System32中的同名DLL
$appDir = "C:\Program Files\TargetApp"

Get-ChildItem $appDir -Filter "*.dll" | ForEach-Object {
$sysDll = "C:\Windows\System32\$($_.Name)"
if (Test-Path $sysDll) {
$appHash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
$sysHash = (Get-FileHash $sysDll -Algorithm SHA256).Hash

if ($appHash -ne $sysHash) {
Write-Host "[!] MISMATCH: $($_.Name)" -ForegroundColor Red
Write-Host " App: $appHash"
Write-Host " Sys: $sysHash"

# 检查版本信息
$appVer = (Get-ItemProperty $_.FullName).VersionInfo
$sysVer = (Get-ItemProperty $sysDll).VersionInfo
Write-Host " App Version: $($appVer.FileVersion)"
Write-Host " Sys Version: $($sysVer.FileVersion)"
}
}
}

3.4 Sysmon Image Load监控

Sysmon Event ID 7可以监控DLL加载:

1
2
3
4
5
6
7
8
9
<!-- Sysmon配置: 监控可疑DLL加载 -->
<ImageLoad onmatch="include">
<!-- 从用户目录加载的DLL -->
<ImageLoaded condition="contains">C:\Users\</ImageLoaded>
<ImageLoaded condition="contains">C:\ProgramData\</ImageLoaded>
<ImageLoaded condition="contains">\Temp\</ImageLoaded>
<!-- 未签名的DLL -->
<Signed condition="is">false</Signed>
</ImageLoad>

查询Sysmon DLL加载事件:

1
2
3
4
5
6
7
8
9
10
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-Sysmon/Operational'
Id = 7
} -MaxEvents 100 | Where-Object {
$_.Message -match "Signed:\s+false" -or
$_.Message -match "C:\\Users\\"
} | Select-Object TimeCreated,
@{N='Process';E={($_.Message -split "`n" | Select-String "Image:").ToString().Trim()}},
@{N='LoadedDLL';E={($_.Message -split "`n" | Select-String "ImageLoaded:").ToString().Trim()}} |
Format-Table -AutoSize -Wrap

3.5 自动化DLL劫持扫描脚本

检查正在运行的进程加载的可疑DLL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 扫描运行中进程的DLL加载情况
Get-Process | ForEach-Object {
$proc = $_
try {
$modules = $proc.Modules | Where-Object {
# 排除系统目录
$_.FileName -and
$_.FileName -notmatch "^C:\\Windows\\" -and
$_.FileName -notmatch "^C:\\Program Files.*\\Microsoft" -and
$_.FileName -match "\.dll$"
}

foreach ($mod in $modules) {
$sig = Get-AuthenticodeSignature $mod.FileName -ErrorAction SilentlyContinue
if ($sig.Status -ne 'Valid') {
[PSCustomObject]@{
Process = $proc.ProcessName
PID = $proc.Id
DLL = $mod.FileName
SigStatus = $sig.Status
DLLSize = $mod.ModuleMemorySize
}
}
}
} catch {}
} | Sort-Object Process | Format-Table -AutoSize -Wrap

四、常见目标与案例

4.1 APT41 — ShadowPad

ShadowPad后门常使用DLL Side-Loading:

1
2
合法程序: AppLaunch.exe (Microsoft ClickOnce)
恶意DLL: mscoree.dll

部署在: C:\ProgramData\<random_folder>\

通过计划任务或服务启动合法EXE

4.2 PlugX恶意软件

PlugX是使用DLL Side-Loading最多的恶意软件家族之一:

1
2
3
4
# 常见的PlugX Side-Loading组合
CamMute.exe + DivXVSMULTICodec.dll
Mc.exe (McAfee) + McUtil.dll
hkcmd.exe (Intel) + hkcmd.dll

三件套: 合法EXE + 恶意DLL + 加密payload文件

4.3 Windows系统DLL劫持提权

某些Windows系统服务存在DLL劫持漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# IKEEXT服务 (IKE and AuthIP IPsec Keying Modules)
# 加载不存在的 wlbsctrl.dll
# 如果攻击者能在PATH中的可写目录放置DLL,可获得SYSTEM权限

# 检查PATH中的可写目录
$env:PATH -split ';' | ForEach-Object {
if ($_ -and (Test-Path $_)) {
$acl = Get-Acl $_
$userAccess = $acl.Access | Where-Object {
$_.IdentityReference -match "Users|Everyone|Authenticated" -and
$_.FileSystemRights -match "Write|FullControl|Modify"
}
if ($userAccess) {
Write-Host "[!] Writable PATH dir: $_" -ForegroundColor Red
$userAccess | ForEach-Object {
Write-Host " $($_.IdentityReference): $($_.FileSystemRights)" -ForegroundColor Yellow
}
}
}
}

五、清除与修复

5.1 识别并删除恶意DLL

确认恶意DLL后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$malDll = "C:\Program Files\TargetApp\version.dll"

# 1. 检查哪些进程加载了该DLL
Get-Process | Where-Object {
$_.Modules.FileName -contains $malDll
} | Select-Object ProcessName, Id, Path

# 2. 终止相关进程
Get-Process | Where-Object {
$_.Modules.FileName -contains $malDll
} | Stop-Process -Force

# 3. 收集证据
Copy-Item $malDll "C:\IR_Evidence\" -Force
Get-FileHash $malDll -Algorithm SHA256 | Format-List

# 4. 删除恶意DLL
Remove-Item $malDll -Force

5.2 防御措施

启用SafeDllSearchMode(确认已启用):

1
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager" /v SafeDllSearchMode

应用开发层面:

使用绝对路径调用LoadLibrary

使用SetDefaultDllDirectories限制搜索范围

启用DLL重定向(.local文件)

系统层面:

监控非标准路径的DLL加载(Sysmon Event 7)

定期扫描应用目录中的未签名DLL

限制用户可写目录

六、关联检查

DLL劫持/侧加载通常与以下内容关联:

20-Windows服务后门 — 服务DLL是劫持重点目标

25-IFEO与AppInit-DLLs后门 — AppInit_DLLs是另一种DLL注入

23-COM劫持 — COM劫持也涉及DLL替换

18-Registry-Run后门 — 可能通过Run Key启动宿主EXE

19-LD_PRELOAD劫持 — Linux等效机制

七、应急响应Checklist

[ ] 使用Process Monitor监控DLL搜索行为

[ ] 使用sigcheck扫描应用目录中的未签名DLL

[ ] 检查System32目录外的系统DLL副本

[ ] 对比可疑DLL与已知正常版本的Hash

[ ] 检查Sysmon Event ID 7中的异常DLL加载

[ ] 检查PATH环境变量中的用户可写目录

[ ] 检查Known DLLs列表是否被篡改

[ ] 扫描ProgramData/Users目录下的EXE+DLL组合

[ ] 验证SafeDllSearchMode注册表值

[ ] 收集证据后删除恶意DLL并终止相关进程


上一章 目录 下一章
21-WMI事件订阅后门 Windows应急响应 23-COM劫持