首页
论坛
专栏
课程

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

2017-12-7 21:51 18221

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

2017-12-7 21:51
18221
 

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

简介

近日闲来无事,试着实现控制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居然不能用数字,服了服了



[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (41)
KevinLong 2017-12-7 22:02
2
0
沙发!学习学习!
zhhigh 2017-12-7 23:09
3
0
学习
hzqst 3 2017-12-8 09:56
4
0
其实这样和直接模拟按键点击也差不了多少了。。
unsummon 2017-12-12 16:55
5
0
学习一下!
greyyang 2017-12-13 14:27
6
0
hzqst 其实这样和直接模拟按键点击也差不了多少了。。
差别还是有的,模拟按键必须将界面置于前台,楼主这种方法,可以实现后台操作,后面继续发展可以控制多个微信,做出类似web微信机器人的东西出来
小夜的夜 2017-12-14 19:32
7
0
直接调用函数崩溃是因为消息机制的问题,不是变量初始化的问题。
双峰山 2017-12-15 10:58
8
0
ui事件始终要传达至功能函数,直接从按键往下找就行了。
tongzeyu 2018-1-2 12:21
9
0
不是在消息映射里面,发送按钮事件处理在eventsource  里面,在notify  之前
tongzeyu 2018-1-2 12:24
10
1
输入框用的com封装的,非一般edit  封装,所以普通的edit虚表是不对的
wx_凹沙利猫 2018-1-11 11:25
11
0
能分享发送信息的源码吗大神
DeverDebug 2018-2-9 20:34
12
0
学习一下!~
doscho 2018-2-24 15:30
13
0
如何获取好友的微信号?谢谢!
doscho 2018-2-24 16:46
14
0
楼主,您好!用什么方法或什么工具可以获取到底层发送函数?望赐教,谢谢!
Cirn 2018-2-27 14:14
15
0
doscho 如何获取好友的微信号?谢谢!
我记得我当时是断点断在好友信息frame设置前端文字的位置,然后顺着字符串向上找
doscho 2018-2-27 17:27
16
0
Cirn 我记得我当时是断点断在好友信息frame设置前端文字的位置,然后顺着字符串向上找
你是用od进行跟踪的?
Cirn 2018-2-28 18:53
17
0
doscho 你是用od进行跟踪的?
对,和IDA配合使用疗效更佳
vgavin 2018-3-1 08:33
18
0
如果这样,为啥不用图像识别+模拟点击?很简单就搞定啊
开花的水管 2018-3-1 10:34
19
0
学习了!感谢楼主分享!
走心i 2018-3-2 23:02
20
0
Windows不是有一套消息处理机制的吗,每一步操作都可以看成是一个消息,然后用spy++看每一步所发出的windows  message,通过向微信模拟发送message来实现输入选择点击等操作,这样应该也可以实现楼主想要的效果的吧。
Cirn 2018-3-7 16:53
21
0
走心i Windows不是有一套消息处理机制的吗,每一步操作都可以看成是一个消息,然后用spy++看每一步所发出的windows message,通过向微信模拟发送message来实现输入选择点击等操作,这样 ...
我刚开始的时候也考虑过这种方式,但是这样模拟操作很方便,获取信息就麻烦了
Cirn 2018-3-7 16:55
22
0
vgavin 如果这样,为啥不用图像识别+模拟点击?很简单就搞定啊
因为这样实现太low。。。而且无法起到练习逆向技能的作用
大魔头 2018-3-9 14:27
23
0
我也用这种方式弄到了,联系人的wxid可以在contact_list里得到,具体断点下在itemclick就好了,我有个问题,如果session_list里面没有这个聊天,我的想法是给contact_btn发个点击,然后选择联系人,然后发送itemclick,最后点击发消息那个,但是好像不太行,一直没能把itemclick模拟出来,不知道你sessionlist是如何模拟的itemclick,想讨教下
巫云雪遥 2018-3-9 15:07
24
0
大魔头 我也用这种方式弄到了,联系人的wxid可以在contact_list里得到,具体断点下在itemclick就好了,我有个问题,如果session_list里面没有这个聊天,我的想法是给contact_ ...
调用Search的那个call比较方便
大魔头 2018-3-9 15:13
25
0
巫云雪遥 调用Search的那个call比较方便
OJBK  多谢了  我试试看 
无泪城 2018-3-9 15:40
26
0
建议搞手机微信,抓到微信的sessionkey后,可以直接脱离微信,通过socket直接跟微信服务器通讯。
巫云雪遥 2018-3-10 03:39
27
0
大魔头 OJBK 多谢了 我试试看
其实弄出来了也不咋样,  有概率和微信自己新消息到达时session_list置顶发生冲突,  然后GG,  最好还是剥皮往里找调用函数
高军 2018-3-10 09:45
28
0
学习l  !!!!!!
wx_大魔头 2018-3-10 10:44
29
0
巫云雪遥 其实弄出来了也不咋样, 有概率和微信自己新消息到达时session_list置顶发生冲突, 然后GG, 最好还是剥皮往里找调用函数
是的,我也觉得得找到底层调用,最好是封包发送,这种模拟还是问题很多
巫云雪遥 2018-3-10 13:10
30
0
wx_大魔头 是的,我也觉得得找到底层调用,最好是封包发送,这种模拟还是问题很多
做底层call还比较容易,做协议难度就呵呵了
samplezuo 2018-3-15 17:03
31
0
有能做这个能力的小伙伴请联系我,114637690@qq.com,加QQ也可以
Cirn 2018-3-22 13:45
32
0
部分函数在return前会调用HeapFree释放函数参数指针指向的空间,如果没有完美模拟的话错误信息会被微信捕获引发崩溃
Cirn 2018-6-18 16:30
33
0
意外操作获得新ID:Cirn09
此账号不再使用
武装的蔷薇 2018-6-22 12:12
34
0
楼楼  想请问一下  需要先进入聊天界面吗
mb_fsczqstd 2019-5-29 12:20
35
0
有QQ群吗?一起交流下
逃避不能对付一切 2019-6-11 17:43
36
0
请问楼主,是最后一步,是如何调用的,调用的时机是什么?谢谢解答
逃避不能对付一切 2019-6-14 13:18
37
0
我想到一个简单的办法,
逃避不能对付一切 2019-6-14 13:21
38
0
逃避不能对付一切 我想到一个简单的办法,
设置接收者,对应点击联系人的这一操作
设置发送信息内容,对应输入信息内容的这一操作
调用按钮单击事件函数,对应单击按钮或按下回车这一操作
楼主的前两步方式相同,第三步,获取发送按钮控件句柄,直接在dll写一个sendmessage (控件句柄,245,0,0)  发送一个假的点击按钮的消息 也可以完美登录, 
各位大佬有什么见解欢迎批评!!
dplxin 2019-6-14 14:39
39
0
看你思考了这么久    就分享一个成果给你
//2.6.7.57
UINT32 caddr_sendmsg = 0x2E4C40;
void SendText(PWXString wxid, PWXString msg)
{
       /*
6BB49BA8 | 8B55 CC                  | mov edx, dword ptr ss:[ebp-0x34]        | [ebp-34]:&L"filehelper"
6BB49BAB | 8D43 14                  | lea eax, dword ptr ds:[ebx+0x14]        |
6BB49BAE | 6A 01                    | push 0x1                                |
6BB49BB0 | 50                       | push eax                                |
6BB49BB1 | 53                       | push ebx                                | ebx:&L"我去222"
6BB49BB2 | 8D8D E4F7FFFF            | lea ecx, dword ptr ss:[ebp-0x81C]       |
6BB49BB8 | E8 83B02100              | call <wechatwin.发文本消息>                  | 发送文本消息
6BB49BBD | 83C4 0C                  | add esp, 0xC                            |
        */
       char temp[0x1000] = { 0 };
       __asm
       {
       
               mov edx, wxid
                
               push 0x1
               push 0
               push msg
               lea ecx, dword ptr ss : [ebp - 0x81C]
               call caddr_sendmsg
               add esp, 0xC
       }
}
dplxin 2019-6-14 14:41
40
0
void SendImage(PWXString wxid, PWXString path)
{
       /*
0FEE9CB4 | 53                       | push ebx                                | ebx:&L"C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\1559269116(1).jpg"
0FEE9CB5 | FF75 CC                  | push dword ptr ss:[ebp-0x34]            | [ebp-34]:&L"filehelper"
0FEE9CB8 | 8D85 A4FBFFFF            | lea eax, dword ptr ss:[ebp-0x45C]       |
0FEE9CBE | 50                       | push eax                                |
0FEE9CBF | 51                       | push ecx                                | 0写死也没事
0FEE9CC0 | E8 7B60FBFF              | call wechatwin.FE9FD40                  |
0FEE9CC5 | 83C4 04                  | add esp, 0x4                            |
0FEE9CC8 | 8BC8                     | mov ecx, eax                            |
0FEE9CCA | E8 61A92100              | call wechatwin.10104630                 |
        */
       char temp[0x1000] = { 0 };
       __asm
       {
               push path
               push wxid
               lea eax, dword ptr ss : [ebp - 0x45C]
               push eax
               push 0
               call caddr_sendimg1
               add esp, 0x4
               mov ecx, eax
               call caddr_sendimg2
               
               }
}
liu年 2019-10-28 23:16
41
0
老铁 有没有发链接的hook 找的头都大了
zylyy 2019-10-29 00:12
42
0
游客
登录 | 注册 方可回帖
返回