[scode type="share"]本文转载于：newx，学习一下。[/scode]在电子取证过程中，也会遇到提取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:UsersAdministratorDocumentsWeChat Files**Msg其中位置为需要解密的微信id，目录内容如下：如果要解密ChatMsg.db，则在命令行窗口输入指令dewechat ChatMsg.db回车即可。解密成功后，会在目录中生成de_ChatMsg.db，用sqlite数据库管理软件打开即可。本文主要是个验证过程，没有做什么突破工作，目前的解密只能算是半自动过程，密码算法部分的获得是下一步需要研究的内容，希望大家共同努力！