解密微信聊天数据库

在看雪看到一篇微信数据库解密算法的帖子,感觉很有意思便尝试了一下,现在此进行总结整理。

微信的聊天记录数据库是放在本地的,其目录为文档\WeChat Files\用户名\Msg。是加密的sqlite数据库,而在微信登录过程中我们可以在内存中找到解密的秘钥,通过楼主给出的解密算法便可以解密出内容(通过sqlcipher也可以解密)。Msg目录中的.db文件都可以解密,其中ChatMsg.db为聊天记录

本文实验的是PC端的微信,解密算法android、PC通用,只是andoroid秘钥获取方式不一样,如有需要请自行查找资料

一、通过调试微信获取秘钥

  1. 启动微信,停留在登录界面,先不要登录,用调试器附加微信,进程名WeChat(注意不是WeChatWeb)
  2. 在wechatwin.dll模块中搜索字符串”DBFactory::encryptDB”,当然也可以在所有模块中搜索,只不过比较慢就是了。
  3. test edx, edx处下断,运行起来,登录微信,断下,edx中即为秘钥地址,秘钥长度32字节。
    Snipaste_2018-04-12_18-04-33

    二、解密算法

    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
       #include "stdafx.h"
    #include <Windows.h>
    #include <openssl/rand.h>
    #include <openssl/evp.h>
    #include <openssl/aes.h>
    #include <openssl/hmac.h>

    //#define ANDROID_WECHAT

    #define SQLITE_FILE_HEADER "SQLite format 3" //length == 16
    #define IV_SIZE 16
    #define HMAC_SHA1_SIZE 20
    #define KEY_SIZE 32

    #ifndef ANDROID_WECHAT
    #define DEFAULT_PAGESIZE 4096
    #define DEFAULT_ITER 64000
    #else
    #define NO_USE_HMAC_SHA1
    #define DEFAULT_PAGESIZE 1024
    #define DEFAULT_ITER 4000
    #endif


    //安卓端这里密码是7位,pc端是经过算法得到的32位pass,这里的0xcc是我隐去的部分,实际不会有0xcc
    unsigned char pass[] = {
    0x5D, 0x81, 0xD4, 0xA9, 0xAF, 0xAF, 0xCC, 0xCC, 0xCC, 0x49, 0x70, 0x39, 0x2F, 0xAC, 0x1E, 0x7F,
    0xBA, 0xB2, 0xFE, 0x4D, 0x39, 0xCF, 0xCC, 0xCC, 0xCC, 0xCE, 0x59, 0x64, 0xCF, 0x43, 0xDF, 0x0D
    };


    int main()
    {
    FILE *fpdb = fopen("MicroMsg.db", "rb+");
    if (!fpdb) {
    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;
    #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("decrypt page:%d/%d \n", nPage, nFileSize / DEFAULT_PAGESIZE);

    #ifndef NO_USE_HMAC_SHA1
    //check hmac
    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))) {
    //hash check err
    return 0;
    }
    #endif
    //
    if (nPage == 1) {
    memcpy(pDecryptPerPageBuffer, SQLITE_FILE_HEADER, offset);
    }

    //aes decrypt
    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);

    //assert(nTotal == DEFAULT_PAGESIZE - reserve - offset);

    //no necessary ,just like sqlcipher
    memcpy(pDecryptPerPageBuffer + DEFAULT_PAGESIZE - reserve, pTemp + DEFAULT_PAGESIZE - reserve, reserve);

    FILE *fp = fopen("MicroMsg_Decrypt.db", "ab+");
    {
    fwrite(pDecryptPerPageBuffer, 1, DEFAULT_PAGESIZE, fp);
    fclose(fp);
    }

    nPage++;
    offset = 0;
    pTemp += DEFAULT_PAGESIZE;
    }
    return 0;
    }

这里需要注意的是,解密算法需要用到openssl库,win10请用1.0.2版本,为避免各种问题,可以直接使用slproweb.com提供的安装版,切勿使用名字含Light的轻量版,安装完毕后,项目属性配置如下,当然将包含的头文件和依赖的lib文件复制到项目目录更好,最后需要注意的是不要忘了libeay32.dll,安装openssl库之后默认路径C:\Windows\SysWOW64\libeay32.dll,编译成可执行文件发布请附带libeay32.dll。
Snipaste_2018-04-12_17-27-53
Snipaste_2018-04-12_17-28-11

解密出的xxx_Decrypt.db文件可通过Navicat Premium数据库可视化工具查看,或者自行编写查询程序。

获取秘钥的部分利用hook技术注入到微信中截获秘钥,然后将聊天记录数据库和截获到的秘钥一并传回,可以做成间谍程序,在此不再展开。

文章作者: TechOtaku
文章链接: http://techotaku.me/2018/04/13/解密微信聊天数据库/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 徒然の博客