原標題:手把手用C++解密Chrome80版本數據庫

谷歌瀏覽器Google Chrome 80正式版例行更新詳細版本80.0.3987.163。Google Chrome瀏覽器又稱谷歌瀏覽器採用Chromium內核全球最受歡迎的免費網頁瀏覽器追求速度、隱私安全的網絡瀏覽器。

先說下吧。chrome80以前的版本是直接可以通過DPAPI來進行解密的。關於DPAPI 大家可以 看這裏的介紹

DPAPI是Windows系統級對數據進行加解密的一種接口無需自實現加解密代碼微軟已經提供了經過驗證的高質量加解密算法提供了用戶態的接口對密鑰的推導存儲數據加解密實現透明並提供較高的安全保證

DPAPI提供了兩個用戶態接口`CryptProtectData`加密數據`CryptUnprotectData`解密數據加密後的數據由應用程序負責安全存儲應用無需解析加密後的數據格式。但是加密後的數據存儲需要一定的機制因爲該數據可以被其他任何進程用來解密當然`CryptProtectData`也提供了用戶輸入額外`數據`來參與對用戶數據進行加密的參數但依然無法放於暴力破解。

總體來說程序可以使用DPAPI來對自己敏感的數據進行加解密也可持久化存儲程序或系統重啓後可解密密文獲取原文。如果應用程序對此敏感數據只是暫存於內存爲了防止被黑客dump內存後進行破解也對此數據無需進行持久化存儲微軟還提供了加解密內存的接口`CryptProtectMemory`和`CryptUnprotectMemory`。加解密內存的接口並可指定`Flag`對此內存加解密的聲明週期做控制詳細見`Memory加密及優缺點`章節

廢話不多說我們且來看看新版的Chrome是怎麼一個加密流程。首先。我們需要大致清楚新版chrome用到的加密。無非就是2個 劃重點

DPAPI

AES-GCM

先給大家看一用python寫的解密吧

aes.py import os import sys import sqlite3 from urllib.parse import urlencode import json, base64 import aesgcm import binascii def dpapi_decrypt(encrypted): import ctypes import ctypes.wintypes class DATA_BLOB(ctypes.Structure): _fields_ = [('cbData', ctypes.wintypes.DWORD), ('pbData', ctypes.POINTER(ctypes.c_char))] p = ctypes.create_string_buffer(encrypted, len(encrypted)) blobin = DATA_BLOB(ctypes.sizeof(p), p) blobout = DATA_BLOB retval = ctypes.windll.crypt32.CryptUnprotectData( ctypes.byref(blobin), None, None, None, None, 0, ctypes.byref(blobout)) if not retval: raise ctypes.WinError result = ctypes.string_at(blobout.pbData, blobout.cbData) ctypes.windll.kernel32.LocalFree(blobout.pbData) return result def aes_decrypt(encrypted_txt): encrypted_txt = binascii.unhexlify(encrypted_txt) encoded_key = "RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAAFBcVfgeqrR6TWICu+11nQAAAAAAIAAAAAABBmAAAAAQAAIAAAADGFDG3ftjedfJDzI98JL+tPfbE3tgNumX5v+PGs9eEgAAAAAA6AAAAAAgAAIAAAAHMoKUPxu+eC153jdAcreqzjPCvccip33ZQPvnOZstQBMAAAAFCQh824CftlmS+gbu8NK1Gev4EVvODPwV6T33S9AXilInJ26Z09nTULJE3pF+9XtEAAAACndz8ZGF2V7IMxQDK6kFAk6wOUv/Bx9hZhZtiyu2urYfKYbCPvMSWg4e9+/oQrEL2NEG+fFjX/EP6SrLzE8Xqy" encrypted_key = base64.b64decode(encoded_key) print("encrypted_key="+encrypted_key.hex+" Len="+str(len(encrypted_key))+"\r\n"); encrypted_key = encrypted_key[5:] print("encrypted_key="+encrypted_key.hex+"\r\n"); key = dpapi_decrypt(encrypted_key) print("key="+key.hex+"\r\n"); nonce = encrypted_txt[3:15] print("nonce="+nonce.hex+"\r\n"); cipher = aesgcm.get_cipher(key) ##print("cipher="+cipher.hex+"\r\n"); print("encrypted_txt="+encrypted_txt[15:].hex+"\r\n"); return aesgcm.decrypt(cipher,encrypted_txt[15:],nonce) print(aes_decrypt("76313068C3E4957EC879AD4483CBFA7476E7B77C035D8355A5D73FCFA9A87007D908896061DDD79471"))

然後是aes-gcm

import os import sys from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import ( Cipher, algorithms, modes ) NONCE_BYTE_SIZE = 12 def encrypt(cipher, plaintext, nonce): cipher.mode = modes.GCM(nonce) encryptor = cipher.encryptor ciphertext = encryptor.update(plaintext) return (cipher, ciphertext, nonce) def decrypt(cipher, ciphertext, nonce): cipher.mode = modes.GCM(nonce) decryptor = cipher.decryptor return decryptor.update(ciphertext) def get_cipher(key): cipher = Cipher( algorithms.AES(key), None, backend=default_backend ) return cipher

如此即可解密。說下簡單的流程吧。

大致流程從C:\Users\0ops\AppData\Local\Google\Chrome\UserData\LocalState這個Json中讀取一個值os_crypt下的encrypted_key

然後取解密祕鑰(encrypted_key)去除前5個字符再通過對其dpapi解密出這個值保存爲key.並且截取15位去除前3位字符保存爲Nonce.

然後使用asegcm進行解密key最終使用aesgcm解密。

大致就是如此。爲了證明我的屁眼代碼可以用。上一個圖。稍等。。我去安裝下chrome80。。。影子系統還原了。。。我安裝好了

取下encrypted_key和被加密的value的HEX。在這之前 我們先看下加密的內容

包含V10和V11的是chrme80的加密。好了 我們來找找freebuf的值

把加密值HEX[v10mC1^ĻI~\`ql>t^c+EO0bJKp1YRn˭F$O]一下得到7631306D43A786939231E0A4D6DC5E**BB497E5C60716CFEFDDB3E74A7ABE2E5F1BAF45EF5F163BC2BB**54F9D30624A4B708D310C168894FFEC189C8959526ECBAD46EF1D7FD224B6868FA64F83CD

然後我們用python解密一下

可看到了解密成功。下一篇用C++來實現自動化解密

幾個注意點

Cookie位於User Data/Default下的Cookies文件 改名爲Cookies.db即可用sqllite進行查詢和查看

上一篇實現了python的簡單解密。這一次我們來用C++實現自動化。在這之前 我們需要用到兩個C++庫

repaidjson cryptopp

編譯環境爲VS2013.這兩個庫不多做介紹,rapidjson是騰訊的一個開源json解析庫。發揮的作用不大,就是解析個json。另外就是cryptopp。嗯。。很牛逼。

解析下大致流程:

1:獲取local state文件位置

2:獲取加密的key(base64編碼)

3:解析sqllite文件

4:DPAPI解密

5:ase-gcm解密

關於Aes-gcm 需要用到 KEY IV 以及被加密的字符串 梳理下這幾個參數的流程:

KEY = local state = > os_crypt => Encrypted_key => Base64Decode(encrypted_key) => 去除首位5個字符 => DPAPI解密

IV = 被加密的字符串掐頭去尾

chiper = 被加密的字符串去頭

一般來說 安裝的這些配置文件都在LOCAL_APPDATA下。可以使用SHGetSpecialFolderPath(NULL, szBuffer, CSIDL_LOCAL_APPDATA, FALSE);

來獲取這個路徑。然後starcat組合一下字符串得到路徑 部分代碼如下:

char szBuffer[MAX_PATH]; if (EncryptBaseKey == "") { string jsonstr; SHGetSpecialFolderPath(NULL, szBuffer, CSIDL_LOCAL_APPDATA, FALSE); strcat(szBuffer, "\\Google\\Chrome\\User Data\\Local State"); jsonstr = readfile(szBuffer); Document root; root.Parse(jsonstr.c_str); Value& infoArray = root["os_crypt"]; EncryptBaseKey = infoArray["encrypted_key"].GetString; } return EncryptBaseKey;

這裏就獲取了加密的祕鑰。但是有一點。如果是非80版本 是不存在os_crypt的,這裏使用的rapidjson就會拋出異常。但不影響。只需要在使用sqllite查詢的時候 接管一下字符串,看看是不是包含v10或者v11即可。如果你使用的和我一樣代碼。請注意大小寫V10和v10的區別。

string e_str = argv[i]; if (strstr(e_str.c_str, "v10") != NULL || strstr(e_str.c_str, "v11") != NULL) { string DecryptVaule=NewDecrypt(argv[i]); strcpy(enc_value_a, DecryptVaule.c_str); } else{ DecryptPass(argv[i], enc_value, 2048); _snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value); }

緊接着就是對他進行base64解密。這裏我用的是cryptopp 先放一下新版解密函數

std::string static NewDecrypt(CHAR *cryptData) { string EncryptValue = cryptData; string Encoded,Decoded; string key,iv,chiper; string recovered;//也就是解密的KEY WCHAR enc_value[2048]; char enc_value_a[2048]; ZeroMemory(enc_value, sizeof(enc_value)); ZeroMemory(enc_value_a, sizeof(enc_value_a)); //-----------------------初始化幾個要用到加密字符串的變量----------------------------------// iv = EncryptValue; chiper = EncryptValue; //---------------------------------------------------------// StringSource((BYTE*)EncryptValue.c_str, EncryptValue.size, true, new HexEncoder( new StringSink(Encoded))); EncryptValue = Encoded; Encoded.clear; //---------------------------------------------------------// key = GetEncryptKEY; StringSource((BYTE*)key.c_str, key.size, true, new Base64Decoder( new StringSink(Decoded))); key = Decoded; key = key.substr(5);//去除首位5個字符 Decoded.clear; DecryptPass((char*)key.c_str, enc_value, 2048); _snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value); key = enc_value_a; StringSource((BYTE*)key.c_str,key.size, true, new HexEncoder( new StringSink(Encoded))); key = Encoded; Encoded.clear; //KEY解密完畢 開始處理Nonce 也就是IV iv =iv.substr(3,12); StringSource((BYTE*)iv.c_str, iv.size, true, new HexEncoder( new StringSink(Encoded))); iv = Encoded; Encoded.clear; //---------------------------------------------------------// //開始處理chiper if (chiper.size < 30){ return "wu xiao zi fu chuan....."; } StringSource((BYTE*)chiper.c_str, chiper.size, true, new HexEncoder( new StringSink(Encoded))); chiper = Encoded; Encoded.clear; chiper = chiper.substr(30);//因爲是HEX 佔了2個字節 //---------------------------------------------------------// //進行AES_GCM try { StringSource((BYTE*)iv.c_str, iv.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource iv = Decoded; Decoded.clear; StringSource((BYTE*)key.c_str, key.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource key = Decoded; Decoded.clear; StringSource((BYTE*)chiper.c_str, chiper.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource chiper = Decoded; Decoded.clear; cout << chiper << endl; GCM< AES >::Decryption d; d.SetKeyWithIV((BYTE*)key.c_str, key.size, (BYTE*)iv.c_str, iv.size); StringSource s(chiper, true, new AuthenticatedDecryptionFilter(d, new StringSink(recovered) ) // StreamTransformationFilter ); // StringSource cout << "recovered text: " << recovered << endl; } catch (const CryptoPP::Exception& e) { cerr << e.what << endl; //exit(1); } return recovered; }

先base64解碼一下

key = GetEncryptKEY; StringSource((BYTE*)key.c_str, key.size, true, new Base64Decoder( new StringSink(Decoded))); key = Decoded; key = key.substr(5);//去除首位5個字符 Decoded.clear;

如此可以得到這一樣一個字符串

這是沒有去除字符的情況下,這個時候去除之後 即祛除了首位的DPAPI 如此便獲得了一個初步解密的KEY。但在這之後,我們還需要對這個KEY做一次解密,因爲這個時候的KEY還不能真正算是解密的KEY 他還需要進行一次DPAPI解密

DPAPI的解密函數部分代碼如下:

DATA_BLOB input; input.pbData = (BYTE*)(cryptData); DATA_BLOB output; DWORD blen; for(blen=128; blen<=2048; blen+=16) { input.cbData = blen; if (CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output)) break; } if (blen>=2048) return 0; CHAR *decrypted = (CHAR *)malloc(clearSize); if (!decrypted) { LocalFree(output.pbData); return 0; } memset(decrypted, 0, clearSize); memcpy(decrypted, output.pbData, (clearSize < output.cbData) ? clearSize - 1 : output.cbData); _snwprintf_s(clearData, clearSize, _TRUNCATE, L"%S", decrypted); free(decrypted); LocalFree(output.pbData); return 1;

在解密之後我們可以得到:

然後我們對加密字符串進行處理,取出iv和chiper。再使用aes-gcm解密即可。

iv =iv.substr(3,12); StringSource((BYTE*)iv.c_str, iv.size, true, new HexEncoder( new StringSink(Encoded))); iv = Encoded; Encoded.clear; //---------------------------------------------------------// //開始處理chiper if (chiper.size < 30){ return "wu xiao zi fu chuan....."; } StringSource((BYTE*)chiper.c_str, chiper.size, true, new HexEncoder( new StringSink(Encoded))); chiper = Encoded; Encoded.clear; chiper = chiper.substr(30);//因爲是HEX 佔了2個字節

解密

try { StringSource((BYTE*)iv.c_str, iv.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource iv = Decoded; Decoded.clear; StringSource((BYTE*)key.c_str, key.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource key = Decoded; Decoded.clear; StringSource((BYTE*)chiper.c_str, chiper.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource chiper = Decoded; Decoded.clear; cout << chiper << endl; GCM< AES >::Decryption d; d.SetKeyWithIV((BYTE*)key.c_str, key.size, (BYTE*)iv.c_str, iv.size); StringSource s(chiper, true, new AuthenticatedDecryptionFilter(d, new StringSink(recovered) ) // StreamTransformationFilter ); // StringSource cout << "recovered text: " << recovered << endl; } catch (const CryptoPP::Exception& e) { cerr << e.what << endl; //exit(1); } return recovered;

最終獻上Demo源碼

// Chrome80解密Demo.cpp : 定義控制檯應用程序的入口點。 // #include "stdafx.h" #include #include #include /*********************************\ 加密庫頭存放在這 \*********************************/ #include "cryptopp\base64.h" using CryptoPP::Base64Decoder; using CryptoPP::Base64Encoder; #include "cryptopp/hex.h" using CryptoPP::HexEncoder; using CryptoPP::HexDecoder; #include "cryptopp/filters.h" using CryptoPP::StringSink; using CryptoPP::StringSource; using CryptoPP::AuthenticatedEncryptionFilter; using CryptoPP::AuthenticatedDecryptionFilter; #include "cryptopp/aes.h" using CryptoPP::AES; #include "cryptopp/gcm.h" using CryptoPP::GCM; #include "cryptopp/secblock.h" using CryptoPP::SecByteBlock; /*********************************\ 加密庫頭加載完畢 \*********************************/ using namespace std; #pragma comment(lib,"userenv.lib") #pragma comment(lib,"cryptlib.lib") #pragma comment(lib,"Crypt32.lib") //RFBBUEkBAAAA0Iyd3wEV0RGMegDAT8KX6wEAAAAFBcVfgeqrR6TWICu+11nQAAAAAAIAAAAAABBmAAAAAQAAIAAAAJxLse8lqGAP4o493iTyljEUUF9y76AAoprRgHJwesCyAAAAAA6AAAAAAgAAIAAAAFtTd4B22Ky/x2LVgQUSaKku2rCvsv+FiMFj+lGN8LmZMAAAANBlkfPhV/zVaMALHr0gK6dM7nFsfNTv6bfFKCyKbIorgbBnjfKp+K5MVz9iizYVs0AAAACihmRGBIQ6oDkgjzCk+9AhePof4eUhB98pb7UlbGgssV2fnGRrBYQHW8Gyyp9W4pojyn9J7GQixtdCIPBwEW92 //763130954DBA6D89BBAB2FF4A4460AEA7B823BA5BAF01B2B5E2CECDED5855F6E1E7B57946599C6ACD7D60F4B03FC11D5F7C6A39FA59FBF33D7 int DecryptPass(CHAR *cryptData, WCHAR *clearData, UINT clearSize) { DATA_BLOB input; input.pbData = (BYTE*)(cryptData); DATA_BLOB output; DWORD blen; for (blen = 128; blen <= 2048; blen += 16) { input.cbData = blen; if (CryptUnprotectData(&input, NULL, NULL, NULL, NULL, 0, &output)) break; } if (blen >= 2048) return 0; CHAR *decrypted = (CHAR *)malloc(clearSize); if (!decrypted) { LocalFree(output.pbData); return 0; } memset(decrypted, 0, clearSize); memcpy(decrypted, output.pbData, (clearSize < output.cbData) ? clearSize - 1 : output.cbData); _snwprintf_s(clearData, clearSize, _TRUNCATE, L"%S", decrypted); free(decrypted); LocalFree(output.pbData); return 1; } int _tmain(int argc, _TCHAR* argv[]) { string EncryptValue; string key, iv, chiper, recovered; string Decoded, Encoded; WCHAR enc_value[2048]; char enc_value_a[2048]; ZeroMemory(enc_value, sizeof(enc_value)); ZeroMemory(enc_value_a, sizeof(enc_value_a)); cout << "請輸入EncryptKEY[BASE64]:" << endl; cin >> key; cout << "請輸入EncryptValue[HEX]:" << endl; cin >> EncryptValue; cout << "<---------------開始解密流程--------------->\r\n" << endl; //開始賦值 iv = EncryptValue; chiper = EncryptValue; StringSource((BYTE*)key.c_str, key.size, true, new Base64Decoder( new StringSink(Decoded))); key = Decoded; Decoded.clear; cout << "1:EncryptKEY 進行Base64解密:\r\n" << key << "\r\n" << endl; key = key.substr(5); cout << "2:EncryptKEY 去除首5個字符:\r\n" << key << "\r\n" << endl; DecryptPass((char*)key.c_str, enc_value, 2048); _snprintf_s(enc_value_a, sizeof(enc_value_a), _TRUNCATE, "%S", enc_value); key = enc_value_a; cout << "3:EncryptKEY 進行DPAPI解密:\r\n" << key << "\r\n" << endl; StringSource((BYTE*)key.c_str, key.size, true, new HexEncoder( new StringSink(Encoded))); key = Encoded; Encoded.clear; cout << "4:對已經通過DPAPI的EncryptKEY 進行HEX編碼:\r\n" << key << "\r\n" << endl; StringSource((BYTE*)iv.c_str, iv.size, true, new HexDecoder( new StringSink(Decoded))); iv = Decoded; Decoded.clear; iv=iv.substr(3, 15); StringSource((BYTE*)iv.c_str, iv.size, true, new HexEncoder( new StringSink(Encoded))); iv = Encoded; Encoded.clear; iv = iv.substr(0,iv.size-6); cout << "5:對要解密的字符串進行反HEX編碼 也就是解碼 並且截取之後再次 進行HEX編碼 賦值給iv:\r\n" << iv << "\r\n" << endl; chiper = chiper.substr(30); cout << "6:對要解密的字符串進行截取末尾15:\r\n" << chiper << "\r\n" << endl; try { StringSource((BYTE*)iv.c_str, iv.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource iv = Decoded; Decoded.clear; StringSource((BYTE*)key.c_str, key.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource key = Decoded; Decoded.clear; StringSource((BYTE*)chiper.c_str, chiper.size, true, new HexDecoder( new StringSink(Decoded) ) // HexEncoder ); // StringSource chiper = Decoded; Decoded.clear; cout << chiper << endl; GCM< AES >::Decryption d; d.SetKeyWithIV((BYTE*)key.c_str, key.size, (BYTE*)iv.c_str, iv.size); StringSource s(chiper, true, new AuthenticatedDecryptionFilter(d, new StringSink(recovered) ) // StreamTransformationFilter ); // StringSource cout << "7:最終解密文本爲:\r\n" << recovered << "\r\n" << endl; } catch (const CryptoPP::Exception& e) { cerr << e.what << endl; //exit(1); } system("pause"); return 0; }

附上一張解密靚照

覈對下解密的密文是否正確

歡迎的加入羣:480257002

*本文作者:hijacking,轉載請註明來自FreeBuf.COM

相關文章