在电子取证过程中,也会遇到提取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了,可以把代码放进来编译了。 由于太多网站转载,而且很多有错漏,已经搞不清原始代码是哪位大神写的了,其中有一些已经被废弃的代码,根据系统报错提示进行了替换,另外做了一个主要的变化就是之前的代码是把数据库名写在变量中,但由于需要解密很多库,为了灵活,改为输入参数的方法,即在运行时带参数运行或者根据提示输入需要解密的数据库文件名。
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数据库管理软件打开即可。
本文主要是个验证过程,没有做什么突破工作,目前的解密只能算是半自动过程,密码算法部分的获得是下一步需要研究的内容,希望大家共同努力!