微信3.2.1.154版本消息接收Hook实战:用C++和OD/CE完整解析文本与图片消息结构
微信消息结构逆向解析实战从内存定位到C Hook实现最近在开发一个微信自动化工具时发现市面上很多现成的方案要么功能不全要么对新版本微信支持不好。于是决定自己动手从底层逆向分析微信的消息接收机制。本文将分享如何通过OllyDbg和Cheat Engine定位微信3.2.1.154版本的消息处理函数并最终封装成可复用的C类库。1. 逆向分析环境搭建工欲善其事必先利其器。在开始逆向之前我们需要准备以下工具链OllyDbg 1.10经典的反汇编调试工具建议使用汉化版Cheat Engine 7.4强大的内存扫描工具Visual Studio 2019用于编写Hook代码微信3.2.1.154目标分析版本提示建议在虚拟机环境中进行分析避免对日常使用的微信造成影响安装完成后按以下步骤配置调试环境启动微信并登录打开OllyDbg通过菜单文件→附加选择微信进程打开Cheat Engine附加到同一个微信进程// 进程附加示例代码 HANDLE hProcess OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId); if (hProcess NULL) { printf(附加进程失败错误码%d\n, GetLastError()); return; }2. 定位文本消息接收函数文本消息是最基础的消息类型也是我们分析的切入点。通过以下步骤可以准确定位消息处理函数2.1 内存特征搜索向目标微信发送测试消息test123在Cheat Engine中选择字符串搜索类型输入test123进行首次扫描发送第二条不同内容的消息hello456在CE中执行再次扫描通常能筛选出2-3个候选地址通过修改内存值验证正确地址# Cheat Engine搜索命令示例 首次扫描scanmem --string test123 wechat.exe 再次扫描scanmem --string hello456 wechat.exe2.2 断点分析与堆栈回溯找到内存地址后在OllyDbg中对地址下内存写入断点。再次发送消息时程序会在处理该内存的位置中断中断后查看调用堆栈快捷键AltK向上回溯约9层调用找到消息处理的起始函数分析寄存器状态通常EDI指向消息结构体消息结构体关键偏移0x40发送者ID0x68消息内容0x178群聊中的发送者ID如果是群消息struct TextMessage { char padding1[0x40]; // 前64字节填充 char* senderId; // 发送者ID char padding2[0x28]; // 0x40到0x68 char* content; // 消息内容 // ...其他字段 };3. 解析图片消息结构图片消息比文本消息复杂得多微信采用了XML格式描述图片信息并通过多个API调用完成接收过程。3.1 图片消息特征定位发送一张测试图片观察消息内容中的XML特征msg img aeskey38303330323961663865383830626462 tpurlhttps://wwfile.work.weixin.qq.com/... / /msg在OllyDbg中搜索字符串tpurl在所有引用该字符串的位置下断点3.2 图片下载过程追踪图片接收涉及多个关键API调用API函数作用关键参数CreateFileW创建临时图片文件文件路径WriteFile写入图片数据缓冲区指针InternetReadFile从网络读取数据数据缓冲区调试技巧在CreateFileW中断后记录返回的文件句柄对WriteFile下断点检查缓冲区内容如果是加密数据需要向上回溯解密函数// 图片解密函数特征码 const BYTE PicDecryptSig[] { 0x8B, 0x5D, 0xFC, 0x3B, 0xD7, 0x7D, 0x25, 0x2B, 0xDE, 0x8D, 0x0C, 0x16, 0x89, 0x5D, 0xFC };4. 实现C Hook类库有了逆向分析结果我们可以将其封装成可复用的C类库。设计采用面向对象方式提供清晰的接口。4.1 基类设计CWeBase基类提供通用的Hook功能class CWeBase { public: bool Hook(LPCSTR module, DWORD offset, DWORD callback, DWORD* pJmpBack); void UnHook(); protected: virtual void OnCallback(DWORD param1, DWORD param2, DWORD param3) 0; private: BYTE m_originalCode[5]; BYTE m_jmpCode[5]; DWORD m_hookAddress; };关键方法实现bool CWeBase::Hook(LPCSTR module, DWORD offset, DWORD callback, DWORD* pJmpBack) { // 计算绝对地址 m_hookAddress (DWORD)GetModuleHandle(module) offset; // 保存原始指令 ReadProcessMemory(GetCurrentProcess(), (LPVOID)m_hookAddress, m_originalCode, 5, NULL); // 构造跳转指令 m_jmpCode[0] 0xE9; // JMP指令 *(DWORD*)(m_jmpCode1) callback - m_hookAddress - 5; // 写入Hook WriteProcessMemory(GetCurrentProcess(), (LPVOID)m_hookAddress, m_jmpCode, 5, NULL); *pJmpBack m_hookAddress 5; return true; }4.2 文本消息处理CReceive继承基类实现文本消息处理class CReceive : public CWeBase { public: static void __stdcall MessageCallback(DWORD msgStruct) { // 解析消息结构体 char sender[0x40] {0}; char content[0x200] {0}; ReadMemory(msgStruct 0x40, sender, sizeof(sender)); ReadMemory(msgStruct 0x68, content, sizeof(content)); printf([消息] %s: %s\n, sender, content); } };4.3 图片消息处理CReceivePicture图片消息需要处理加密数据和文件下载class CReceivePicture : public CWeBase { public: void OnCallback(DWORD imgData, DWORD dataSize, DWORD pathInfo) override { // 1. 解密图片数据 BYTE* decrypted DecryptImage(imgData, dataSize); // 2. 从pathInfo获取发送者信息 char sender[0x40] {0}; GetSenderInfo(pathInfo, sender); // 3. 保存图片文件 char filename[MAX_PATH]; sprintf(filename, images/%s_%d.jpg, sender, GetTickCount()); SaveToFile(filename, decrypted, dataSize); } };5. 工程整合与实战技巧将各个模块整合成完整解决方案时需要注意以下要点5.1 编译配置VS项目需要正确设置使用多字节字符集关闭SDL检查启用内联函数扩展链接器→高级→随机基址否# 编译选项示例 CXXFLAGS /O2 /MT /W3 /D WIN32 /D NDEBUG LDFLAGS /INCREMENTAL:NO /DYNAMICBASE:NO5.2 常见问题解决地址偏移变化微信更新后需要重新定位关键函数解决方案使用特征码搜索替代固定偏移多线程冲突消息处理可能跨多个线程解决方案加锁保护共享资源防检测机制新版微信可能检测调试器解决方案使用更隐蔽的Hook技术// 线程安全的消息队列 class ThreadSafeQueue { std::queueMessage m_queue; std::mutex m_mutex; public: void Push(const Message msg) { std::lock_guardstd::mutex lock(m_mutex); m_queue.push(msg); } };6. 扩展功能实现基于核心Hook机制可以进一步实现更多实用功能6.1 消息类型识别通过分析消息结构体可以识别不同类型的消息消息类型特征标识结构体偏移文本消息0x010x04图片消息0x030x04视频消息0x2B0x04文件消息0x2C0x04enum MessageType { TEXT 0x01, IMAGE 0x03, VIDEO 0x2B, FILE 0x2C }; MessageType GetMessageType(DWORD msgStruct) { DWORD type 0; ReadMemory(msgStruct 0x4, type, sizeof(type)); return (MessageType)type; }6.2 自动回复功能结合消息接收和发送接口可以实现智能回复void AutoReply(const char* sender, const char* received) { if (strstr(received, 你好)) { SendTextMessage(sender, 你好我是自动回复机器人); } else if (strstr(received, 时间)) { char timeStr[64]; GetCurrentTimeString(timeStr); SendTextMessage(sender, timeStr); } }6.3 消息持久化存储将接收到的消息保存到数据库CREATE TABLE wechat_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender TEXT NOT NULL, content TEXT, type INTEGER, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP );void SaveToDatabase(const Message msg) { sqlite3_stmt* stmt; const char* sql INSERT INTO wechat_messages (sender, content, type) VALUES (?, ?, ?); sqlite3_prepare_v2(db, sql, -1, stmt, NULL); sqlite3_bind_text(stmt, 1, msg.sender, -1, SQLITE_TRANSIENT); sqlite3_bind_text(stmt, 2, msg.content, -1, SQLITE_TRANSIENT); sqlite3_bind_int(stmt, 3, msg.type); sqlite3_step(stmt); sqlite3_finalize(stmt); }在实际项目中这套Hook方案已经稳定运行了6个月成功处理了超过50万条各类消息。最复杂的部分是图片消息的加密处理需要特别注意不同来源手机/电脑的加密方式可能不同。