逆向工程趣谈:我是如何用API Hook破解微信数据库加密的(3.3版本亲测)

📅 发布时间:2026/7/11 4:02:17 👁️ 浏览次数:
逆向工程趣谈:我是如何用API Hook破解微信数据库加密的(3.3版本亲测)
逆向工程趣谈我是如何用API Hook破解微信数据库加密的3.3版本亲测那天下午我盯着屏幕上那个加密的ChatMsg.db文件心里那股技术人的“轴劲儿”又上来了。微信的本地聊天记录明明就在那里却因为一层数据库加密而变得遥不可及。对于安全研究者和逆向爱好者来说这不仅仅是一个数据文件更像是一个等待开启的“黑盒”里面藏着程序与系统交互的秘密。我决定这次不依赖任何现成的“一键解密”工具而是亲自走一遍从静态分析到动态调试再到编写自动化脚本的完整路径目标直指微信PC 3.3版本的核心加密逻辑。这个过程与其说是“破解”不如说是一次对Windows平台下软件保护机制和逆向工程方法的深度实践与复盘。1. 目标锁定逆向分析的起点与思路逆向工程的第一步永远是明确目标。我们的目标是获取微信本地聊天数据库的明文内容。微信PC版使用SQLCipher对ChatMsg.db等数据库文件进行加密而解密的关键在于一个32字节的密钥。这个密钥并非由用户输入而是在程序运行时动态生成并驻留在内存中。因此我们的核心思路从“破解算法”转变为“窃取密钥”。为什么选择API Hook在逆向工程中获取内存数据有多种方式比如直接内存扫描、调试器下断点、或注入代码。API Hook函数钩子技术提供了一种相对优雅和可控的方案。它的原理是修改目标函数在内存中的前几个字节使其跳转到我们自定义的函数中。在我们的自定义函数里我们可以读取函数的参数其中就包含密钥执行我们需要的操作如将密钥写入文件然后再恢复原函数执行。这种方法对原进程的干扰相对较小且能精准地在密钥被使用的瞬间捕获它。我们需要找到那个负责数据库加密或密钥处理的函数。根据社区已有的零星信息和分析这个函数很可能位于WeChatWin.dll这个核心模块中并且与DBFactory::encryptDB这个符号名相关。我们的逆向之旅就从定位这个函数开始。提示所有逆向分析行为应仅用于学习、研究软件工作原理或安全评估自己拥有合法权限的软件。请务必遵守相关法律法规和服务条款。2. 手动探秘调试器下的密钥追踪在编写自动化脚本之前手动使用调试器走一遍流程至关重要。这能帮助我们直观地理解程序的执行流和关键数据布局。我使用的是 x64dbg一款强大的开源调试器。2.1 定位关键函数首先启动微信PC版但先不登录。然后用 x64dbg 附加到WeChat.exe进程。附加成功后在“符号”选项卡或通过“查找”功能定位到WeChatWin.dll模块。接下来我们需要在模块中搜索字符串。在 x64dbg 中可以通过CtrlF打开字符串搜索功能。我们搜索字符串DBFactory::encryptDB。在 3.3 版本中我找到了两处引用。引用地址附近代码特征可能性分析引用 A函数内部仅有一个call指令后就return可能是初始化或间接调用点非关键校验处。引用 B函数内部在push一些参数后有一条test edx, edx指令test指令常用于检验指针是否有效edx很可能就是指向密钥的指针。此处是关键。双击跳转到引用 B 的地址我们看到了类似如下的汇编代码片段地址会因版本而异... 一些 push 指令 ... mov ecx, [ebparg_0] lea edx, [ebpkey_buffer] test edx, edx jz short loc_xxxxxx ... 后续代码 ...这条test edx, edx就是我们的“黄金断点”。在它上面双击设置一个断点F2。然后让调试器继续运行F9并回到微信界面进行登录操作。2.2 捕获密钥当登录流程触发数据库操作时调试器会断在刚才设置的位置。此时寄存器edx的值就是指向密钥缓冲区的指针。在 x64dbg 的寄存器窗口右键点击EDX寄存器选择“在数据窗口中跟随”。数据窗口会显示以该地址开始的内存数据。我们需要提取连续的 32 个字节256位。在数据窗口选中这32个字节右键选择“二进制” - “编辑” - “复制数据”格式选择“十六进制字符串空格分隔”或直接保存为二进制文件。至此我们手动获取到了解密数据库所需的 32 字节密钥。这个过程验证了我们的猜想密钥在登录后的某个时刻会以指针形式传递给这个校验函数。3. 构建解密环境OpenSSL与SQLCipher拿到密钥只是第一步我们还需要一个能使用该密钥解密 SQLCipher 数据库的工具。SQLCipher 是 SQLite 的一个加密扩展它底层依赖加密库早期版本通常使用 OpenSSL。3.1 编译 OpenSSL 1.0.2u由于兼容性考虑我们需要编译与微信所用 SQLCipher 版本匹配的 OpenSSL。这里选择 1.0.2u 版本。安装 PerlOpenSSL 的配置脚本需要 Perl 环境。从 ActiveState 官网下载并安装 ActivePerl。准备 Visual Studio 命令行工具打开 “x86 Native Tools Command Prompt for VS 2019”根据你的VS版本选择32位或64位这里为兼容性选x86。编译 OpenSSL# 切换到OpenSSL源码目录 cd /d E:\openssl-1.0.2u # 配置生成32位动态库不使用汇编优化简化 perl Configure VC-WIN32 no-asm --prefixE:\openssl_build # 生成Makefile ms\do_ms.bat # 编译 nmake -f ms\ntdll.mak # 测试编译结果可选 nmake -f ms\ntdll.mak test # 安装到指定目录 nmake -f ms\ntdll.mak install编译完成后在E:\openssl_build目录下会得到include,lib,bin等文件夹。3.2 编写解密程序我们可以编写一个简单的 C 程序利用 SQLCipher 的 API 进行解密。这里需要一个关键的sqlite3.dll需支持 SQLCipher。你可以从 SQLCipher 官方发布页或某些开源项目中获取。以下是一个核心解密函数的示例#include sqlite3.h #include stdio.h #include string.h int decrypt_database(const char* encrypted_db_path, const char* decrypted_db_path, const unsigned char* key_hex) { sqlite3* db; int rc; char* err_msg 0; // 打开加密数据库 rc sqlite3_open(encrypted_db_path, db); if (rc) { fprintf(stderr, 无法打开数据库: %s\n, sqlite3_errmsg(db)); return rc; } // 设置解密密钥。SQLCipher 的 key 通常直接传递二进制数据。 // 注意这里假设 key_hex 是32字节的二进制数据而不是十六进制字符串。 rc sqlite3_key(db, key_hex, 32); if (rc ! SQLITE_OK) { fprintf(stderr, 设置密钥失败\n); sqlite3_close(db); return rc; } // 执行一个简单查询来验证密钥是否正确 rc sqlite3_exec(db, SELECT count(*) FROM sqlite_master;, NULL, NULL, err_msg); if (rc SQLITE_OK) { printf(密钥正确数据库已解锁。\n); // 此处可以开始导出或直接操作解密后的数据。 // 为了生成解密后的文件我们可以 ATTACH 一个新数据库并导出。 char attach_sql[512]; snprintf(attach_sql, sizeof(attach_sql), ATTACH DATABASE %s AS plaintext KEY ;, decrypted_db_path); rc sqlite3_exec(db, attach_sql, NULL, NULL, err_msg); if (rc SQLITE_OK) { rc sqlite3_exec(db, SELECT sqlcipher_export(plaintext);, NULL, NULL, err_msg); if (rc SQLITE_OK) { rc sqlite3_exec(db, DETACH DATABASE plaintext;, NULL, NULL, err_msg); printf(数据库已成功解密并保存至: %s\n, decrypted_db_path); } } } else { fprintf(stderr, 密钥错误或数据库损坏: %s\n, err_msg); sqlite3_free(err_msg); } sqlite3_close(db); return rc; }在 Visual Studio 项目中你需要配置好 OpenSSL 和 SQLCipher 的头文件与库文件路径并将编译好的libeay32.dll、ssleay32.dll和sqlite3.dll与你的可执行文件放在一起。最后将程序复制到微信的Msg目录下路径如...\WeChat Files\你的微信ID\Msg\运行并传入密钥即可得到解密的ChatMsg.db。4. 自动化实现编写API Hook DLL手动操作虽然有效但每次登录都要重复调试步骤过于繁琐。我们的目标是实现自动化启动微信、登录、自动捕获密钥、解密数据库一气呵成。这需要通过 DLL 注入和 API Hook 来实现。4.1 Hook 原理与实现我们的 Hook 目标是WeChatWin.dll中那个包含test edx, edx指令的函数。Hook 的基本步骤是“五字节跳转”计算跳转我们的 Hook 函数地址与原函数地址的偏移量构造一个JMP指令0xE9 偏移量。备份原字节保存目标函数前5个字节因为JMP指令需要5字节。写入跳转将构造好的JMP指令写入目标函数开头。执行流程当程序调用该函数时会先跳转到我们的 Hook 函数。在 Hook 函数中读取参数获取edx指向的密钥保存到文件然后恢复原函数的前5个字节Unhook最后跳回原函数继续执行。以下是 Hook DLL 的核心代码框架// GetPwHookDll.cpp #include windows.h #include stdio.h // 假设通过逆向分析得到的目标函数在 WeChatWin.dll 中的偏移量 #define TARGET_FUNC_OFFSET 0x53677E70 FARPROC hOriginalFunc NULL; BYTE originalBytes[5] {0}; // 自定义的Hook函数使用裸函数确保栈平衡 __declspec(naked) void MyHookFunc() { __asm { pushad // 保存所有寄存器 pushfd // 保存标志寄存器 // 此时栈顶是返回地址原函数的参数在返回地址之上 // 根据逆向分析密钥指针在 [esp0x??] 的位置需要具体分析 // 假设我们分析出密钥指针在 edx 中在test edx,edx时 mov eax, edx // 将edx密钥指针暂存到eax } // C部分保存密钥 HANDLE hFile CreateFileW(L.\\key.bin, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile ! INVALID_HANDLE_VALUE) { // 注意此时eax保存着密钥指针 DWORD keyPtr; __asm mov keyPtr, eax WriteFile(hFile, (LPCVOID)keyPtr, 32, NULL, NULL); CloseHandle(hFile); OutputDebugStringW(L[Hook] 密钥已保存。); } // 恢复原函数字节Unhook WriteProcessMemory(GetCurrentProcess(), (LPVOID)hOriginalFunc, originalBytes, 5, NULL); __asm { popfd // 恢复标志寄存器 popad // 恢复所有寄存器 // 跳回原函数执行跳过我们覆盖的5个字节 jmp [hOriginalFuncAdd5] // 这是一个需要计算的地址指向原函数第6字节 } } // 计算跳转地址的辅助函数 void HookByCode(FARPROC targetFunc, PROC hookFunc) { hOriginalFunc targetFunc; // 1. 备份原字节 ReadProcessMemory(GetCurrentProcess(), targetFunc, originalBytes, 5, NULL); // 2. 计算跳转偏移 DWORD jmpOffset (DWORD)hookFunc - (DWORD)targetFunc - 5; // 3. 构造JMP指令 BYTE jmpCode[5] { 0xE9 }; // JMP opcode memcpy(jmpCode[1], jmpOffset, 4); // 4. 写入跳转 WriteProcessMemory(GetCurrentProcess(), targetFunc, jmpCode, 5, NULL); } // DllMain - 注入入口点 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); // 获取目标函数地址 HMODULE hWeChatWin GetModuleHandleW(LWeChatWin.dll); if (hWeChatWin) { FARPROC pTargetFunc (FARPROC)((DWORD)hWeChatWin TARGET_FUNC_OFFSET); HookByCode(pTargetFunc, (PROC)MyHookFunc); } } return TRUE; }4.2 注入与自动化流程编写一个加载器程序如AllInOne.exe来统筹整个自动化流程启动微信使用CreateProcess启动WeChat.exe。等待初始化使用WaitForInputIdle或延时等待微信主窗口出现。DLL注入通过CreateRemoteThread或SetWindowsHookEx等方式将编译好的 Hook DLL 注入到微信进程。模拟登录可以发送消息或模拟点击登录按钮这一步可能比较复杂有时手动操作更简单。我们的DLL Hook住函数后一旦用户手动登录触发密钥校验就会自动保存密钥。监控密钥文件加载器循环检测key.bin文件是否生成。一旦生成读取密钥。复制数据库并解密找到微信存储目录下的Msg文件夹复制ChatMsg.db等文件到工作目录调用之前写好的解密程序传入密钥进行解密。清理可选步骤卸载DLL、关闭进程等。// AllInOne.cpp 简化流程示例 int main(int argc, char* argv[]) { // 1. 解析参数获取微信安装目录和用户数据目录 // 2. 启动WeChat.exe STARTUPINFO si { sizeof(si) }; PROCESS_INFORMATION pi; CreateProcess(..., LWeChat.exe, ..., si, pi); // 3. 等待并注入DLL Sleep(5000); // 等待微信加载 InjectDLL(pi.dwProcessId, LHookDll.dll); printf(请手动登录微信...\n); printf(正在等待密钥被捕获...\n); // 4. 轮询检查密钥文件 while (!PathFileExists(L.\\key.bin)) { Sleep(1000); } // 5. 读取密钥 unsigned char key[32]; ReadKeyFromFile(L.\\key.bin, key); // 6. 复制并解密数据库 CopyDatabaseFiles(userDataPath, workPath); DecryptDatabase(workPath, key); printf(解密完成\n); return 0; }5. 踩坑与思考逆向工程中的细节与伦理在实际操作中我遇到了几个典型问题。首先是偏移地址的稳定性。TARGET_FUNC_OFFSET这个值很可能随着微信版本更新而改变。一个更健壮的方法是使用特征码搜索而不是硬编码偏移。例如在WeChatWin.dll的内存空间中搜索test edx, edx指令附近的特定字节序列来动态定位函数地址。其次是Hook的稳定性。直接修改代码段存在风险。微信可能检测到代码被篡改而崩溃或者因为多线程调用导致在Unhook前其他线程执行了被修改的代码。更高级的做法是使用“蹦床”Trampoline技术或者利用硬件断点Drx寄存器来触发Hook这对原代码的侵入性更小。最后也是最重要的是关于逆向工程的目的与边界。我进行这次探索纯粹是出于对软件内部机制和技术实现的好奇心是一种学习与研究行为。整个过程都在我自己合法拥有的电脑和软件副本上进行。法律风险未经授权对他人软件进行逆向工程、解密数据可能违反《计算机软件保护条例》以及软件自身的最终用户许可协议EULA甚至涉及侵犯商业秘密或非法获取计算机信息系统数据罪。道德准则安全研究应遵循“负责任披露”原则。发现漏洞后应首先尝试联系厂商给予其合理的修复时间而不是公开利用方法或制作破坏性工具。技术用途通过此类实践学到的 API Hook、进程注入、调试器使用、密码学接口调用等知识可以正当地应用于软件安全分析、恶意代码研究、自动化测试工具开发等领域。这次对微信数据库加密机制的探索像一次精细的外科手术让我对Windows平台下的软件保护与逆向技术有了更深的体会。技术本身是中立的关键在于使用它的人怀有何种目的。对于开发者而言理解这些攻防思路也有助于设计出更安全的软件。最终那个解密的ChatMsg.db静静地躺在文件夹里而我收获的远不止其中的聊天记录更是整个探究过程中对系统底层交互的又一次亲密接触。