PC 微信逆向:破解聊天記錄文件!
作者: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 數據庫管理軟件打開即可。
本文主要是個驗證過程,沒有做什麼突破工作,目前的解密只能算是半自動過程,密碼算法部分的獲得是下一步需要研究的內容,希望大家共同努力!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/viAMP8xl3B9_ly7qsKdRKA