首页
论坛
课程
招聘
[原创] 对控制PC端微信发送信息的研究
2017-12-7 21:51 21636

[原创] 对控制PC端微信发送信息的研究

2017-12-7 21:51
21636
 

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

简介

近日闲来无事,试着实现控制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钢琴协奏曲第一乐章真好听。

 

原文链接:
https://cirn09.github.io/2017/12/06/WeChatControl/

 

看雪ID居然不能用数字,服了服了


技术人员的狂欢 | 看雪2020第四届安全开发者峰会顺利落幕!!

收藏
点赞0
打赏
分享
最新回复 (41)
雪    币: 21
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
KevinLong 活跃值 2017-12-7 22:02
2
0
沙发!学习学习!
雪    币: 15
活跃值: 活跃值 (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhhigh 活跃值 2017-12-7 23:09
3
0
学习
雪    币: 9029
活跃值: 活跃值 (1380)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
hzqst 活跃值 3 2017-12-8 09:56
4
0
其实这样和直接模拟按键点击也差不了多少了。。
雪    币: 90
活跃值: 活跃值 (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
unsummon 活跃值 2017-12-12 16:55
5
0
学习一下!
雪    币: 7
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
greyyang 活跃值 2017-12-13 14:27
6
0
hzqst 其实这样和直接模拟按键点击也差不了多少了。。
差别还是有的,模拟按键必须将界面置于前台,楼主这种方法,可以实现后台操作,后面继续发展可以控制多个微信,做出类似web微信机器人的东西出来
雪    币: 302
活跃值: 活跃值 (27)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
小夜的夜 活跃值 2017-12-14 19:32
7
0
直接调用函数崩溃是因为消息机制的问题,不是变量初始化的问题。
雪    币: 33
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
双峰山 活跃值 2017-12-15 10:58
8
0
ui事件始终要传达至功能函数,直接从按键往下找就行了。
雪    币: 5
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tongzeyu 活跃值 2018-1-2 12:21
9
0
不是在消息映射里面,发送按钮事件处理在eventsource  里面,在notify  之前
雪    币: 5
活跃值: 活跃值 (13)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tongzeyu 活跃值 2018-1-2 12:24
10
1
输入框用的com封装的,非一般edit  封装,所以普通的edit虚表是不对的
雪    币: 3
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_凹沙利猫 活跃值 2018-1-11 11:25
11
0
能分享发送信息的源码吗大神
雪    币: 1199
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
DeverDebug 活跃值 2018-2-9 20:34
12
0
学习一下!~
雪    币: 15
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
doscho 活跃值 2018-2-24 15:30
13
0
如何获取好友的微信号?谢谢!
雪    币: 15
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
doscho 活跃值 2018-2-24 16:46
14
0
楼主,您好!用什么方法或什么工具可以获取到底层发送函数?望赐教,谢谢!
雪    币: 253
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Cirn 活跃值 2018-2-27 14:14
15
0
doscho 如何获取好友的微信号?谢谢!
我记得我当时是断点断在好友信息frame设置前端文字的位置,然后顺着字符串向上找
雪    币: 15
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
doscho 活跃值 2018-2-27 17:27
16
0
Cirn 我记得我当时是断点断在好友信息frame设置前端文字的位置,然后顺着字符串向上找
你是用od进行跟踪的?
雪    币: 253
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Cirn 活跃值 2018-2-28 18:53
17
0
doscho 你是用od进行跟踪的?
对,和IDA配合使用疗效更佳
雪    币: 1
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vgavin 活跃值 2018-3-1 08:33
18
0
如果这样,为啥不用图像识别+模拟点击?很简单就搞定啊
雪    币: 1271
活跃值: 活跃值 (279)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
开花的水管 活跃值 2018-3-1 10:34
19
0
学习了!感谢楼主分享!
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
走心i 活跃值 2018-3-2 23:02
20
0
Windows不是有一套消息处理机制的吗,每一步操作都可以看成是一个消息,然后用spy++看每一步所发出的windows  message,通过向微信模拟发送message来实现输入选择点击等操作,这样应该也可以实现楼主想要的效果的吧。
雪    币: 253
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Cirn 活跃值 2018-3-7 16:53
21
0
走心i Windows不是有一套消息处理机制的吗,每一步操作都可以看成是一个消息,然后用spy++看每一步所发出的windows message,通过向微信模拟发送message来实现输入选择点击等操作,这样 ...
我刚开始的时候也考虑过这种方式,但是这样模拟操作很方便,获取信息就麻烦了
雪    币: 253
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Cirn 活跃值 2018-3-7 16:55
22
0
vgavin 如果这样,为啥不用图像识别+模拟点击?很简单就搞定啊
因为这样实现太low。。。而且无法起到练习逆向技能的作用
雪    币: 122
活跃值: 活跃值 (177)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
大魔头 活跃值 2018-3-9 14:27
23
0
我也用这种方式弄到了,联系人的wxid可以在contact_list里得到,具体断点下在itemclick就好了,我有个问题,如果session_list里面没有这个聊天,我的想法是给contact_btn发个点击,然后选择联系人,然后发送itemclick,最后点击发消息那个,但是好像不太行,一直没能把itemclick模拟出来,不知道你sessionlist是如何模拟的itemclick,想讨教下
雪    币: 36
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
巫云雪遥 活跃值 2018-3-9 15:07
24
0
大魔头 我也用这种方式弄到了,联系人的wxid可以在contact_list里得到,具体断点下在itemclick就好了,我有个问题,如果session_list里面没有这个聊天,我的想法是给contact_ ...
调用Search的那个call比较方便
雪    币: 122
活跃值: 活跃值 (177)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
大魔头 活跃值 2018-3-9 15:13
25
0
巫云雪遥 调用Search的那个call比较方便
OJBK  多谢了  我试试看 
游客
登录 | 注册 方可回帖
返回