PC版VX数据库解密详细教程

xingyun86 2019-8-7 2004

  在电子取证过程中,也会遇到提取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了,可以把代码放进来编译了。       由于太多网站转载,而且很多有错漏,已经搞不清原始代码是哪位大神写的了,其中有一些已经被废弃的代码,根据系统报错提示进行了替换,另外做了一个主要的变化就是之前的代码是把数据库名写在变量中,但由于需要解密很多库,为了灵活,改为输入参数的方法,即在运行时带参数运行或者根据提示输入需要解密的数据库文件名。

[C++] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
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数据库管理软件打开即可。
       本文主要是个验证过程,没有做什么突破工作,目前的解密只能算是半自动过程,密码算法部分的获得是下一步需要研究的内容,希望大家共同努力!


×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回