揭秘WinSxS:与HelloJackHunter并肩探索DLL劫持与自动化狩猎

本文深入探讨Windows Side-by-Side(WinSxS)机制与DLL劫持技术,介绍自动化工具HelloJackHunter的开发与应用,涵盖漏洞挖掘、函数导出代理及实战检测方法,为安全研究人员提供完整的技术实现路径。

与HelloJackHunter并肩:揭秘WinSxS的奥秘

本文关于Windows Side-by-Side加载的研究是我在2023年底圣诞节与新年之间的空闲时间深入进行的。众所周知,动态链接库(DLL)侧加载/DLL劫持并非新技术,Windows Side-by-Side(WinSxS)也是如此;然而,从对抗性技术角度看,侧加载在建立初始访问、持久化、权限提升或环境内执行方面非常实用。我在阅读John Carroll关于ExpLoading(一种从当前目录劫持搜索顺序的技术)的精彩文章后开始了这项研究。当时,我也在研究WinSxS,因为有人在博客文章中提到过,但该博客缺乏任何概念验证代码,这让我深入探索。因此,许多工具和博客文章应运而生;在研究过程中,我发现了另一位研究人员过去深入研究DLL劫持和函数导出及代理的工作:Xenov - 演讲/工具。在进行这项研究时,我借鉴了Aaron的代码并用Python3重写,已在此处分支。不过,该工具的初衷与HelloJackHunter类似,因此重写是合理的。基于他人的工作是我们学习和改进的方式。

侧加载/劫持101

动态链接库(DLL)搜索顺序劫持,通常简称为DLL劫持,通过外部DLL利用应用程序的执行流程。通过劫持用于加载合法内容的搜索顺序,可以强制应用程序加载恶意DLL。

当易受攻击的应用程序(我发现在野外有一些非WinSxS二进制文件,包括2020年我为Nvidia获得的CVE)以提升权限运行时,任何加载到其中的恶意DLL都会继承这些提升权限,从而实现权限提升。应用程序的行为通常不受干扰,因为恶意DLL被设计为无缝加载它们替换的合法DLL,或者在DLL路径未明确定义的情况下。

这种隐蔽的DLL启动能力提供了无数机会。在Rundll32不实用的场景中,转移受信任二进制文件的执行流程,遵循"靠山吃山"(LOLBINS)原则,提供了从不同位置部署恶意DLL并将其注入合法进程的手段。

什么是WinSxS?

WinSxS代表"Windows Side by Side",是位于C:\Windows\WinSxS的目录,Windows在其中存储安装操作系统所需的文件以及这些文件的备份或不同版本。在Windows环境中,有大量子文件夹和几乎所有二进制文件的副本,这使其易于被利用。因此,许多LOLBINS可能绕过执行策略。

WinSxS在处理更新中扮演关键角色。安装更新时,系统文件的新版本会添加到该文件夹中。这确保应用程序的最新版本可用,有助于整体系统安全性和性能。这也意味着我们可以利用合法二进制文件和多个版本的这些二进制文件用于恶意目的,暂时将DLL劫持放在一边;这意味着也有多个PowerShell和cmd.exe的副本;以下是一个来自W10虚拟机的示例:

1
2
3
4
5
6
c:\Windows\WinSxS\amd64_microsoft-windows-commandprompt_31bf3856ad364e35_10.0.22621.2506_none_6ae72c5495fc1170\cmd.exe
c:\Windows\WinSxS\amd64_microsoft-windows-commandprompt_31bf3856ad364e35_10.0.22621.2506_none_6ae72c5495fc1170\f\cmd.exe
c:\Windows\WinSxS\amd64_microsoft-windows-commandprompt_31bf3856ad364e35_10.0.22621.2506_none_6ae72c5495fc1170\r\cmd.exe
c:\Windows\WinSxS\amd64_microsoft-windows-powershell-exe_31bf3856ad364e35_10.0.22621.2506_none_48f0644b7dd22b85\powershell.exe
c:\Windows\WinSxS\amd64_microsoft-windows-powershell-exe_31bf3856ad364e35_10.0.22621.2506_none_48f0644b7dd22b85\f\powershell.exe
c:\Windows\WinSxS\amd64_microsoft-windows-powershell-exe_31bf3856ad364e35_10.0.22621.2506_none_48f0644b7dd22b85\r\powershell.exe

在您的环境中,这些路径可能有所不同;因此,调查WinSxS二进制文件的版本总是值得的,并可能为环境内执行提供额外选项。我在特定环境中看到的是利用替代路径绕过弱应用程序允许列表实现的能力,C:\Windows\System32可能被阻止,WinSxS可能会帮助您。

将两者结合

现在我们对WinSxS和DLL劫持有了更多了解,如何将两者结合形成自动化识别和狩猎工作流程?典型的工作流程如下:

  • 在WinSxS中狩猎二进制文件
  • 映射从$currentdir调用的DLL
  • 运行HelloJackHunter并在for循环中指向DLL

狩猎可用二进制文件相对容易,只需要一些PowerShell开始;建议是在您自己的开发系统上进行研究,并在目标环境中复制更多,因为提供的单行命令都不是为了操作安全而设计的,更多是为了突出快速获取所需内容的路径。

映射可用二进制文件

有许多方法狩猎二进制文件。简单在资源管理器中搜索*.exe将给您一个GUI列表,或使用类似everything.exe的工具将允许您复制和导出路径,或仅使用PowerShell如下并指向WinSxS目录:

1
GCI -Path C:\Windows\WinSxS -Recurse -Filter *.exe | Select -First 20 | Select Name, FullName, @{l='FileVersion';e={[System.Version]($_.VersionInfo.FileVersion)}} | Group Name | ForEach-Object { $_.Group | Sort-Object -Property FileVersion -Descending | Select-Object -First 1 }

我在Windows 10虚拟机上运行了这个命令,生成了一个很好的可用二进制文件列表,您可以在此处下载并玩耍(https://github.com/ZephrFish/HelloJackHunter/blob/main/WinSxSBins.txt)。或者,如果您想自己运行上述命令,我将解释该命令的作用:

  • GCI -Path C:\Windows\WinSxS -Recurse -Filter *.exe:命令的这一部分使用GCI别名作为Get-ChildItem cmdlet。它在C:\Windows\WinSxS目录和子目录中递归搜索(-recurse)所有具有.exe扩展名的文件。
  • | Select -first 20:命令的这一部分使用Select-Object模块/cmdlet从先前步骤获取的文件列表中选择前20个项目;您可以删除此选项以转储所有内容并使用类似Out-String导出到文件。
  • | select name,fullname,@{l='FileVersion';e={[SYSTEM.version]($_.versioninfo.fileversion)}}:再次使用Select-Object cmdlet从列表中的每个项目选择特定属性。它直接选择Name和FullName属性。此外,它使用计算表达式(@{l=‘FileVersion’;e={SYSTEM.version}})创建一个名为FileVersion的自定义属性。该表达式使用($_.versioninfo.fileversion)从每个文件提取文件版本信息,[SYSTEM.version]将其设置为System.Version对象。
  • | group Name:命令的这一部分基于它们的Name属性对项目进行分组。这意味着具有相同名称的文件将被分组在一起。
  • | %{$_.Group | sort -descending fileVersion | select -first 1}:命令的这一部分使用%别名作为ForEach-Object cmdlet迭代每组文件。在每组内,它基于它们的FileVersion属性降序排序文件(sort -descending fileVersion),然后从每组中选择第一个文件(select -first 1)。这有效地从每组中选择版本号最高的文件。

我们可以稍微修改上述内容,仅将路径存储到对象中,我们可以在DLLHiJackChecker脚本中调用;对象如下:

1
2
3
4
5
6
$LatestBinaries = GCI -Path C:\Windows\WinSxS -Recurse -Filter *.exe | 
    Select -First 20 | 
    Select Name, FullName, @{l='FileVersion';e={[System.Version]($_.VersionInfo.FileVersion)}} | 
    Group Name | 
    ForEach-Object { $_.Group | Sort-Object -Property FileVersion -Descending | Select-Object -First 1 } |
    Select-Object -ExpandProperty FullName

虽然这仅限于仅20个二进制文件,但Select -First可以修改为在狩猎时包含任意多个。以下是我之前运行的输出,显示对象存储许多路径:

一旦我们有了可用的列表,下一步是大规模狩猎DLLHiJacking。手动操作,可以利用Process Monitor搜索运行进程中的所有DLL加载和由运行应用程序调用。可以采取以下步骤:

  1. 启动Process Monitor,使用以下过滤器,识别潜在易受攻击的应用程序和二进制文件。

    • Result contains NOT FOUND
    • Path ends with .dll
  2. 将运行列表导出到CSV,然后可以用一些简单的PowerShell解析CSV:

1
2
$logData = Import-Csv C:\PathToFile\log.csv
$logData | Where-Object { $_.Path -like "*\CurrentDirectory\*" } | Select TimeOfDay, Path, Result

一旦获得了可攻击DLL的列表,下一步是批量构建恶意DLL;这就是HelloJackHunter发挥作用的地方。如果手动操作不是您的风格,您可以尝试自动化procmon,但使用PowerShell有点棘手,我从StackOverflow/Google/Git获取了一些代码并将它们混合在一起,稍微修改以在此场景中工作:

 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
# 在后台启动ProcMon
Start-Process "C:\Users\User\procmon.exe" -ArgumentList "/Quiet /Minimized /LoadConfig DLLHijacking.pmc /BackingFile log.pml"

# 从二进制文件文本文件列表启动您的应用程序
$binaryPaths = Get-Content -Path "C:\Users\User\WinSxSBins.txt"
foreach ($binaryPath in $binaryPaths) {
    if (Test-Path $binaryPath) {
        Start-Process $binaryPath -PassThru
    } else {
        Write-Host "Binary path does not exist: $binaryPath"
    }
}

Start-Sleep -Seconds 60

# 终止Procmon以便我们可以检查结果
Start-Process "C:\Users\User\procmon.exe" -ArgumentList "/Terminate"

# 将ProcMon日志转换为CSV以便更容易分析
& "C:\Users\User\procmon.exe" /OpenLog log.pml /SaveAs log.csv /SaveApplyFilter

$logData = Import-Csv log.csv
$result = $logData | Where-Object { $_.Path -like "*\CurrentDirectory\*" } | Select TimeOfDay, Path, Result

# 显示结果
$result

要创建DLLHijacking.pmc,可以采取以下步骤:

  • 打开ProcMon
  • 配置过滤器:
    • 转到Filter -> Filter…或按Ctrl+L。
    • 添加新过滤器,设置如下:
      • Field: Operation
      • Condition: is
      • Value: Load Image
      • Action: Include
    • 如果您正在狩猎非WinSxS二进制文件,那么您可能想添加额外过滤器以排除从中加载DLL的标准和受信任目录,例如C:\Windows\或您认为安全的任何其他目录。
      • Field: Path
      • Condition: excludes
      • Value: C:\Windows\
      • Action: Exclude
    • 应用过滤器。
  • 设置额外选项:
    • 您可以指定列以显示相关信息,如Path、Result和Detail,显示正在访问的特定DLL路径。
  • 保存您的配置:
    • 转到File -> Save Filter…。
    • 将配置保存为.pmc文件,适当命名,例如DLLHijacking.pmc。

HelloJackHunter - 自动化识别

自动化事物总是使狩猎生活更轻松;引入我今天编写和发布的工具:HelloJackHunter,一个对HijackHunter的文字游戏。该工具扫描可用DLL并利用dumpbin.exe提取导出函数,将每个与示例消息框关联。这种映射便于枚举可以利用的函数。此外,它支持各种方法用于函数钩子和执行。

1
2
Checker.ps1 WinSxSBins.txt
HelloJackHunter

通过工作流程运行,第一步是构建可用二进制文件列表,检查它们是否易受攻击,然后执行hellojackhunter导出相关函数。作为高级概念验证,以下代码可用于证明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
#include <windows.h>
#include <iostream>

BOOL APIENTRY DllMain(HMODULE hModule,
                      DWORD  ul_reason_for_call,
                      LPVOID lpReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        std::cout << "DLL loaded by a process.\n";
        break;
    case DLL_THREAD_ATTACH:
        std::cout << "A thread is created in the current process.\n";
        break;
    case DLL_THREAD_DETACH:
        std::cout << "A thread is exiting cleanly.\n";
        break;
    case DLL_PROCESS_DETACH:
        std::cout << "DLL unloaded from a process.\n";
        break;
    }
    return TRUE;
}

以上是一个简单的DLLMain函数;每个case选项指定DLL加载时采取的操作:

  • DLL_PROCESS_ATTACH: 当DLL加载到进程内存时触发。这发生在进程启动时或使用LoadLibrary函数动态加载DLL时。 在此case中的典型用途包括初始化全局或静态数据、设置钩子或在调用进程中加载DLL时分配所需资源。

  • DLL_THREAD_ATTACH: 当在已加载DLL的进程中创建新线程时发生此通知。如果DLL在进程启动时加载,每个启动的新线程将触发此条件中的任何代码。 这通常用于分配或初始化特定于新线程的数据,例如线程本地存储。

  • DLL_THREAD_DETACH: 当线程干净退出时触发。这不一定在进程终止时发生,因为线程可能在进程结束前结束。 这是清理在DLL_THREAD_ATTACH中分配的资源的机会,例如释放线程特定数据以避免内存泄漏。

  • DLL_PROCESS_DETACH: 当DLL从进程内存卸载时发生,这可能因为进程终止或因为DLL被FreeLibrary调用动态卸载。 此case通常用于清理任务,例如释放共享资源、注销钩子和释放DLL使用的内存,以确保干净退出且无资源泄漏。

各种函数关于DLL劫持并非新事物。HelloJackHunter发挥作用的新部分是映射可用函数以在DLL执行时触发。

已知易受攻击的二进制文件

因此,从我对WinSxS的研究中,我发现了四个始终易受攻击的二进制文件和其他几个,但最容易演示的如下表所示。还有数百个要测试,但与其全部自己做,我宁愿与社区分享并让人们自己查看!(因为Andy无法弄清楚Ghost及其自己的神奇markdown实现,您需要在表中水平滚动)

二进制名称 路径 DLL名称/路径
ngentask.exe C:\Windows\WinSxS\amd64_netfx4-ngentask_exe_b03f5f7f11d50a3a_4.0.15912.0_none_d5e7146d665097c0\ngentask.exe mscorsvc.dll
explorer.exe C:\Windows\WinSxS\amd64_microsoft-windows-explorer_31bf3856ad364e35_10.0.22621.3235_none_31b295f9f540d278\explorer.exe cscapi.dll
aspnet_wp.exe C:\Windows\WinSxS\amd64_netfx4-aspnet_wp_exe_b03f5f7f11d50a3a_4.0.15912.0_none_107a08446d17dcf2\aspnet_wp.exe webengine.dll, webengine4.dll
aspnet_regiis.exe c:\Windows\WinSxS\amd64_netfx4-aspnet_regiis_exe_b03f5f7f11d50a3a_4.0.15912.0_none_833013222f03235e\aspnet_regiis.exe webengine4.dll

每个都可以利用本地目录中的DLL进行利用,并调用以在搜索顺序劫持中执行函数。它们还作为技术性"靠山吃山"二进制文件,因为路径名称中的GUI会使路径略有不同,但二进制文件仍然存在。要轻松找到它们,可以使用以下PowerShell:

1
(Get-Command binaryname.exe -All).Path

上述命令类似于Windows上的where binary,语法在此文章中找到。

检测此活动

最终,研究很有趣,但如何检测此活动?嗯,我编写了一个简单的Yara规则来检测从C:\Windows\WinSxS外部加载DLL的WinSxS二进制文件。该规则主要专注于匹配二进制文件中的模式,表明它正在加载DLL,特别查看这些DLL加载的路径。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
rule WinSxS_Outside_DLL_Loading
{
    meta:
        description = "Detect binaries loading DLLs from outside the C:\\Windows\\WinSxS directory"
        author = "ZephrFish"
        reference = "Internal Analysis"
        date = "2024-05-12"

    strings:
        $suspect_path = /LoadLibrary(Ex)?\s*\(.*[^\WinSxS\\][A-Za-z]:\\.*\.dll.*/ nocase wide ascii

    condition:
        $suspect_path
}

然而,重要的是要注意YARA不直接监视系统行为或实时DLL加载活动。相反,YARA用于扫描静态文件以获取签名和模式。要有效

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计