PC版微信數據庫解密詳細教程

轉載自: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了,可以把代碼放進來編譯了。

       由於太多網站轉載,而且很多有錯漏,已經搞不清原始代碼是哪位大神寫的了,其中有一些已經被廢棄的代碼,根據系統報錯提示進行了替換,另外做了一個主要的變化就是之前的代碼是把數據庫名寫在變量中,但由於需要解密很多庫,爲了靈活,改爲輸入參數的方法,即在運行時帶參數運行或者根據提示輸入需要解密的數據庫文件名。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

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數據庫管理軟件打開即可。

 

       本文主要是個驗證過程,沒有做什麼突破工作,目前的解密只能算是半自動過程,密碼算法部分的獲得是下一步需要研究的內容,希望大家共同努力!

       作者:newx

       QQ:2337242

【 原創文章,轉載請註明出處。 】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章