作者:newx
連結:https://bbs.pediy.com/thread-251303.htm
在電子取證過程中,也會遇到提取PC版微信數據的情況,看雪、52破解和CSDN等網上的PC版微信資料庫破解文章實在是太簡略了,大多數只有結果沒有過程。經過反覆試驗終於成功解密了資料庫,現在把詳細過程記錄下來,希望大家不要繼續在已經解決的問題上過度浪費時間,以便更投入地研究尚未解決的問題。
通過查閱資料得知,與安卓手機版微信的7位密碼不同,PC版微信的密碼是32位元組(64位),加密算法沒有說明,但是可以通過OllyDbg工具從內存中獲取到這個密碼,然後通過一段C++代碼進行解密。
首先下載OllyDbg 2.01漢化版,我用的版本如下圖所示:
運行OllyDbg,然後運行PC版微信(需要下載客戶端的,不是網頁版)。先不要點擊登錄按鈕。
切換到Ollydbg界面:
點擊文件菜單,選擇「附加」,在彈出的對話框中找到名稱為WeChat的進程,其窗口名稱為「登錄」。然後點擊「附加」。
附加成功後OllyDbg開始加載,成功加載後可以看到最上面OllyDbg後面有WeChat.exe的字樣:
在查看菜單中選擇「可執行模塊」:
找到名稱為WeChatWin的模塊,雙擊選中。為了方便觀察,在窗口菜單中選擇水平平鋪。在CPU窗口標題欄可以看到「模塊WeChatWin」字樣。
在插件中選擇「StrFinder字符查找」中的「查找ASCII字符串」(注意如果下載的OllyDbg版本不對,可能沒有相關插件,因此一定要找對版本),要稍微等一會兒,會出現搜索結果的窗口。
在此窗口點擊滑鼠右鍵,選擇「Find」,在搜索框中輸入「DBFactory::encryptDB」。
會自動定位在第一處,但我們需要的是第二處,即「encryptDB %s DBKey can’t be null」下面這一處。可以用滑鼠點擊滾動條向下,找到第二處,用滑鼠雙擊此處。
在CPU窗口中可以看到已經定位到了相應的位置。用滑鼠點擊滾動條向下翻。
下面第六行應該是TEST EDX,EDX,就是用來比對密碼的彙編語言代碼。在最前面地址位置(本文中是0F9712BA)雙擊設置斷點(設置斷點成功則地址會被標紅,而且可以在斷點窗口中看到設置成功的斷點)
點擊「運行」按鈕(或者在調試菜單中選擇「運行」),這時寄存器窗口中的EDX的值應該是00000000。
切換到微信登錄頁面,點擊登錄,然後到手機端確認登錄。這是OllyDbg界面中的數據不斷滾動,直到EDX不再為全0並且各個窗口內容停止滾動為止。
在EDX的值上面點擊滑鼠右鍵,在彈出的菜單裡面選擇「數據窗口中跟隨」,則數據窗口中顯示的就是EDX的內容。
圖示中從0B946A80(這個數值是變化的,不但每臺電腦不同,每次調試也可能完全不同)到0B946A9F共32個字節就是微信的加密密碼,本圖中就是:
「53E9BFB23B724195A2BC6EB5BFEB0610DC2164756B9B4279BA32157639A40BB1」
一共32個字節,共64位。
得到這個之後,就可以關閉OllyDbg了,微信也會自動被關閉。
接下來就是解密過程。在看雪、52破解等多個論壇中都有相關的C++源碼,開始企圖使用Dev-C++或者C-Free等輕量級IDE進行編譯,也使用過Visual C++ 6.0綠色精簡版,結果多次嘗試出現各種錯誤,反覆失敗,最終不得已使用Visual Studio,並對代碼進行了一定的修正,終於調試成功。
正好Visual Studio 2019剛剛發布直接到官方網站下載了社區版。
根據查到的資料,需要先安裝openssl,為了省事直接下載了最新的Win64OpenSSL-1_1_1b,安裝後發現各種報錯,繼續查找資料發現原來sqlcipher使用的是低版本的openssl,之後找到了一個Win64OpenSSL-1_0_2r也報錯,最後發現還是官方這個直接解壓縮的版本靠譜:
https://www.openssl.org/source/openssl-1.0.2r.tar.gz
把壓縮包直接解壓到任意目錄,比如c:\openssl-1.0.2r
啟動Visual Studio 2019社區版(估計Visual Studio 2008以後的都應該可以,懶得找就直接官網下載最新的吧)
在啟動界面右下方選擇「創建新項目」
滾動下拉條,在窗口中選擇C++控制臺應用:
給項目隨便起個名字,選擇保存位置:
然後點擊「創建」,即可完成新項目創建。生成默認的Hello World代碼:
先要做好項目的基礎配置,之前調試失敗主要問題就出在這裡了。
在項目菜單中最下面選擇項目屬性「dewechat屬性」(這個跟設置的項目名稱一致)
對話框最左上角的配置後面,可以選擇配置的是Debug模式還是Release模式(Release模式不包含調試信息,編譯完成的exe文件更小一些,但如果是自己用,這兩個模式沒有區別,配置了哪個,後面就要用哪個模式編譯,否則會報錯)
先選擇C/C++下面的「常規」選項:
右邊第一條是「附加包含目錄」,點擊右側空白處。在下拉框裡選擇「編輯…」,在對話框中點擊四個圖標按鈕最左側的「新行」按鈕,會生成一個空白行,點擊右側的「…」:
在彈出的對話框裡選擇剛剛安裝的openssl目錄(本文是c:\openssl-1.0.2r)中的include目錄。
設置完成後如下:
然後選擇左側「連結器」下面的「常規」:
在中間位置,有一個「附加庫目錄」,點擊右側空白處,選擇openssl目錄下的lib目錄,設置完成後如下:
最後點擊連結器下面的「輸入」:
右側最上面有「附加依賴項」,默認已經有一些系統庫,點擊右側內容,選擇「編輯…」
這個沒有增加新行的按鈕,只能手工錄入或者拷貝文件名進去,需要增加上圖所示的兩個庫名稱。
設置完成後如下:
現在所有的設置都OK了,可以把代碼放進來編譯了。
由於太多網站轉載,而且很多有錯漏,已經搞不清原始代碼是哪位大神寫的了,其中有一些已經被廢棄的代碼,根據系統報錯提示進行了替換,另外做了一個主要的變化就是之前的代碼是把資料庫名寫在變量中,但由於需要解密很多庫,為了靈活,改為輸入參數的方法,即在運行時帶參數運行或者根據提示輸入需要解密的資料庫文件名。
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數據 + 16IV + 20 HMAC + 12#define DEFAULT_ITER 64000#else#define NO_USE_HMAC_SHA1#define DEFAULT_PAGESIZE 1024#define DEFAULT_ITER 4000#endif//pc端密碼是經過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) //第二個參數argv[1]是文件名 strcpy_s(dbfilename, argv[1]); //複製 //沒有提供文件名,則提示用戶輸入 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_SHA1 unsigned char mac_salt[16] = { 0 }; memcpy(mac_salt, salt, 16); for (int i = 0; i < sizeof(salt); i++) { mac_salt[i] ^= 0x3a; }#endif
int reserve = IV_SIZE; //校驗碼長度,PC端每4096位元組有48位元組#ifndef NO_USE_HMAC_SHA1 reserve += HMAC_SHA1_SIZE;#endif reserve = ((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_SHA1 PKCS5_PBKDF2_HMAC_SHA1((const char*)key, sizeof(key), mac_salt, sizeof(mac_salt), 2, sizeof(mac_key), mac_key);#endif
unsigned char* pTemp = pDbBuffer; unsigned char pDecryptPerPageBuffer[DEFAULT_PAGESIZE]; int nPage = 1; int offset = 16; while (pTemp < pDbBuffer + nFileSize) { printf("解密數據頁:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);
#ifndef NO_USE_HMAC_SHA1 unsigned 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;}將之前默認的代碼全部清除,將以上代碼拷貝進去,保存。然後在工具條欄中選擇是Debug還是Release模式,是x86還是x64(需要跟之前配置匹配,如果選了沒配置的模式會報錯。測試發現幾個選項沒有太大區別,建議默認),之後點擊「本地windows調試器」(或者按F5鍵),如果前面的步驟操作都正確,應該可以完成編譯並自動運行,彈出一個命令行窗口,提示需要輸入文件名:
最下方顯示了生成的exe文件路徑,將這個文件拷貝到微信資料庫所在的目錄,一般是:
C:\Users\Administrator\Documents\WeChat Files\********\Msg
其中********位置為需要解密的微信id,目錄內容如下:
如果要解密ChatMsg.db,則在命令行窗口輸入指令dewechat ChatMsg.db回車即可。
解密成功後,會在目錄中生成de_ChatMsg.db,用sqlite資料庫管理軟體打開即可。
本文主要是個驗證過程,沒有做什麼突破工作,目前的解密只能算是半自動過程,密碼算法部分的獲得是下一步需要研究的內容,希望大家共同努力!
--- EOF ---最近整理一份小白入門黑客資料《初學者如何入門黑客教程》,覆蓋了網絡滲透、網絡攻擊、防禦、各種黑客常用工具入門等等。獲取方式:關注公眾號並回復 黑客 領取,更多內容陸續奉上。