查看原文
其他

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

2018-03-02 Cirn 看雪学院

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



简介


近日闲来无事,试着实现控制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还挺方便的。


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后面(upload/attach/201712/777010_vi27ia77mx9m4w9.png)


那么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钢琴协奏曲第一乐章真好听。






本文由看雪翻译小组 Cirn 原创

转载请注明来自看雪社区


热门阅读


点击阅读原文/read,

更多干货等着你~

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存