目录
前言
参考大神anhkgg的文章https://bbs.pediy.com/thread-249274.htm
实现目前最新的微信版本:3.2.1.154
并扩展支持上接收图片、视频、文件、小视频等信息
一、找接收消息的位置
使用工具
OllyDbg、CE(Cheat Engine)
1、打开OllyDbg、CE(Cheat Engine)和微信,附加微信进程到OllyDbg、CE(Cheat Engine)中
OD附加有标题“微信”的进程,CE附加与OD加载的进程ID相同的进程
2、给加载上调试的微信发一个消息:“123456”。切到CE界面,点击“新的扫描”,扫描类型选择“搜索字符串”,输入文本“123456”,点击首次扫描,发现查到很多地址;
3、再次发送“abcdef”,把CE界面搜索文本改为“abcdef”,注意这次点击再次扫描,发现还剩两个地址,把查询结果添加到地址列表
4、改变其中一个数值,如果微信收到的内容跟着变,那么说明是这个地址,如果没变,说明是另一个。找到这个内存后接下来在OD中下内存写入断点;
5、再次发一条信息,在段下来的地方根据堆栈向上找接收内容的来源,最终向上回溯大概9次,点击K查看堆栈的话是这个位置
6、在这里下断点观察EDI的内容,EDI指向内存再指向的内存如下图所示。+40的位置是发送者ID,+68的位置是发送的内容。如果改成群发送的文本,+40的位置是群ID,+178是发送者的群成员ID
二、找接收图片地址
1、发送一张图片,发现内容部分是一串xml格式字符
<?xml version="1.0"?>
<msg>
<img aeskey="38303330323961663865383830626462" encryver="0" cdnthumbaeskey="" cdnthumburl="" cdnthumblength="0" cdnthumbheight="0" cdnthumbwidth="0" cdnmidheight="0" cdnmidwidth="0" cdnhdheight="0" cdnhdwidth="0" cdnmidimgurl="" length="0" tpthumburl="https://wwfile.work.weixin.qq.com/cgi-bin/download?f=30680201020461305f0201000204edb3413602030f55ca02045b6cc2dc020460a37bf7042435353632333961362d623138302d346366662d383162302d6362353663643936323635320201000202052004102181b6b9c48579d8654f6879536581310201010201000400&t=70F95321C6137C5FE12CE67AC53A6475E7BF5274DC1132D233AC7140376BBA5726343EF95DFB4C2C60BD3887E2EBA21F15D86ACC22464085DBF1C88083E05964211CF2E2A981440A09207130D1E8705AAA6409FFFC159D078B20FA3FE5C6B639D710E171C6AC2FF9BE4D7CF00639A8FD4D8B200A876EBDE5BA43E912D1177CFFD802B62DE3682C9236F7ECF52C1C4A61910FA7602716878D840BD05AE49E496EBD88C1E1290171813060E5EDE1600F6F294833A53F301821F25BF1597F1E6D4E89BCFB37D3AB83EAACBDC02F033E29A5C9A1BB552D97FB0FA8E9F5695AAD02787A87372534B0F9160BF65C1C08B4DA33BD13010C1AA2B94510CFBC4EC4B2C1060F7DA6986C06298940CB97D1277BF365&p=3" tpthumblength="2522" tpthumbheight="131" tpthumbwidth="224" tpthumbaeskey="38303330323961663865383830626462" tpurl="https://wwfile.work.weixin.qq.com/cgi-bin/download?f=30680201020461305f0201000204edb3413602030f55ca02045b6cc2dc020460a37bf7042435353632333961362d623138302d346366662d383162302d6362353663643936323635320201000202052004102181b6b9c48579d8654f6879536581310201010201000400&t=70F95321C6137C5FE12CE67AC53A6475E7BF5274DC1132D233AC7140376BBA5726343EF95DFB4C2C60BD3887E2EBA21F15D86ACC22464085DBF1C88083E05964211CF2E2A981440A09207130D1E8705AAA6409FFFC159D078B20FA3FE5C6B639D710E171C6AC2FF9BE4D7CF00639A8FD4D8B200A876EBDE5BA43E912D1177CFFD802B62DE3682C9236F7ECF52C1C4A61910FA7602716878D840BD05AE49E496EBD88C1E1290171813060E5EDE1600F6F294833A53F301821F25BF1597F1E6D4E89BCFB37D3AB83EAACBDC02F033E29A5C9A1BB552D97FB0FA8E9F5695AAD02787A87372534B0F9160BF65C1C08B4DA33BD13010C1AA2B94510CFBC4EC4B2C1060F7DA6986C06298940CB97D1277BF365&p=2" tplength="1840" tpheight="131" tpwidth="224" tpauthkey="0A2B6F4E2D4D77415141414141444B35692D425A6764323663357252576C314831736F40696D2E7778776F726B10E7C6F6A205" tphdurl="https://wwfile.work.weixin.qq.com/cgi-bin/download?f=30680201020461305f0201000204edb3413602030f55ca02045b6cc2dc020460a37bf7042435353632333961362d623138302d346366662d383162302d6362353663643936323635320201000202052004102181b6b9c48579d8654f6879536581310201010201000400&t=70F95321C6137C5FE12CE67AC53A6475E7BF5274DC1132D233AC7140376BBA5726343EF95DFB4C2C60BD3887E2EBA21F15D86ACC22464085DBF1C88083E05964211CF2E2A981440A09207130D1E8705AAA6409FFFC159D078B20FA3FE5C6B639D710E171C6AC2FF9BE4D7CF00639A8FD4D8B200A876EBDE5BA43E912D1177CFFD802B62DE3682C9236F7ECF52C1C4A61910FA7602716878D840BD05AE49E496EBD88C1E1290171813060E5EDE1600F6F294833A53F301821F25BF1597F1E6D4E89BCFB37D3AB83EAACBDC02F033E29A5C9A1BB552D97FB0FA8E9F5695AAD02787A87372534B0F9160BF65C1C08B4DA33BD13010C1AA2B94510CFBC4EC4B2C1060F7DA6986C06298940CB97D1277BF365&p=1" tphdlength="1299" />
</msg>
2、img有几个属性:aeskey、tpthumbaeskey、tpurl等,随便复制一个,如tpurl,在OD中搜索参考字符串:右键——搜索——所有参考字符串,弹出“索索-文本字符串中引用”窗口,Ctrl+F弹出搜索输入框,输入tpurl点击确定(快捷键Ctrl+L查找下一处;Ctrl+Sheft+L查找上一处)
3、双击查到的行,转到反汇编窗口。在这下断点,并搜索所有Push "tpurl"的地方都下断点(一共查到两处)
4、再次发一张图片,在第一个push “tpurl”的地方断下来,然后再CreateFileW下断,F5后在CreateFileW断下时观察参数路径,一直F5执行直到路径包含File\Image停下,Ctrl+F5执行到返回指令,此时Eax寄存器中保存的是文件句柄,紧接着程序会向文件中写数据,所以在KERNEL32.WriteFile下断点(其他断点都可以先禁用,以免产生干扰),直到参数句柄和打开文件句柄一致
5、此时观察WriteFile第二个参数也就是lpBuffer,发现是空的,再次F5直到有值,此时会发现存的数据格式是图片格式。
6、但是有的人按照这一套操作下来,可能有的人会发现写入文件时lpBuffer 的数据不是图片格式,而是不知道是什么格式的一堆字符,这是因为如果用手机选择本地图片文件发送的时候,保存的是加密后的图片信息,向上追溯保存信息的来源,然后向上追溯向上两层堆栈处,此时ebp-14是加密后图片信息,在这个位置找到函数其实位置(或者向上找到ebp-14开始出现的地方)下断点。
7、最后找到这个位置(特征码:\x8B\x5D\xFC\x3B\xD7\x7D\x25\x2B\xDE\x8D\x0C\x16\x89\x5D\xFC),此时的ebp-4是加密前的图片数据,在此位置下断点
8、那么这里的函数入参只有图片信息,没有发送者信息,怎样找到发送者信息呢?经过寻找,发现esp+4中存储的内存结构中,有图片路径信息,接收文本消息处的消息里也有路径信息,可以作为关联
9、再在收文本信息的位置下断点,查看消息结构体信息,可以看到有两个路径信息,+18C是缩略图,+1A0是原图,而接收图片断点在收到图片时也会执行两次,一次是缩略图,一次是原图
三、代码实现
1、CWeBase基类
提供两个入口函数Hook、UnHook,方便hook调用
/**
* @brief hook对象基类
* 两个入口函数Hook、UnHook,方便hook调用
*/
class CWeBase
{
private:
//HOOK地址
unsigned int m_nAddress;
//代码
unsigned char m_sCode[10];
//代码长度
unsigned int m_nCodeLen;
//线程表
TV_ThreadTable m_ThreadTable;
public:
CWeBase();
~CWeBase();
private:
/**
* @brief 获取HOOK代码和长度
* @param nAddress hook的代码位置
* @param strCode 代码位置的反汇编的代码
* @param nCodeLen 代码位置的反汇编的代码所占内存长度
*/
bool GetHookCode(int nAddress, std::string& strCode, int& nCodeLen);
//获取线程列表
void GetAllThread();
//挂起所有线程
void SuspendAllThread();
//恢复所有线程
void ResumeAllThread();
//刷新CPU缓存
void UpdateCache(LPVOID lpBase, int nBaseSize);
protected:
unsigned int GetCallAddressByOffset(unsigned int nOffset, LPCSTR module = NULL);
bool GetObjectString(unsigned int nObject, unsigned int nOffset, LPSTR lpOutBuf, int nBufLen);
int GetObjectInt(unsigned int nObject, unsigned int nOffset);
public:
/**
* @brief 下钩子
* @param lpModuleName 模块名
* @param nOffset 偏移地址
* @param nCallBack 回调函数地址
* @param pJmpAddress 记录回调后跳转回来的地址
* @retval 1 成功
* @retval 0 失败
*/
bool Hook(LPCSTR lpModuleName, unsigned int nOffset, unsigned int nCallBack, unsigned int* pJmpAddress);
/**
* @brief UnHook
* 取消钩子
*/
void UnHook();
};
2、CReceive类,继承CWeBase基类
在接收消息的特定位置下钩子,并解析接收到的消息信息,包括发送者id和接收的内容(文本、视频、图片等)
/**
* @ brief hook接收消息
* 在接收消息的特定位置下钩子,并解析接收到的消息信息,包括发送者id和接收的内容(文本、视频、图片等)
*/
class CReceive :public CWeBase
{
public:
CReceive();
~CReceive();
/**
* @ brief 解析接收的信息
* @ param 接收的信息存放地址
*/
void CallBackManage(unsigned int nObject);
public:
//记录回调后转回地址
static unsigned int m_nJmpAddress;
static CReceive* m_sReveiv;
//回调地址
T_CallBack_RecvMsg m_pCallBack;
/**
* @brief 下钩子
* @retval 1 成功
* @retval 0 失败
*/
bool ToHook(T_CallBack_RecvMsg pCallBack);
};
3、CReceivePicture:接收图片hook
接收图片和路径信息,通过路径信息和发送者信息进行关联
/**
* @brief 接收图片hook
* 接收图片和路径信息,通过路径信息和发送者信息进行关联
*/
class CReceivePicture :public CWeBase
{
public:
CReceivePicture();
~CReceivePicture();
/**
* brief 接收图片hook回调
* param nBuffer 图片内容
* param nBufferSize 图片内容长度
* param nObject 图片路径所在结构
*/
void CallBackManage(unsigned int nBuffer, unsigned int nBufferSize,unsigned int nObject);
public:
//记录回调后转回地址
static unsigned int m_nJmpAddress;
//接收图片hook对象静态指针
static CReceivePicture* m_sReveivPicture;
//接收图片路径配置
static std::string m_sRevPicPath;
//回调地址
T_CallBack_RecvMsg m_pCallBack;
/**
* @brief 下钩子
* @retval 1 成功
* @retval 0 失败
*/
bool ToHook(T_CallBack_RecvMsg pCallBack);
};
4、完整代码源码地址:https://gitee.com/zhangruyi/we-chat-tool