对控制PC端微信发送信息的研究

经过断断续续约一个半月的逆向分析与开发之后,终于成功实现了控制微信发送信息。

简介

近日闲来无事,试着实现控制WeChat PC发送信息,没想到断断续续竟搞了一个半月才搞定,踩了不少坑,也在其中学到很多。
本文主要讨论思路,具体代码略过。

先上最终效果图

分析过程

WeChatForPC有三个比较重要的文件:WeChat.exe、WeChatWin.dll、WeChatResource.dll。

  • WeChat.exe只执行一些简单的操作,如检查更新、校验WeChatWin.dll,主要逻辑代码都在WeChatWin.dll中。
  • WeChatWin.dll包含WeChatPC的绝大部分主题代码,一些重要的函数使用了VMP进行混淆(较低版本中混淆了更多代码)
  • WeChatResource.dll保存了WeChat的所有资源文件:图片、音效、XML等,并使用了某种未知的方式进行压缩

坑之一

最初为了定位发送信息的函数,我选择从网络事件入手。然而网络收发牵扯到了众多线程(顺便学习Winapi线程相关的函数),加上即使不主动发送信息,微信也会有一些网络IO事件,这使得定位相当困难。

坑之二

所以我决定从界面入手进行分析,定位发送按钮的单击事件处理函数,最终找到发送信息函数。所以便去学习了Winapi图形界面相关的函数及消息队列机制等,学成归来发现还是分析不动,微信界面并非使用Windows原生api实现,而是使用了一款C++用户界面库:DuiLib,还有一些比较知名的软件也用了这个库,如百度杀毒/卫士、酷我音乐盒、火绒等。于是又去复习了C++开发及逆向的相关内容并稍微学习了一下DuiLib(不得不说,DuiLib的官方文档真烂),感觉DuiLib还挺方便的。

1
2
3
4
5
6
7
8
9
void Notify(TNotifyUI& msg)
{
if( msg.sType == _T("windowinit") ) OnPrepare();
else if( msg.sType == _T("click") ) { // 事件类型判断
if( msg.pSender == m_pCloseBtn ) { // 事件主体判断
PostQuitMessage(0); // 事件处理部分
return;
}
...

DuiLib官方给出的Demo中处理按钮单击事件的方法基本都是在Notify函数中判断事件类型为click,然后进一步判断事件主体是否为某控件,最终调用响应函数处理。
其中事件类型直接用DuiString类型标注,类型判断直接比较字符串(效率略低,改成枚举类型多好)。事件主体用控件指针标注,判断标准是直接比较指针,指针可以用控件Name靠FindControl函数获得。

这样我们可以通过控件Name定位Notify函数,进而找到事件处理函数。
Name可以靠猜,也可以从Frame类的构造函数入手。DuiLib构造复杂窗口一般依靠XML生成,XML文件储存在WeChatResource.dll中且经过未知方式压缩,不过还好内部内容是靠资源路径检索,在WeChatWin.Dll中搜索”.xml”可以找到很多结果,选择其中一个下断点就可以顺藤摸瓜找到XML的解析过程。最后多麻烦几次就可以dump到微信绝大部分的XML,然后经过一番分析就可以找到发送按钮的Name,然后就可以找到发送按钮单击事件处理函数,理想真是美好啊。

坑之三

事实上,DuiLib提供了多条事件处理路线,Notify函数是最常用的一条,发送按钮单击事件处理函数并不在Notify中,事件主体控件的判断也没用控件Name。推测可能是使用了DuiLib提供的消息映射

该文章详细分析了DuiLib的事件处理流程 https://note.youdao.com/share/?id=66febb0420f97966d16ecbd7bc7aae3f#/

之后我想了另一条思路,发送按钮单击事件处理函数必定要调用输入控件的GetText获取发送信息内容,发送完成之后又必然调用Input的SetText之类的函数清空内容。我的新思路就是利用Input控件的GetText顺藤摸瓜找到目标.

这条思路比较成功,最终定位到了一片经过VMP混淆过的函数,这个函数有好几个参数,其中有接收者的WXID以及发送信息内容字符串这两个参数,直接修改这两个参数也达到了预期的效果。

目标找到了,开始写Hook代码吧,写完个小Demo才发现直接调用这个函数会导致微信崩溃,可能是有些变量没初始化吧。

最终思路

踩了这么多坑,总算快成功了,最后的思路是放弃直接调用底层实现,转而调用前端层的代码,模拟用户操作来实现控制微信发送信息。这个思路的实现分为三步:

  1. 设置接收者,对应点击联系人的这一操作
  2. 设置发送信息内容,对应输入信息内容的这一操作
  3. 调用按钮单击事件函数,对应单击按钮或按下回车这一操作

第一步无论是逆向分析还是编写Demo都太顺利了,一次成功,搞得我都忘了我是怎么做到的了…
第二步也很简单,单击事件处理过程中通过虚表调用过GetText,而在DuiLib源码中SetText就在GetText后面

那么SetText和GetText在虚表中也应该是相邻的,写个Demo验证一下发现果然如此。
现在看来,最后一步更简单了,事件处理函数我们早就找到了,直接调用就好。

最后成品编写过程中还有一个问题需要解决,三步函数调用都是thiscall,必须要知道类地址。写Demo时吧地址硬编码,编译为DLL,靠StrongOD注入就好,独立程序就不能硬编码地址了。

关于地址,也有两条思路,一条是利用DuiLib向Windows注册的UserData找到m_PainterManager(不知道这玩意是啥可以去看看DuiLibDemo,官方文档就算了吧),调用其成员函数FindControl寻找各控件,这条方向可以参考 https://bbs.pediy.com/thread-220798.htm (作者的水平比我不知道高到哪里去了)(刚看到作者又实现了一个Spy++ For DuiLib,想实现这条思路的可以去拜读一番);另一条思路实现调试器,在控件构造函数下断点,获取控件地址。
相比较,第二种方案比较直接,比较方便,所以我选择了这条方案。

最后完成的太过顺利,感觉意犹未尽就又完成了好友信息的获取、控制修改备注、记录收发信息的功能,具体思路大同小异,实现起来也非常简单,就不再详细说明。

实现调试器的时候也是踩了一堆坑,自己造的轮子总是bug满满,现成的轮子又找不到(后来在x64dbg项目中发现TitanEngine,然后又发现直接搜”Debugger Engine”就能找到巨硬家的轮子,而我当时用的关键词是”Debugger Frame”)

一个半月的工作还是挺开心的,学到不少东西,虽然怎么也找不到目标时各种烦躁,最终坚持下来看到自己的作品成功跑起来真是成就感满满。还有莫扎特第21钢琴协奏曲第一乐章真好听。

又闲下来了,下面干点啥呢