PC微信逆向:破解聊天記錄文件!
作者:newx
鏈接:https://bbs.pediy.com/thread-251303.htm
在電子取證過程中,也會遇到提取PC版微信數(shù)據(jù)的情況,看雪、52破解和CSDN等網(wǎng)上的PC版微信數(shù)據(jù)庫破解文章實在是太簡略了,大多數(shù)只有結(jié)果沒有過程。經(jīng)過反復(fù)試驗終于成功解密了數(shù)據(jù)庫,現(xiàn)在把詳細(xì)過程記錄下來,希望大家不要繼續(xù)在已經(jīng)解決的問題上過度浪費時間,以便更投入地研究尚未解決的問題。
通過查閱資料得知,與安卓手機(jī)版微信的7位密碼不同,PC版微信的密碼是32字節(jié)(64位),加密算法沒有說明,但是可以通過OllyDbg工具從內(nèi)存中獲取到這個密碼,然后通過一段C++代碼進(jìn)行解密。
首先下載OllyDbg 2.01漢化版,我用的版本如下圖所示:

運行OllyDbg,然后運行PC版微信(需要下載客戶端的,不是網(wǎng)頁版)。先不要點擊登錄按鈕。

切換到Ollydbg界面:

點擊文件菜單,選擇“附加”,在彈出的對話框中找到名稱為WeChat的進(jìn)程,其窗口名稱為“登錄”。然后點擊“附加”。

附加成功后OllyDbg開始加載,成功加載后可以看到最上面OllyDbg后面有WeChat.exe的字樣:

在查看菜單中選擇“可執(zhí)行模塊”:

找到名稱為WeChatWin的模塊,雙擊選中。為了方便觀察,在窗口菜單中選擇水平平鋪。在CPU窗口標(biāo)題欄可以看到“模塊WeChatWin”字樣。

在插件中選擇“StrFinder字符查找”中的“查找ASCII字符串”(注意如果下載的OllyDbg版本不對,可能沒有相關(guān)插件,因此一定要找對版本),要稍微等一會兒,會出現(xiàn)搜索結(jié)果的窗口。

在此窗口點擊鼠標(biāo)右鍵,選擇“Find”,在搜索框中輸入“DBFactory::encryptDB”。

會自動定位在第一處,但我們需要的是第二處,即“encryptDB %s DBKey can’t be null”下面這一處。可以用鼠標(biāo)點擊滾動條向下,找到第二處,用鼠標(biāo)雙擊此處。

在CPU窗口中可以看到已經(jīng)定位到了相應(yīng)的位置。用鼠標(biāo)點擊滾動條向下翻。

下面第六行應(yīng)該是TEST EDX,EDX,就是用來比對密碼的匯編語言代碼。在最前面地址位置(本文中是0F9712BA)雙擊設(shè)置斷點(設(shè)置斷點成功則地址會被標(biāo)紅,而且可以在斷點窗口中看到設(shè)置成功的斷點)
點擊“運行”按鈕(或者在調(diào)試菜單中選擇“運行”),這時寄存器窗口中的EDX的值應(yīng)該是00000000。
切換到微信登錄頁面,點擊登錄,然后到手機(jī)端確認(rèn)登錄。這是OllyDbg界面中的數(shù)據(jù)不斷滾動,直到EDX不再為全0并且各個窗口內(nèi)容停止?jié)L動為止。

在EDX的值上面點擊鼠標(biāo)右鍵,在彈出的菜單里面選擇“數(shù)據(jù)窗口中跟隨”,則數(shù)據(jù)窗口中顯示的就是EDX的內(nèi)容。

圖示中從0B946A80(這個數(shù)值是變化的,不但每臺電腦不同,每次調(diào)試也可能完全不同)到0B946A9F共32個字節(jié)就是微信的加密密碼,本圖中就是:
“53E9BFB23B724195A2BC6EB5BFEB0610DC2164756B9B4279BA32157639A40BB1”
一共32個字節(jié),共64位。
得到這個之后,就可以關(guān)閉OllyDbg了,微信也會自動被關(guān)閉。
接下來就是解密過程。在看雪、52破解等多個論壇中都有相關(guān)的C++源碼,開始企圖使用Dev-C++或者C-Free等輕量級IDE進(jìn)行編譯,也使用過Visual C++ 6.0綠色精簡版,結(jié)果多次嘗試出現(xiàn)各種錯誤,反復(fù)失敗,最終不得已使用Visual Studio,并對代碼進(jìn)行了一定的修正,終于調(diào)試成功。
正好Visual Studio 2019剛剛發(fā)布直接到官方網(wǎng)站下載了社區(qū)版。
根據(jù)查到的資料,需要先安裝openssl,為了省事直接下載了最新的Win64OpenSSL-1_1_1b,安裝后發(fā)現(xiàn)各種報錯,繼續(xù)查找資料發(fā)現(xiàn)原來sqlcipher使用的是低版本的openssl,之后找到了一個Win64OpenSSL-1_0_2r也報錯,最后發(fā)現(xiàn)還是官方這個直接解壓縮的版本靠譜:
https://www.openssl.org/source/openssl-1.0.2r.tar.gz
把壓縮包直接解壓到任意目錄,比如c:\openssl-1.0.2r
啟動Visual Studio 2019社區(qū)版(估計Visual Studio 2008以后的都應(yīng)該可以,懶得找就直接官網(wǎng)下載最新的吧)
在啟動界面右下方選擇“創(chuàng)建新項目”

滾動下拉條,在窗口中選擇C++控制臺應(yīng)用:

給項目隨便起個名字,選擇保存位置:

然后點擊“創(chuàng)建”,即可完成新項目創(chuàng)建。生成默認(rèn)的Hello World代碼:

先要做好項目的基礎(chǔ)配置,之前調(diào)試失敗主要問題就出在這里了。
在項目菜單中最下面選擇項目屬性“dewechat屬性”(這個跟設(shè)置的項目名稱一致)

對話框最左上角的配置后面,可以選擇配置的是Debug模式還是Release模式(Release模式不包含調(diào)試信息,編譯完成的exe文件更小一些,但如果是自己用,這兩個模式?jīng)]有區(qū)別,配置了哪個,后面就要用哪個模式編譯,否則會報錯)
先選擇C/C++下面的“常規(guī)”選項:

右邊第一條是“附加包含目錄”,點擊右側(cè)空白處。在下拉框里選擇“編輯…”,在對話框中點擊四個圖標(biāo)按鈕最左側(cè)的“新行”按鈕,會生成一個空白行,點擊右側(cè)的“…”:

在彈出的對話框里選擇剛剛安裝的openssl目錄(本文是c:\openssl-1.0.2r)中的include目錄。

設(shè)置完成后如下:

然后選擇左側(cè)“鏈接器”下面的“常規(guī)”:

在中間位置,有一個“附加庫目錄”,點擊右側(cè)空白處,選擇openssl目錄下的lib目錄,設(shè)置完成后如下:

最后點擊鏈接器下面的“輸入”:

右側(cè)最上面有“附加依賴項”,默認(rèn)已經(jīng)有一些系統(tǒng)庫,點擊右側(cè)內(nèi)容,選擇“編輯…”

這個沒有增加新行的按鈕,只能手工錄入或者拷貝文件名進(jìn)去,需要增加上圖所示的兩個庫名稱。
設(shè)置完成后如下:

現(xiàn)在所有的設(shè)置都OK了,可以把代碼放進(jìn)來編譯了。
由于太多網(wǎng)站轉(zhuǎn)載,而且很多有錯漏,已經(jīng)搞不清原始代碼是哪位大神寫的了,其中有一些已經(jīng)被廢棄的代碼,根據(jù)系統(tǒng)報錯提示進(jìn)行了替換,另外做了一個主要的變化就是之前的代碼是把數(shù)據(jù)庫名寫在變量中,但由于需要解密很多庫,為了靈活,改為輸入?yún)?shù)的方法,即在運行時帶參數(shù)運行或者根據(jù)提示輸入需要解密的數(shù)據(jù)庫文件名。
using namespace std;#include <Windows.h>#include <iostream>#include <openssl/rand.h>#include <openssl/evp.h>#include <openssl/aes.h>#include <openssl/hmac.h>#undef _UNICODE#define SQLITE_FILE_HEADER "SQLite format 3"#define IV_SIZE 16#define HMAC_SHA1_SIZE 20#define KEY_SIZE 32#define SL3SIGNLEN 20#ifndef ANDROID_WECHAT#define DEFAULT_PAGESIZE 4096 //4048數(shù)據(jù) + 16IV + 20 HMAC + 12#define DEFAULT_ITER 64000#else#define NO_USE_HMAC_SHA1#define DEFAULT_PAGESIZE 1024#define DEFAULT_ITER 4000#endif//pc端密碼是經(jīng)過OllyDbg得到的32位pass。unsigned char pass[] = { 0x53,0xE9,0xBF,0xB2,0x3B,0x72,0x41,0x95,0xA2,0xBC,0x6E,0xB5,0xBF,0xEB,0x06,0x10,0xDC,0x21,0x64,0x75,0x6B,0x9B,0x42,0x79,0xBA,0x32,0x15,0x76,0x39,0xA4,0x0B,0xB1 };char dbfilename[50];int Decryptdb();int CheckKey();int CheckAESKey();int main(int argc, char* argv[]){if (argc >= 2) //第二個參數(shù)argv[1]是文件名strcpy_s(dbfilename, argv[1]); //復(fù)制//沒有提供文件名,則提示用戶輸入else {cout << "請輸入文件名:" << endl;cin >> dbfilename;}Decryptdb();return 0;}int Decryptdb(){FILE* fpdb;fopen_s(&fpdb, dbfilename, "rb+");if (!fpdb){printf("打開文件錯!");getchar();return 0;}fseek(fpdb, 0, SEEK_END);long nFileSize = ftell(fpdb);fseek(fpdb, 0, SEEK_SET);unsigned char* pDbBuffer = new unsigned char[nFileSize];fread(pDbBuffer, 1, nFileSize, fpdb);fclose(fpdb);unsigned char salt[16] = { 0 };memcpy(salt, pDbBuffer, 16);#ifndef NO_USE_HMAC_SHA1unsigned char mac_salt[16] = { 0 };memcpy(mac_salt, salt, 16);for (int i = 0; i < sizeof(salt); i++){mac_salt[i] ^= 0x3a;}#endifint reserve = IV_SIZE; //校驗碼長度,PC端每4096字節(jié)有48字節(jié)#ifndef NO_USE_HMAC_SHA1reserve += HMAC_SHA1_SIZE;#endifreserve = ((reserve % AES_BLOCK_SIZE) == 0) ? reserve : ((reserve / AES_BLOCK_SIZE) + 1) * AES_BLOCK_SIZE;unsigned char key[KEY_SIZE] = { 0 };unsigned char mac_key[KEY_SIZE] = { 0 };OpenSSL_add_all_algorithms();PKCS5_PBKDF2_HMAC_SHA1((const char*)pass, sizeof(pass), salt, sizeof(salt), DEFAULT_ITER, sizeof(key), key);#ifndef NO_USE_HMAC_SHA1PKCS5_PBKDF2_HMAC_SHA1((const char*)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);#endifunsigned char* pTemp = pDbBuffer;unsigned char pDecryptPerPageBuffer[DEFAULT_PAGESIZE];int nPage = 1;int offset = 16;while (pTemp < pDbBuffer + nFileSize){printf("解密數(shù)據(jù)頁:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);#ifndef NO_USE_HMAC_SHA1unsigned char hash_mac[HMAC_SHA1_SIZE] = { 0 };unsigned int hash_len = 0;HMAC_CTX hctx;HMAC_CTX_init(&hctx);HMAC_Init_ex(&hctx, mac_key, sizeof(mac_key), EVP_sha1(), NULL);HMAC_Update(&hctx, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset + IV_SIZE);HMAC_Update(&hctx, (const unsigned char*)& nPage, sizeof(nPage));HMAC_Final(&hctx, hash_mac, &hash_len);HMAC_CTX_cleanup(&hctx);if (0 != memcmp(hash_mac, pTemp + DEFAULT_PAGESIZE - reserve + IV_SIZE, sizeof(hash_mac))){printf("\n 哈希值錯誤! \n");getchar();return 0;}#endif//if (nPage == 1){memcpy(pDecryptPerPageBuffer, SQLITE_FILE_HEADER, offset);}EVP_CIPHER_CTX* ectx = EVP_CIPHER_CTX_new();EVP_CipherInit_ex(ectx, EVP_get_cipherbyname("aes-256-cbc"), NULL, NULL, NULL, 0);EVP_CIPHER_CTX_set_padding(ectx, 0);EVP_CipherInit_ex(ectx, NULL, NULL, key, pTemp + (DEFAULT_PAGESIZE - reserve), 0);int nDecryptLen = 0;int nTotal = 0;EVP_CipherUpdate(ectx, pDecryptPerPageBuffer + offset, &nDecryptLen, pTemp + offset, DEFAULT_PAGESIZE - reserve - offset);nTotal = nDecryptLen;EVP_CipherFinal_ex(ectx, pDecryptPerPageBuffer + offset + nDecryptLen, &nDecryptLen);nTotal += nDecryptLen;EVP_CIPHER_CTX_free(ectx);memcpy(pDecryptPerPageBuffer + DEFAULT_PAGESIZE - reserve, pTemp + DEFAULT_PAGESIZE - reserve, reserve);char decFile[1024] = { 0 };sprintf_s(decFile, "dec_%s", dbfilename);FILE * fp;fopen_s(&fp, decFile, "ab+");{fwrite(pDecryptPerPageBuffer, 1, DEFAULT_PAGESIZE, fp);fclose(fp);}nPage++;offset = 0;pTemp += DEFAULT_PAGESIZE;}printf("\n 解密成功! \n");return 0;}
將之前默認(rèn)的代碼全部清除,將以上代碼拷貝進(jìn)去,保存。然后在工具條欄中選擇是Debug還是Release模式,是x86還是x64(需要跟之前配置匹配,如果選了沒配置的模式會報錯。測試發(fā)現(xiàn)幾個選項沒有太大區(qū)別,建議默認(rèn)),之后點擊“本地windows調(diào)試器”(或者按F5鍵),如果前面的步驟操作都正確,應(yīng)該可以完成編譯并自動運行,彈出一個命令行窗口,提示需要輸入文件名:

最下方顯示了生成的exe文件路徑,將這個文件拷貝到微信數(shù)據(jù)庫所在的目錄,一般是:
C:\Users\Administrator\Documents\WeChat Files\********\Msg
其中********位置為需要解密的微信id,目錄內(nèi)容如下:

如果要解密ChatMsg.db,則在命令行窗口輸入指令dewechat ChatMsg.db回車即可。

解密成功后,會在目錄中生成de_ChatMsg.db,用sqlite數(shù)據(jù)庫管理軟件打開即可。
本文主要是個驗證過程,沒有做什么突破工作,目前的解密只能算是半自動過程,密碼算法部分的獲得是下一步需要研究的內(nèi)容,希望大家共同努力!
