本次投稿至华盟网,转自公众号黑白之道
出处:https://www.77169.net/html/272981.html
0x00 适用场景
本文所采用技术,仅用来实现自定义功能,适用场景仅为自己两台电脑使用聊天软件互发消息学习研究使用。
*本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。
0x01 需求产生
某聊天软件有已读回执功能,可以通过查看标记,判断对方是否读了你的消息:

经常出现这样一种情况,联系人发来几条消息,啊T.T,我好想看看他说了什么:

然后点开看了一下,完蛋,又是借钱的:

这个辩解苍白无力~

那么伟大的需求就此诞生:如何偷偷看他消息,而不让他知道我已读了?
0x02 探究原理
盲猜原理一定是这样:
钉钉客户端查看了消息->客户端发送已读回执到服务器->服务器向消息发送者发送已读状态
那也就是说,只要我们使用的PC客户端不发送已读回执,服务器就永远不知道我们读了该条消息,对放也就不会知道我们看过了。
一开始想看有没有人造过轮子,于是github上翻了翻,但没什么收获。不过在翻的过程中发现了一个有意思的项目:dingtalk-interceptor,但该项目删除了,于是就去网上翻了翻,果然找到了相关的文章,针对的是老版的客户端(3.x),该版本就是Electron包装的一层网页,项目实现原理是通过拦截所有包含updateToRead的请求,从而实现永远未读消息的。
![]()
因此我们要寻找的突破口就在这个关键字上:updateToRead
0x03 环境准备
调试环境:
win10 2004 x64

x96dbg

ida7.0

顺便推荐个工具,可以让ida和od/xdbg/lldb等同步调试:
https://github.com/bootleg/ret-sync
聊天软件版本:
![]()
0x04 开始动手
首先用x64dbg启动主程序,主程序目录在./main/current/路径下

通过不断尝试,发现该聊天软件的核心逻辑都在MainFrame.dll中,因此只针对MainFrame.dll进行搜索,关键字:updateToRead。
先定位到模块中:


然后在该模块中搜索updateToRead,并全部设置断点
发送两条消息并阅读测试,命中断点如下:

关闭其他断点,并用另一个号给调试的账号发送消息,并查看:
在第一个断点处,消息依然是未读状态
在第二个断点处,消息依然是未读状态
在第三个断点处,消息依然也是未读状态
在断到第四个断点处,消息依然是未读状态,但放过断点,消息就变成了已读:


解决方案有几个:
-
破坏原有数据包,使其服务器无法正确更新状态
-
在发送前拦截该数据包,使其不发送
-
不进入检测消息是否未读状态
此处通过调用该接口“/r/IDLMessageStatus/updateToReadV2”向该接口发送请求,来告诉服务器,消息已经被读了。此处可以直接暴力将接口修改,修改成错误的地址,也可以实现看了消息不显示已读。但直接通过该种方式修改,有两个弊端,
-
会向服务器发送一条数据,虽然服务器没能正确处理,但会返回一个异常。
-
客户端收到该错误响应后会认为自己与服务器断开连接,会经常认为自己掉线了从而发不出去消息
路线1走不通,因此继续寻找突破点,使用ida反编译该模块,查找该接口:


发现该接口是出自sub_1127B210函数:

进入sub_1127B210内容:

往上跟踪,发现sub_110BA580调用了sub_1127B210

往上跟踪,发现sub_107269E0调用了sub_110BA580
sub_107269E0函数内容:

其中包含多个接口,作用是向服务器发送一个包含消息id的请求包,从而实现消息已读。
此时已经基本了解大概情况,继续用x64dbg调试,在调试过程中发现在执行完sub_107269E0消息并未直接变成已读,而是继续向上层函数继续返回,直到返回了这样一个函数:

伪代码:
char __thiscall sub_10C77E70(_BYTE *this)
{
char *i; // esi
char result; // al
char v3; // [esp+8h] [ebp-10h]
char v4; // [esp+10h] [ebp-8h]
for ( i = this; ; sub_10C77EC0(i) )
{
result = i[20];
if ( result )
break;
sub_10C77060(i, (int)&v3);
if ( v3 )
sub_10C785D0(i, &v4);
else
sub_10C77210(i);
}
return result;
}
该函数sub_10C77E70在if ( v3 )处存在两个分支,一个分支调用sub_10C785D0(i, &v4),另一个分支即已读分支sub_10C77210(i),也就是我们的call所调用的函数。因此,v3是一个关键判断点
v3所对应汇编代码为:jz short loc_10C77EAC
偏移为:C77298

在C77298上下断点,通过修改寄存器zf标志位,使其进入sub_10C785D0(i, &v4)


修改后发现,后续发送的消息均不显示已读,且不会再进入该判断,因此只要让v3永远=True,即可永不进入sub_10C77210(i)分支,即修改该处条件跳转为jmp


修改后Patch到dll上即可。
特征码:C0 75 33 8D 45 F0 8B CE 50 E8 CE F1 FF FF 80 7D F0 00 8B CE
修改后:C0 EB 33 8D 45 F0 8B CE 50 E8 CE F1 FF FF 80 7D F0 00 8B CE
0x05 已读不回

0x07 免责申明
本文是作者闲暇之余,方便自己搞的一个小功能。如果对其他人/公司造成不好的影响请联系我们处理。

韩国 1F
这个思路挺有意思的,不过改了dll不怕被检测吗?
菲律宾 2F
直接改跳转确实暴力,但就怕后续更新失效
云南省 B1
@ 时光信笺 直接跳转改jmp虽然快,但热更新怕是要跪
湖南省娄底市冷水江市 3F
之前改过其他软件的类似功能,折腾半天最后还是放弃了
黑龙江省大庆市 4F
要是比特币跌回3万他们还能撑住不?
吉林省松原市 5F
搞逆向的都喜欢从updateToRead下手啊🤔
福建省福州市 6F
感觉对普通用户来说没啥必要,但技术研究可以
江苏省无锡市 7F
有没有更简单的hook方案?直接patch太硬核了
河南省新乡市 8F
这方法在mac上能用吗?
越南 9F
看起来是钉钉?版本号也不说清楚
山东省济南市 10F
改完会不会影响其他功能,比如消息同步?
江苏省南京市 B1
@ 亡灵祭司 改完之后客户端会不会弹异常警告啊?
湖北省武汉市 11F
这方法有点硬核了,不过挺有意思👍
江苏省常州市 12F
dll都敢直接patch,大佬操作
辽宁省辽阳市 13F
我之前也试过hook消息回执,结果一更新全废了
台湾省 14F
话说这种patch在自启时会不会被杀软报毒?
日本 15F
mac上估计得用frida搞,x64dbg玩不转
印度 16F
updateToRead这个关键字确实经典,一搜一个准
河南省洛阳市 17F
这要是不小心手滑了咋办
北京市 18F
要是公司网络监控软件也这么干就坏了
广东省 19F
看懂了一半,反正我是不敢随便改dll
浙江省衢州市 20F
技术细节有点硬核,但需求很真实哈哈
陕西省宝鸡市 21F
有没有可能通过本地代理拦截请求实现?那样更安全些
重庆市沙坪坝区 22F
前几天逆向微信就这么搞的,结果登录直接失败
安徽省合肥市 23F
所以这招对最新版还管用吗?
辽宁省沈阳市 24F
那个红色箭头指的“???”太真实了,一看就是没读
江苏省无锡市 25F
这功能说白了就是心理战,看了不回更气人
日本 26F
技术文章看得有点头大,不过需求挺实在的
宁夏银川市 B1
@ 玄武门 这需求很接地气吧,技术细节就留给我们折腾了
中国 27F
求问32位系统还能不能这么玩?