一、写在前面
1.为什么做免考?
相较于考试,免考更能锻炼自身创新和探索能力,更有挑战性。
2.选做微信推送防撤回原因?
- 微信已经成为日常沟通中必不可少的工具,如何知道别人在你没看微信的情况下偷偷说了什么就变得很重要(嘿嘿嘿)
- CTF我只研究过杂项、密码学和逆向,别的部分我也不擅长所以就不做了。
二、写在中间
1.什么是防撤回?
- 指对方发出撤回请求,系统应答后,对方微信显示成功撤回,而我方微信显示对方撤回提示但消息依然显示在界面。
2.实验环境
- 实验对象:wechatwin.dll (PC 2.6.7.57版本)
- 实验工具:
- IDA 6.6(7.0也行,好像6.6已经下不到了,反正版本不影响)
- windbg(Windows应用商店里有)
- VC2017(12、15版不知道行不行)
3.撤回原理
- 撤回逻辑:
-
- 对方在一个终端上发起撤回请求,服务端收到请求,下发命令给我方终端
- 我方终端收到了服务端发来的撤回消息命令
- 我方终端执行撤回消息指令
- 撤回实现:
- 我方终端依旧响应服务器发来的撤回消息指令
- 我方终端执行撤回消息提醒,但不撤回消息内容
4.撤回代码逆向分析
(1)将微信核心模块放入IDA,搜索一下撤回有关的命令
(2)分析一哈,挺多的,但基本上逻辑顺序与二、3的一致,那就动态调试找一下哪一条是服务器下的命令,很简单就能发现是 On RevokeMsg svrId : %d ,那就好办了 F5 一下
(3)再分析一哈,v191就应该是服务器下的命令,对应编号应该为10002(10000+2),然后查看一下这段程序调用的函数汇编,首先是 sub_10483FE0 函数
- 先看看如何调用
1 #对sub_10483FE0的汇编调用 2 sub esp, 14h 3 mov ecx, esp; 4 push 0 5 push dword ptr [eax+4] 6 call sub_10483FE0
- 再看看函数都调用了什么
1 #sub_10483FE0函数的汇编 2 push ebp 3 mov ebp, esp 4 sub esp, 8 5 push esi 6 mov esi, ecx 7 mov ecx, [ebp+arg_0] 8 push edi 9 mov dword ptr [esi], 0 10 mov dword ptr [esi+4], 0 11 mov dword ptr [esi+8], 0 12 mov dword ptr [esi+0Ch], 0 13 mov dword ptr [esi+10h], 0
- 先分析 sub_10483FE0 函数,左面就是汇编语言各种操作命令,右边是操作对象。从第9行开始就出现了dword ,这里是对注册表键值的处理吗?再看看后面的4和8,应该就是对字符串编码进行操作。然后搜索了一下(问百度),在Windows开发人员中心里发现了MultiByteToWideChar 功能,具体介绍如下:
- 搞了半天,原来 sub_10483FE0 这个类功能就是对宽字符进行操作,类似于C++里的wstring操作,应该是考虑到消息的编码方式应该是Unicode编码,再结合对 sub_10483FE0 函数的调用汇编代码,可以看出 ecx 完成的是 wstring 操作, dword ptr [eax+4] 完成的是java new String(XXX, UTF-8) 操作
- 虽然知道了函数是干啥的,但也不知道具体实现过程,所以使用动态调试,将断点设在call sub_10483FE0上, 查看此时的ecx的值
1 0:000:x86> dd 00efcb00-14 2 00efcb0c 13f0e7f0 000000d6 00000100 00000000
- 继续运行,当调用结束后查看ecx 之后都干了啥
1 0:000:x86> du 13f0e7f0 2 13f0e7f0 ""revokemsg"> "" 3 13f0e830 "msg> wxid_xxxxxxxxxxxxx " 4 13f0e870 "18778893426" 5 13f0e8b0 "id> 5379365542789842691 " 6 13f0e8f0 "" 7 13f0e930 ""不知名网友" 撤回了一条消息]]> " 8 13f0e970 ">
(4)到这里就显而易见了,出现了
- 挺好的,要是搜索的话也是sub_10257570 是唯一一个处理replacemsg的函数,仔细看看发现sub_10257570 被sub_10252F80 调用
- 然后一步一步往下找,真的好麻烦(过程就省略一点),真的追吐血了(有时间的话你们可以追追看)
- 最后发现sub_10253250 函数,是整个处理replacemsg部分的实现函数
- 感觉离事实的真相更进一步了,查看了一下 sub_10253250 函数内部逻辑,发现有一处判断语句
- 分别给v69赋值0和1,发现当为1时,有提示语并且消息没了,但是为0时,提示语也有,但是消息还在,听到这里开不开心?
(5)如何让消息不撤回到这里已经很清楚了,只需要在这里给他按个补丁,一直让他在else里走就好,但这里会有一个问题出现,你的微信上别人无法撤回消息,你的消息你也撤回不了,岂不是有点尴尬,那就要找一找微信是怎么区分自己发的撤回指令和其他人发的指令的函数。
- 到这里我就有点蒙了,sub_10253250 后面有好多,根本就不知道哪里是,直到看到了10000,是个熟悉的数字(10002为消息撤回参数)
1 if ( v50 == 10000 ) 2 goto LABEL_46;
- 也就是说在这之前就完成了消息撤回的步骤,再往上找,发现sub_1025A390 函数,在其附近的代码段找到两句调用函数
1 sub_1004E320((int)&v46); 2 ... 3 ... 4 ... 5 v11 = sub_1025A390 ( xmm0_0, v10, a3, a4, (int)&v46);
- 前一句是初始化v46(后来才知道这个变量是结构体变量),后一句是对v46的具体操作,那就要动态调试一下,看看两种撤回情况对v46变量的影响
1 0:013> g 2 *** WARNING: Stack unwind information not available. 3 #别人撤回时 4 012fc2e0 00000001 00000000 00000002 4cdf683f 5 #自己撤回时 6 012fc2e0 00000001 00000001 00000002 4cdf689f
- 显而易见,微信通过这两个数据差异来判断撤回的主体是谁,但也引出一个问题,当你自己撤回和别人撤回时会出现两种提示
- 那么v46这个变量一定关联着两个函数或者一条判断语句,那就继续看sub_10253250 函数的后半部分,发现
- 再去观察 sub_102541D0 函数,可以看出sub_102541D0 -> sub_102542A0的调用关系
- 再去看 sub_102542A0 函数,发现当变量为1时,就可以触发重新编辑功能
(6)到这里所有撤回流程就很清楚了,当自己撤回时,就按照正常指令进行操作,当他人撤销时,将消息撤回置位else,不让他进入if(true)就可以实现防撤回的功能了。
三、写在后面
1.防撤回实现
(1)生成汇编语言代码,作为补丁,放入wechatwin.dll所在的文件夹重启微信
(2)实现:
(3)彩蛋:撤回消息提醒是不是变了,原理全在 sub_102542A0 函数,可以自己试试。。。。嘿嘿嘿
2.感受:
逆向是真的麻烦,搞了半天补丁制作(借助了部分大佬的代码),才弄出来,结果写博客的时候微信升级了,虽然看了一下逻辑基本没变,但是地址都变了,f**k,不想再做一遍补丁了,虽然这个功能挺好用的,但是需要电脑客户端一直挂着微信就很麻烦,应该改进一下把撤回的消息都发给文件助手就会方便很多(网上有个大佬做过以前版本的),做到一半的时候,都想换题做个什么CTF杂项专题来着,幸好还是一点点坚持下来了,努力也没算白费(版本一换就不行o(╥﹏╥)o),终于不用写博客了(*^▽^*)。