首页 > 代码库 > [微信协议分析] 文本消息

[微信协议分析] 文本消息

参考:

微信协议简单调研笔记

微信破解研究总结

Sync协议

  道听途说,加上上面参考中都是提到微信使用Sync协议。去年项目中做过参考Microsoft Exchange ActiveSync 协议来优化消息协议的方案,虽经过长时间讨论定稿,但由于一些原因最终没有实现;也从中深知Sync并不是表面上那么简单、那么好用。

  Sync 有啥问题呢?

  1. SyncKey 生成维护成本

    SyncKey 在ActiveSync中为字符串,客户端不需要解析,但服务端实现要用数字自增,需要强一致性,且不能回退。

  2. 消息的订阅模式 采用类似Zookeeper的One time triggler 还是每条消息都推送一条通知能

    One time trigger能够避免并发通知时,获取消息时重复问题,但增加了交互成本,和客户端实现复杂性。

  3. 自己发的消息,SyncKey怎么获取

    尤其要支持多端同步发消息,保证消息同步;也只好消息发完在给自己同步一遍(自己设备发的可以不带消息体)

  4. 消息推送延时加重

    Sync 消息体获取方式:Notify - Ack - get - Mssage, 也就是至少第四个应用包才能返回消息,在移动网络下成本很高。文中提到消息通过单独https请求,那么延时更为严重了(嗯,实测新版本并非如此)。

 

手机客户端不再Sync协议

抓包分析版本:Android 微信6.0, 抓包分析可参考:android 移动网络实时抓包

在wifi、gprs网络状况下都相同,客户端会依次尝试使用80、8080、443 端口连接服务器;消息发送、接收都使用长连接进行.

 

协议格式:

4byte Packet Len(包含4字节本身)

2byte Head Len(包含2字节本身) + 2byte Version(1) + 4byte Operation + 4byte SeqId + ….

(Packet Len - Head Len)  Body

 

协议交互方式:

- 客户端请求(一应一答,通过seqid匹配):

      seqid = 1 开始,依次递增,服务器回复相同的seqid 作为应答

- 服务器推送通知(单向):

     seqid = 0,Operation = 7a,  客户端不需要应答

 

主要业务:

-心跳包:

     发起客户端请求,Operation = 0c,长度为16字节,算是最小的包

-发消息:

     发起客户端请求,Operation = ed

  单点在线时发完消息后,应答携带SyncKey,不再同步,多点在线时,通过通知同步SyncKey,将随后文章分析。

-收消息:

     1. 服务器推送消息, Operation = 7a,  Body 中携带消息内容

    抓包分析时,通过改变消息体大小,可能到接收方第一个包length 也会随之变化,可确认消息是push的。

     2. 发起客户端单向请求,即消息的应答Ack

-加密:

     未分析,一般来说像whatsapp那样,使用 hash(密码/OTP + 长连接第一个请求获取RandomCode) 做RC4 的密钥

 总结:

现在版本的微信消息推送,并非Sync方式,而是推送+ack方式;

从他们协议设计来看,应该最开始用的是Notify + Sync Req + Sync Rsp 方式,因为协议上是不支持服务器发起请求的;

现在改成 Sync 消息+ 单向 Ack Req 的push方式,虽然协议上怪异, 相比Sync 消息接收会更加及时。从以前看到的文章都是说用的Sync协议,应该是是后期版本做了修改,push方式更为高效、而且通过顺序的SyncKey也能够修复丢失的消息。

下面一个通知包示例:

技术分享

4 byte: 265 PacketLen

2 byte: 16   Head Length

2 byte: 1     Protocol Version

4 byte: 7a   Operation

4 byte: 0   SeqId 这是一个通知性消息

4 byte: unknow header

 

……. Body

 

Web客户端

  web微信客户端使用比较标准的Sync协议,Sync协议也比较适合web长轮询模型。

  移动客户端模式下,协议是二进制的而且有加密,很难分析;微信侧重手机端,web端主体协议应该保持与移动端一致,可通过web端推测整体协议实现,json也比较好分析。

  接收一条消息后SyncKey变化: 

  synckey:1_624161340|2_624162225|3_624162051|11_624161867|201_1420112604|1000_1420104656
  synckey:1_624161340|2_624162226|3_624162051|11_624161867|201_1420112631|1000_1420104656

  可以看出:

  - Synckey 有多个,应该是应对不同业务,其中2为为所有个人消息、讨论组消息,其他可能是联系人、朋友圈、订阅号等,201 为当前时间戳。

  - SyncKey 的值为数字自增,不是从0开始,应该有个固定的初始值。  

  - 发消息时,发完需要自己给再自己同步回一下SyncKey。

- 下面是一条消息增量同步结构,一堆要同步字段+是否修改FlagMask,同步协议变得很简洁。

{"BaseResponse": {"Ret": 0,"ErrMsg": ""},"AddMsgCount": 1,"AddMsgList": [{"MsgId": 1625734358,"FromUserName": "@sssss","ToUserName": "@ssssssss2","MsgType": 1,"Content": "捶地笑……","Status": 3,"ImgStatus": 1,"CreateTime": 1420109883,"VoiceLength": 0,"PlayLength": 0,"FileName": "","FileSize": "","MediaId": "","Url": "","AppMsgType": 0,"StatusNotifyCode": 0,"StatusNotifyUserName": "","RecommendInfo": {"UserName": "","NickName": "","QQNum": 0,"Province": "","City": "","Content": "","Signature": "","Alias": "","Scene": 0,"VerifyFlag": 0,"AttrStatus": 0,"Sex": 0,"Ticket": "","OpCode": 0},"ForwardFlag": 0,"AppInfo": {"AppID": "","Type": 0},"HasProductId": 0,"Ticket": ""}],"ModContactCount": 0,"ModContactList": [],"DelContactCount": 0,"DelContactList": [],"ModChatRoomMemberCount": 0,"ModChatRoomMemberList": [],"Profile": {"BitFlag": 0,"UserName": {"Buff": ""},"NickName": {"Buff": ""},"BindUin": 0,"BindEmail": {"Buff": ""},"BindMobile": {"Buff": ""},"Status": 0,"Sex": 0,"PersonalCard": 0,"Alias": "","HeadImgUpdateFlag": 0,"HeadImgUrl": "","Signature": ""},"ContinueFlag": 0,"SyncKey": {"Count": 6,"List": [{"Key": 1,"Val": 624161340},{"Key": 2,"Val": 624162166},{"Key": 3,"Val": 624162051},{"Key": 11,"Val": 624161867},{"Key": 201,"Val": 1420109883},{"Key": 1000,"Val": 1420104656}]},"SKey": ""}

 

  

[微信协议分析] 文本消息