文章来源:ske的博客
原文地址:https://skewwg.github.io/2021/06/15/diao-yu-yu-she-gong-xi-lie-zhi-wei-xin-liao-tian-ji-lu-jie-mi/
0x01 前言
该文是从网上各文章中东拼西凑出来的一篇纯复现文章,分享该文章目的是为了让没基础的朋友也能够复现。也请看到该篇文章的朋友严禁将该方法用于非授权的测试及非法用途,否则后果自负。
0x02 逆向获取密钥
微信的数据库存储在本地,好友微信id等各种信息存储在该目录下
C:\Users\xxxxxx\Documents\WeChat Files\wxid_xxxxxxxxxx\Msg
和微信好友的聊天记录存储在multi目录下的MSG0.db数据库里
C:\Users\xxxxxx\Documents\WeChat Files\wxid_xxxxxxxxxx\Msg\Multi
解密这些数据库需要密钥,密钥是所有数据库共用同一个密钥,所以逆向的时候找到解密其中一个数据库时用的密钥即可。
至于如何找到这个密钥,因为这些数据库都是通过AES加密,而AES的密钥是32位的,32的十六位进制是20,所以在逆向的过程中重点关注20这个数字。
下面开始详细的寻找密钥过程,因为水平有限,所以跟着前辈的路线走,目的就是为了找到密钥是多少
从下面这篇文章中知道可以通过 open db fail=%d ,error=%s 这个字符串取定位到密钥附近的代码
于是使用x32dbg去跟踪,使用x32dbg打开wechat.exe程序
会弹出微信的登录框
右键–>搜索–>所有模块–>字符串
在引用的搜索框中输入 open db fail=%d ,error=%s
选中这两行按F2设置断点
根据文章中的说明对红框中的代码设置断点
于是双击第一个断点,进入CPU页面,对je wechatwin.57A3DCDB设置断点
接下来重启
双击登录
此时跳到了刚才断点处
按F7单步步进,进入到 cmp dword ptr ds:[ebx+30],0
往下翻,对红框的内容设置断点
按F9重新回到了一开始的断点处
继续按F9跳到了push eax,再按F9跳到push esi,然后查看eax的值
选中EAX的值,右键–>在内存窗口中转到,可以看到下一个字节是数字20
得到了密钥的地址:88 4D 88 04,接下来跳转到地址04884D88获取密钥的值,右键–>转到–>表达式
输入刚才得到的地址04884D88
跳转过去后,取32个字节,即红框里的内容
04884D88 6F 50 9B 68 38 4C 46 68 8B A0 8C 54 E1 0A DA 74 oP.h8LFh. .Tá.Út
04884D98 21 30 09 87 52 84 43 AF 8E CF C9 14 F4 DF 95 19 !0..R.C¯.ÏÉ.ôß..
此时密钥拿到了,接下来尝试解密
0x03 解密本地数据库
接下来需要配置解密代码的项目,用Visual Studio创建C++控制台应用,根据顺序将openssl的include添加进去
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[] = { 0xba,0x9f,0x56,0x04,0xc7,0x6b,0x4a,0xd5,0x8c,0xf7,0xaa,0x46,0x78,0x2c,0x7f,0xb7,0x2b,0x03,0xc5,0xfd,0x00,0x10,0x41,0xdb,0x9a,0xd6,0x6b,0x5f,0x6b,0x16,0x28,0xe1 };
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;
}
0x04 CE寻找密钥的基址
下一步是如何获取动态密钥,方法是使用CE取寻找密钥的基址,先下载CE和中文语言包,下载地址:
https://d7qe0znl1rfet.cloudfront.net/installer/575474003088649/859880
https://www.cheatengine.org/download/ch_cn.zip
6F509B68384C46688BA08C54E10ADA7421300987528443AF8ECFC914F4DF9519
0x05 实现获取动态密钥的代码
最后就可以通过代码去获取,步骤则是:通过WeChatMainWndForPC获取微信的句柄,通过该句柄获取到微信的进程id,再找到微信的安装目录,拼接路径得到WeChatWin.dll的路径,通过上一步的偏移量得到密钥的值。
同理获取微信id和微信名的偏移量,字符串搜索微信id,找到了很多地址,选择05E开头的,因为密钥的偏移量是05E开头的,那么猜测微信id可能也存在同一个dll里,即WeChatWin.dll里
得到偏移量,同理可以获取微信名称的偏移量。
核心代码如下:
getKey.h
#pragma once
#include <stdio.h>
#include <Windows.h>
#include <tchar.h>
#include <direct.h>
#include <stdlib.h>
#include <TlHelp32.h>
#include <string>
#include <strstream>
#include <list>
#include <Psapi.h>
#pragma comment (lib,"Psapi.lib")
#pragma comment(lib, "Version.lib")
using namespace std;
const string wxVersoin1 = "2.9.0.123";
#define version1 0x16B4D50
#define wxid_addr1 0x16B4914
#define wxname_addr1 0x16B498C
const string wxVersoin2 = "2.9.0.112";
#define version2 0x16B4C70
#define wxid_addr2 0x16B4834
#define wxname_addr2 0x16B48AC
const string wxVersoin3 = "2.9.5.56";
#define version3 0x17744A8
#define wxid_addr3 0x1774054
#define wxname_addr3 0x17740CC
const string wxVersoin4 = "3.2.1.154";
#define version4 0x1AD1F8C
#define wxid_addr4 0x1AC2100
#define wxname_addr4 0x1AD1BAC
DWORD GetProcessIDByName(const char* pName);
PVOID GetProcessImageBase(DWORD dwProcessId, const char* dllName);
DWORD IsWxVersionValid(WCHAR* VersionFilePath);
int GetdbKey(unsigned char* databasekey, unsigned char* wxid);
getKey.cpp
#include "getKey.h"
#include <iostream>
//判断微信版本,确定偏移
DWORD IsWxVersionValid(WCHAR* VersionFilePath)
{
string asVer = "";
VS_FIXEDFILEINFO* pVsInfo;
unsigned int iFileInfoSize = sizeof(VS_FIXEDFILEINFO);
int iVerInfoSize = GetFileVersionInfoSizeW(VersionFilePath, NULL);
if (iVerInfoSize != 0) {
char* pBuf = new char[iVerInfoSize];
if (GetFileVersionInfoW(VersionFilePath, 0, iVerInfoSize, pBuf)) {
if (VerQueryValue(pBuf, TEXT("\\"), (void**)&pVsInfo, &iFileInfoSize)) {
//主版本2.9.0.123
//2
int s_major_ver = (pVsInfo->dwFileVersionMS >> 16) & 0x0000FFFF;
//9
int s_minor_ver = pVsInfo->dwFileVersionMS & 0x0000FFFF;
//0
int s_build_num = (pVsInfo->dwFileVersionLS >> 16) & 0x0000FFFF;
//123
int s_revision_num = pVsInfo->dwFileVersionLS & 0x0000FFFF;
//把版本变成字符串
strstream wxVer;
wxVer << s_major_ver << "." << s_minor_ver << "." << s_build_num << "." << s_revision_num;
wxVer >> asVer;
}
}
delete[] pBuf;
}
printf("var = %s\n", asVer.c_str());
if (asVer == wxVersoin1)
return version1;
else if (asVer == wxVersoin2)
return version2;
else if (asVer == wxVersoin3)
return version3;
else if (asVer == wxVersoin4)
return version4;
else
return 0;
return version4;
}
//通过pid和模块名获取基址
PVOID GetProcessImageBase(DWORD dwProcessId, const char* dllName)
{
PVOID pProcessImageBase = NULL;
MODULEENTRY32 me32 = { 0 };
me32.dwSize = sizeof(MODULEENTRY32);
// 获取指定进程全部模块的快照
HANDLE hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (INVALID_HANDLE_VALUE == hModuleSnap)
{
return pProcessImageBase;
}
// 获取快照中第一条信息
BOOL bRet = ::Module32First(hModuleSnap, &me32);
while (strcmp((char*)me32.szModule, dllName) != 0)
{
Module32Next(hModuleSnap, &me32);
}
pProcessImageBase = (PVOID)me32.modBaseAddr;
// 关闭句柄
::CloseHandle(hModuleSnap);
return pProcessImageBase;
}
//获取数据库密钥
int GetdbKey(unsigned char* databasekey, unsigned char* wxid)
{
HWND phandle = FindWindow("WeChatMainWndForPC", NULL);//获取句柄
if (!phandle)
{
printf("%d\n", GetLastError());
return 0;
}
DWORD pid;
GetWindowThreadProcessId(phandle, &pid);//获取进程id
if (!pid)
{
return 0;
}
printf("wechat pid = %d\n", pid);
HANDLE mProc = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (mProc == NULL)
{
return 0;
}
wchar_t path[MAX_PATH];
if (!GetModuleFileNameExW(mProc, NULL, path, MAX_PATH))
return 0;
else
{
// cout << "path" << path << endl;
for (int i = 0; i < sizeof(path); ++i) {
cout << (char)path[i];
if (path[i] == '.')
{
wcscpy_s(&path[i], sizeof(L"Win.dll"), L"Win.dll");
break;
}
}
}
cout << endl;
for (int i = 0; i < sizeof(path); ++i) {
cout << (char)path[i];
}
//DWORD WxDatabaseKey = IsWxVersionValid(path);
//if (!WxDatabaseKey)
// return 0;
DWORD WxDatabaseKey = version4;
//获取WeChatWin的基址
DWORD base_address = (DWORD)GetProcessImageBase(pid, "WeChatWin.dll");
// printf("dllbase_address = %p\n", base_address);
DWORD dwKeyAddr = base_address + WxDatabaseKey;
//printf("Addr = %p\n", dwKeyAddr);
int addr = 0;
DWORD dwOldAttr = 0;
//获取数据库密钥
ReadProcessMemory(mProc, (LPCVOID)dwKeyAddr, &addr, 4, NULL);
//printf("key addr = %x\n", addr);
ReadProcessMemory(mProc, (LPCVOID)addr, databasekey, 0x20, NULL);
unsigned char wxname[100] = { 0 };
//获取微信昵称
DWORD wxAddr = base_address + wxname_addr4;
//printf("wxAddr = %x\n", wxAddr);
ReadProcessMemory(mProc, (LPCVOID)wxAddr, wxname, 100, NULL);
printf("\nname: %s\n", wxname);
//获取微信id
wxAddr = base_address + wxid_addr4;
//printf("wxAddr = %x\n", wxAddr);
ReadProcessMemory(mProc, (LPCVOID)wxAddr, &addr, 4, NULL);
//printf("wxid addr = %x\n", addr);
ReadProcessMemory(mProc, (LPCVOID)addr, wxid, 100, NULL);
printf("wxid: %s\n", wxid);
//打印密钥
printf("key: ");
for (int i = 0; i < 0x20; i++)
{
printf("%02x", databasekey[i]);
}
return 1;
}
wechatKey.cpp
// wechatKey.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "Getkey.h"
unsigned char pass[33] = { 0 };
unsigned char filepath[256] = { 0 };
unsigned char wxid[256] = { 0 };
int main()
{
std::cout << "Hello World!\n";
GetdbKey(pass, wxid);
}
运行工具得到密钥
0x06 实战
下载MSG0.db文件
获取密钥
本地解密得到聊天数据内容
0x07 总结
0x08 参考链接
核心思路 https://www.xuenixiang.com/thread-2409-1-1.html
https://bbs.pediy.com/thread-251303.htm
https://www.dongzt.cn/archives/495.html
https://www.52pojie.cn/thread-1084703-1-1.html
https://blog.csdn.net/weixin_30230009/article/details/105100181
配置openssl教程 https://bbs.pediy.com/thread-251303-1.htm
基址寻找 https://zhuanlan.zhihu.com/p/95932843
核心代码 https://github.com/A2kaid/Get-WeChat-DB