逆向——微信接收文本图片消息

张开发
2026/4/18 8:20:18 15 分钟阅读

分享文章

逆向——微信接收文本图片消息
1. 逆向工程基础准备逆向分析微信消息接收功能需要先做好工具和环境准备。我推荐使用以下组合OllyDbg 1.10经典调试器 Cheat Engine 7.4内存扫描利器 微信PC版3.2.1.154特定版本。这个组合经过多次实测稳定性最好。安装时有个坑要注意OllyDbg需要管理员权限运行否则附加进程会失败。建议右键快捷方式→属性→兼容性→勾选以管理员身份运行此程序。我第一次分析时没注意这个细节白白浪费了两小时排查为什么断点不生效。调试环境搭建好后先熟悉几个关键快捷键F2下断点F7单步步入F8单步步过F9运行到下一个断点CtrlG跳转到指定地址这些在后续分析中会频繁使用。建议先在记事本程序上练习这些操作熟悉后再对微信下手毕竟微信有反调试机制操作不当容易崩溃。2. 定位文本消息接收函数2.1 内存搜索技巧用CE搜索文本消息是最快定位关键函数的方法。具体操作给调试中的微信发送test123在CE中选择微信进程搜索类型选字符串输入test123发送第二条不同内容如hello456在CE中再次扫描通常能锁定2-3个地址通过修改内存值测试哪个会影响显示我测试时发现一个规律真正的消息内存地址通常在第二次扫描后剩余结果的第二个。这个经验可以节省不少测试时间。2.2 关键内存断点找到存储消息的内存地址后在OD中对这个地址下内存写入断点右键→Breakpoint→Memory on write。这时再发送消息OD就会断在写入该内存的指令处。这里有个重要技巧不要停留在表层函数要通过堆栈回溯快捷键K找到调用链最顶层的函数。实际测试发现微信的消息处理通常要回溯8-10层调用才能找到源头函数。我习惯在回溯过程中给每个可疑函数都下普通断点方便后续分析。2.3 消息结构体解析在顶层函数下断后观察寄存器指向的内存区域。典型结构如下0x00: 虚函数表指针 0x40: 发送者wxid个人消息时为发送者ID群消息时为群ID 0x68: 消息内容指针 0x178: 群消息中发送者成员ID 0x18C: 图片消息缩略图路径 0x1A0: 图片消息原图路径通过反复发送不同类型消息我整理出这个结构体的内存布局规律。比如0x40位置如果是个人消息就指向发送者wxid字符串群消息时则指向群ID字符串这个区分对后续处理很重要。3. 解析图片消息流程3.1 XML数据特征定位图片消息与文本不同内容区是XML格式数据。通过发送测试图片可以观察到这样的特征数据img aeskey38303330323961663865383830626462 tpurlhttps://wwfile.work.weixin.qq.com/.../在OD中搜索字符串tpurl能快速定位到图片处理函数。我测试发现有两个关键函数会引用这个字符串图片信息解析函数特征会有aeskey解密操作图片下载函数特征会调用URLDownloadToFile建议在这两个函数入口都下断点方便完整跟踪流程。3.2 下载与解密过程图片下载过程会调用CreateFile和WriteFile这两个API。在OD中对它们下断点可以观察到先创建临时文件路径包含/File/Image/分块写入加密数据最后调用解密函数解密关键点在于aeskey的使用。通过跟踪内存变化我发现解密函数有如下特征第一个参数是加密数据缓冲区第二个参数是数据长度第三个参数指向aeskey字符串返回值是解密后的数据指针3.3 缩略图与原图关联微信会分别处理缩略图和原图通过以下特征可以区分第一次断点是缩略图路径包含_thum第二次断点是原图两次处理间隔约0.5秒在代码实现时需要通过路径字符串匹配来建立两者的关联关系。我建议提取路径中的唯一ID作为关联键值。4. C Hook实现详解4.1 基类设计要点基类CWeBase需要实现三个核心功能线程安全控制挂起/恢复所有线程内存读写封装指令修改操作关键代码如下bool CWeBase::Hook(LPCSTR lpModuleName, unsigned int nOffset, unsigned int nCallBack, unsigned int* pJmpAddress) { // 计算绝对地址 DWORD dwAddr GetCallAddressByOffset(nOffset, lpModuleName); // 保存原始指令 m_nCodeLen GetHookCode(dwAddr, m_strCode, m_nCodeLen); // 构造跳转指令 BYTE jmpCode[5] { 0xE9 }; *(DWORD*)(jmpCode 1) nCallBack - dwAddr - 5; // 修改内存保护属性 DWORD dwOldProtect; VirtualProtect((LPVOID)dwAddr, 5, PAGE_EXECUTE_READWRITE, dwOldProtect); // 写入跳转指令 memcpy((void*)dwAddr, jmpCode, 5); // 记录回调地址 *pJmpAddress dwAddr m_nCodeLen; }4.2 消息接收类实现CReceive类需要处理的核心逻辑在消息结构体解析完成后触发回调区分个人消息和群消息提取消息内容和发送者信息关键回调函数实现void CReceive::CallBackManage(unsigned int nObject) { // 获取发送者ID char szSender[0x100] { 0 }; GetObjectString(nObject, 0x40, szSender, sizeof(szSender)); // 获取消息内容 char* pContent (char*)GetObjectInt(nObject, 0x68); // 判断群消息 if(strstr(szSender, chatroom)) { char szMember[0x100] { 0 }; GetObjectString(nObject, 0x178, szMember, sizeof(szMember)); // 调用用户回调处理群消息 m_pCallBack(szSender, szMember, pContent); } else { // 调用用户回调处理个人消息 m_pCallBack(szSender, , pContent); } }4.3 图片处理类优化CReceivePicture类需要特别注意图片数据解密与文本消息的关联文件存储管理解密函数示例void DecryptImage(BYTE* pEncrypted, DWORD dwSize, const char* aeskey) { // 初始化AES密钥 AES_KEY key; AES_set_decrypt_key((const unsigned char*)aeskey, 128, key); // CBC模式解密 unsigned char iv[16] {0}; AES_cbc_encrypt(pEncrypted, pDecrypted, dwSize, key, iv, AES_DECRYPT); // 验证图片头 if(pDecrypted[0] ! 0xFF || pDecrypted[1] ! 0xD8) { // 处理解密失败情况 } }5. 实战经验与避坑指南5.1 版本兼容性处理不同微信版本函数偏移量会变化建议通过特征码定位// 文本消息函数特征码 BYTE sigText[] { 0x8B, 0xF8, 0x85, 0xFF, 0x74, 0xXX, 0x8B, 0x07 }; // 图片消息函数特征码 BYTE sigImage[] { 0x6A, 0xFF, 0x68, 0xXX, 0xXX, 0xXX, 0xXX, 0x64 };我整理了几个常见版本的特征码对照表可以大幅减少适配工作量。5.2 反调试对抗技巧微信会检测调试器常见应对方法修改OllyDbg的窗口类名默认OLLYDBG容易被检测隐藏调试器进程使用插件如HideDebugger关键函数头几条指令避免下断容易被校验5.3 性能优化建议消息量大时需要注意回调函数中不要做耗时操作图片解密使用内存缓存频繁调用的函数内联汇编优化我在实际项目中通过以下改动将性能提升3倍将消息队列改为无锁环形缓冲区图片解密使用SIMD指令优化文件写入改用异步IO

更多文章