PC微信逆向:发送与接收消息的分析与代码实现

2019 年 8 月 20 日 黑客技术与网络安全

来自公众号:信安之路

本文作者:鬼手56

我们先来定位一下消息接收函数,这对我们后面分析消息发送函数会有所帮助

定位消息接收函数的相关思路

与接收消息函数最直接相关的东西肯定是消息本身,所以消息本身的内容就是我们的切入点。我们可以首先找到存放消息内容的地址,然后对地址下断,通过栈回溯最终定位到接收消息的函数

定位消息内容的地址

首先用另外一个微信给自己发一条消息,在不点开消息的状态下用 CE 搜索消息内容

然后再发送一条消息

此时有效结果只剩下 3 个,把这三个地址加入到下方地址栏,右键->更改记录->类型

将显示范围调大

其中有一个是最原始的未经处理的消息,也是显示的最全的那一条,剩下的两条是经过处理的。我们需要中间那个未经任何处理的消息

定位接收消息函数的地址

既然消息内容的地址找到了,那么接下来就通过这个内容来找到接收消息的函数

在 OD 中找到这个地址,下内存写入断点。为什么是写入不是访问?因为这个是最原始的的消息,要想对这条消息进行处理就必须改写当前的这条消息,所以在这个位置下内存写入断点,当客户端对这条原始消息进行处理时,断点就会断下。

此时,再次发送一条消息,程序断下,删除内存写入断点,这个时候堆栈的返回地址里面一定有一个函数是用来接收消息的。我们点击 K 查看调用堆栈。

经过排查确认是这个 call,大家可以根据我图中的函数特征直接找到这个 call。我们在这个函数下断点,让程序再次断下,分析附近的代码。

分析接收消息函数

好友消息

此时我们点击查看堆栈中 esp 寄存器的值,数据窗口跟随

此时 [esp+0x40] 的位置是发送者的微信 ID,[esp+0x68] 的位置是消息内容(通过这个 call 我们还可以拿到文件助手的 ID 是 filehelper,这对后面分析消息发送会有用,大家可以去试验一下)

[esp+0x114] 的位置是 0,[esp+0x128] 的位置是一串未知数据。

群消息

然后我们再发送一条群消息,看看有什么区别

此时 [esp+0x40] 的位置是群 ID,[esp+0x68] 的位置是消息内容

[esp+0x114] 的地址不再是零,而是消息发送者的 ID,[esp+0x128] 的位置依旧是一串未知数据。大家可以用同样的方式分析处图片和表情在内存中的表现形式。

那么我们只要记录下这个 call 的地址+偏移,然后写一个 dll 注入到微信进程空间中,HOOK 这个函数,就能拦截所有的消息,并显示到我们的程序中。

总结

总结一下思路,寻找切入点->找地址->下断->栈回溯分析。就是这么简单粗暴

代码实现

注入之后接收消息的代码如下:

void RecieveMsg()
{
  wstring receivedMessage = L"";
  BOOL isFriendMsg = FALSE;
  //[[esp]]
  //信息块位置
  DWORD** msgAddress = (DWORD * *)r_esp;

  //消息类型[[esp]]+0x30
  //[01文字] [03图片] [31转账XML信息] [22语音消息] [02B视频信息]
  //感谢:.順唭_自嘫ɑ、unravel提供类型消息。
  DWORD msgType = *((DWORD*)(**msgAddress + 0x30));
  receivedMessage.append(L"消息类型:");
  switch (msgType)
  {
  case 0x01:
    receivedMessage.append(L"文字 ");
    break;
  case 0x03:
    receivedMessage.append(L"图片 ");
    break;

  case 0x22:
    receivedMessage.append(L"语音 ");
    break;
  case 0x25:
    receivedMessage.append(L"好友确认 ");
    break;
  case 0x28:
    receivedMessage.append(L"POSSIBLEFRIEND_MSG ");
    break;
  case 0x2A:
    receivedMessage.append(L"名片 ");
    break;
  case 0x2B:
    receivedMessage.append(L"视频 ");
    break;
  case 0x2F:
    //石头剪刀布
    receivedMessage.append(L"表情 ");
    break;
  case 0x30:
    receivedMessage.append(L"位置 ");
    break;
  case 0x31:
    //共享实时位置
    //文件
    //转账
    //链接
    receivedMessage.append(L"共享实时位置、文件、转账、链接 ");
    break;
  case 0x32:
    receivedMessage.append(L"VOIPMSG ");
    break;
  case 0x33:
    receivedMessage.append(L"微信初始化 ");
    break;
  case 0x34:
    receivedMessage.append(L"VOIPNOTIFY ");
    break;
  case 0x35:
    receivedMessage.append(L"VOIPINVITE ");
    break;
  case 0x3E:
    receivedMessage.append(L"小视频 ");
    break;
  case 0x270F:
    receivedMessage.append(L"SYSNOTICE ");
    break;
  case 0x2710:
    //系统消息
    //红包
    receivedMessage.append(L"红包、系统消息 ");
    break;
  case 0x2712:
    receivedMessage.append(L"撤回消息 ");
    break;
  default:
    break;
  }
  receivedMessage.append(L"");

  //dc [[[esp]] + 0x114]
  //判断是群消息还是好友消息
  //相关信息
  wstring msgSource2 = L"<msgsource />";
  wstring msgSource = L"";
  msgSource.append(GetMsgByAddress(**msgAddress + 0x168));

  if (msgSource.length() <= msgSource2.length())
  {
    receivedMessage.append(L"收到好友消息:");
    isFriendMsg = TRUE;
  }
  else
  {
    receivedMessage.append(L"收到群消息:");
    isFriendMsg = FALSE;
  }

  //好友消息
  if (isFriendMsg == TRUE)
  {
    receivedMessage.append(L"好友wxid:")
      .append(GetMsgByAddress(**msgAddress + 0x40))
      .append(L"");
  }
  else
  {
    receivedMessage.append(L"群号:")
      .append(GetMsgByAddress(**msgAddress + 0x40))
      .append(L"");

    receivedMessage.append(L"消息发送者:")
      .append(GetMsgByAddress(**msgAddress + 0x114))
      .append(L"");

    receivedMessage.append(L"相关信息:");
    receivedMessage += msgSource;
    receivedMessage.append(L"");
  }

  receivedMessage.append(L"消息内容:")
    .append(GetMsgByAddress(**msgAddress + 0x68))
    .append(L"");


  //文本框输出信息
  USES_CONVERSION;
  SetWindowText(GetDlgItem(hWinDlg, IDC_MSG), W2A(receivedMessage.c_str()));
}

定位微信的消息发送函数

定位消息发送函数的相关思路

首先思考一下消息发送函数背后的编程逻辑,一个发消息的函数,至少需要三个参数。第一个是发送给谁,第二个是发送的内容,第三个是消息的类型。所以我们可以从参数入手,然后通过栈回溯的方式找到发送消息的 call。

至于突破口我们可以从发送的消息内容和消息的接收者的微信 ID 入手,比如文件传输助手的微信 ID 是 filehelper,这个可以在接收消息的 call 中拿到。以这个微信 ID 为突破口会比从文本来追溯方便。

在拿到接收者的微信 ID 之后,对这个地址下内存访问断点,然后通过栈回溯的方式就能找到发送消息的 call

过滤当前聊天窗口的微信 ID

首先将当前聊天窗口设置为文件传输助手,搜索 filehelper

除了文件传输助手,我们还知道个人的微信ID都是以wxid_开头的,所以将窗口切换到微信好友,搜索wxid_

接着我们选中所有地址,加入到下方地址栏

然后选择全部地址右键->更改记录->类型

将长度修改为 50 以显示更多的内容

此时你会看到微信好友的 ID,记录下这个 ID,待会有用

再将窗口切回文件助手,下方地址栏的 ID 会发生变化,将数值不是 filehelper 的全部剔除掉。剩下的地址中的某一个是当前窗口的微信 ID,它会随着你当前微信窗口 ID 进行变换。

定位当前聊天窗口的 ID

这个当前聊天窗口的 ID 到底有什么作用呢?我们来测试一下

选中所有地址,右键->更改记录->数值,将当前聊天窗口的 ID 改为 filehelper,然后在当前好友的聊天窗口发送一条消息,你会发现此时消息发到了文件传输助手

当前聊天窗口的 ID 是谁 谁就会接收到这条消息,利用这个特性我们来找出那个唯一的当前窗口 ID

选中一半地址,将其更改为 filehelper,然后在当前窗口发送消息。如果消息发给了 filehelper,那么选中的地址里面就有真正的当前聊天窗口的 ID。重复这个步骤,可以找到真正的当前窗口 ID

定位发送消息的函数

接着载入 OD,在找到的当前窗口 ID 的地址中下一个内存访问断点。为什么是内存访问断点而不是内存写入呢?因为当前微信窗口的 ID 肯定会被发送消息的当作参数传入到堆栈中,所以必定会访问这个 ID,而不是写入 ID。

给好友发送一条消息,点击发送,内存访问断点断下。

此时 eax 指向当前窗口 ID,接着删除内存访问断点。点击 K 查看调用堆栈,在堆栈的返回地址中逐个排查每一个函数,这个函数必须有两个以上的参数,其中一个参数是消息内容,另外一个参数是消息 ID

经过排查,可以在调用堆栈的第二层找到一个疑似发消息的 call。在这个地方下断点,让程序断下,分析附近代码

分析发送消息的函数

普通消息

此时 edx 指向微信 ID,[edx+4] 保存的是微信 ID 的长度

ebx 指向消息内容,[ebx+4] 保存的是消息内容的长度。那么这个很有可能就是我们要找的发送消息的 call。

找到了发送消息的函数,那么怎么验证呢?利用微信 ID。将 edx 指向的微信 ID 的地址和我们之前在 CE 中找到的当前窗口的微信 ID 对比,你会发现两个地址是一样的。

改变这个地址的微信 ID 和内容,就能直接改变消息的接收者和内容,这个刚才我们已经实验过了。再结合这个函数传入的参数有当前消息的内容,就可以确定这个 call 就是微信发送消息的函数。

艾特某人消息

除了以普通文本的方式发送消息以外,还可以以艾特某人的方式发送消息。那么当发送的消息是艾特某人的时候,这个函数和发送普通文本消息有什么区别呢?区别就在于 eax 寄存器的值

先发送一条普通消息程序断下

此时 eax 的值为 0,然后再发送一条艾特某人的消息

此时 eax 是有值的,数据窗口跟随,看看这个 14704C40 的地址保存的是什么内容

里面的被艾特的人的微信 ID,普通消息与艾特消息的区别就在于 eax 是否保存了被艾特人的微信 ID。大家可以用同样的方式分析处图片和表情在内存中的表现形式。

接下来我们只要记录下当前发送消息函数的地址+偏移,就能写一个 dll 注入到微信进程空间中,直接调用发送消息的函数,就能实现用自己写的程序给任何人发送消息。

总结

总结一下思路,寻找切入点->找地址->下断->栈回溯分析。跟接收消息的步骤是一致的。找call的关键在于你能不能找到一个好的切入点,并且利用切入点与call之间的关系。

代码实现

调用发送消息的函数代码如下:

void SendTextMessage(wchar_t* wxid, wchar_t* msg)
{
  //拿到发送消息的call的地址
  DWORD dwSendCallAddr = GetWeChatWinAddr() + 0x2EB4E0;

  //微信ID/群ID
  wxMsg id = {0};
  id.pMsg = wxid;
  id.msgLen = wcslen(wxid);
  id.buffLen = wcslen(wxid)*2;

  //消息内容
  wxMsg text = { 0 };
  text.pMsg = msg;
  text.msgLen = wcslen(msg);
  text.buffLen = wcslen(msg)*2;


  //取出微信ID和消息的地址
  char* pWxid = (char*)&id.pMsg;
  char* pWxmsg = (char*)&text.pMsg;


  char buff[0x81C] = { 0 };
  //调用微信发送消息call
  __asm {
    mov edx, pWxid;
    push 1;
    mov eax, 0;
    push eax;
    mov ebx, pWxmsg;
    push ebx;
    lea ecx, buff;
    call dwSendCallAddr;
    add esp, 0xC;
  }
}

最终效果

发送消息

接收消息

成品就暂时不发了,下回补上,有个 bug 调了好久没调出来,拜拜


●编号928,输入编号直达本文

●输入m获取文章目录

推荐↓↓↓

Linux学习

更多推荐25个技术类公众微信

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。

登录查看更多
1

相关内容

【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
194+阅读 · 2020年6月29日
【MIT-ICML2020】图神经网络的泛化与表示的局限
专知会员服务
42+阅读 · 2020年6月23日
商业数据分析,39页ppt
专知会员服务
160+阅读 · 2020年6月2日
ACL2020接受论文列表公布,571篇长文208篇短文
专知会员服务
66+阅读 · 2020年5月19日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
161+阅读 · 2020年5月14日
Keras作者François Chollet推荐的开源图像搜索引擎项目Sis
专知会员服务
29+阅读 · 2019年10月17日
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
GitHub 热门:别再用 print 输出来调试代码了
Python开发者
27+阅读 · 2019年4月24日
已删除
架构文摘
3+阅读 · 2019年4月17日
基于R语言进行Box-Cox变换
R语言中文社区
45+阅读 · 2018年11月19日
比Selenium快100倍的方法爬东方财富网财务报表
程序人生
8+阅读 · 2018年10月31日
爬了自己的微信,原来好友都是这样的!
七月在线实验室
4+阅读 · 2018年1月18日
别@微信团队了,我用Python给自己戴上了圣诞帽!
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
Mesh R-CNN
Arxiv
4+阅读 · 2019年6月6日
Arxiv
6+阅读 · 2019年4月8日
Arxiv
10+阅读 · 2017年11月22日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
194+阅读 · 2020年6月29日
【MIT-ICML2020】图神经网络的泛化与表示的局限
专知会员服务
42+阅读 · 2020年6月23日
商业数据分析,39页ppt
专知会员服务
160+阅读 · 2020年6月2日
ACL2020接受论文列表公布,571篇长文208篇短文
专知会员服务
66+阅读 · 2020年5月19日
干净的数据:数据清洗入门与实践,204页pdf
专知会员服务
161+阅读 · 2020年5月14日
Keras作者François Chollet推荐的开源图像搜索引擎项目Sis
专知会员服务
29+阅读 · 2019年10月17日
相关资讯
PC微信逆向:两种姿势教你解密数据库文件
黑客技术与网络安全
16+阅读 · 2019年8月30日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
GitHub 热门:别再用 print 输出来调试代码了
Python开发者
27+阅读 · 2019年4月24日
已删除
架构文摘
3+阅读 · 2019年4月17日
基于R语言进行Box-Cox变换
R语言中文社区
45+阅读 · 2018年11月19日
比Selenium快100倍的方法爬东方财富网财务报表
程序人生
8+阅读 · 2018年10月31日
爬了自己的微信,原来好友都是这样的!
七月在线实验室
4+阅读 · 2018年1月18日
别@微信团队了,我用Python给自己戴上了圣诞帽!
iOS高级调试&逆向技术
CocoaChina
3+阅读 · 2017年7月30日
Top
微信扫码咨询专知VIP会员