协议分析TMP
最近闲来有事, 分析了一个非常低端(非常低端的意思是说你不应该对她是否能取代你现有的QQ客户端作任何可能的奢望,她只是一个实验性的东西)的手机QQ的协议, 是手机QQ3.0,
所用到的TCP/HTTP通信协议版本是1.4, 也不知道是哪一年release的了, 至少有七八年的历久了吧, 反正就是: 功能非常弱!
主要的分析原因是想学学网络方面的编程经验(这是我第2次弄socket编程 :-) ), 以及学学怎么抓包分析.
主要用到的工具软件
手机QQ3.0: http://www.ruan8.com/soft_5872.html
kEmulator(Java模拟器):http://gamevina.us/kemulator-vh/
Wireshark(协议分析):http://www.wireshark.org/download.html
Java Decompiler(Java查看(和谐)工具):http://jd.benow.ca/
分析流程简要介绍
1.获取服务器信息
网上分析这个版本手机QQ的用户不在少数, 我也了解到这个版本比较容易分析, 协议简单, 所以...
首先, 打开这个网址:http://conf.3g.qq.com/newConf/kjava/aubin2.jsp
(注意使用unicode编码查看, 否则可能乱码) 然后你就会看到类似下面的东西:
SERERCONFIG_NUM=5& SERVERCONFIG_TYPE=KQQTCP,KQQTCP,KQQHTTP,KQQHTTP,KQQHTTP& SERVERCONFIG_URL=socket://58.60.12.177:14000,socket: //211.136.236.88:14000,http://tqq.tencent.com:8000,http: //mconn.tencent.com:14000,http: //kconn.tencent.com:21001&UPDATAECONFIG_NUM=1& UPDATECONFIG_VERSION=2.4.2&UPDATECONFIG_MUST=N& UPDATECONFIG_URL=http://mq.3g.qq.com/g/s?aid=mqq& UPDATECONFIG_HELPTXT=请升级到2.4.3版QQ2005(Java 版)&SMSSERVICE_NUM=2&SMSSERVICE_NAME=24小时在线,关注好友& SMSSERVICE_ADDRESS=10661700,1066170056& SMSSERVICE_TEXT=HQ,SQKJ%2c||QQNO||&SMSSERVICE_HELPTXT=不用手机上网,通过短信就能登陆QQ,累积在线时间真是方便,让你QQ等级不停增长!%0a当前操作需要通过短信操作完成,选择取定将发送一条短信,请根据收到的短信提示完成操作!, 关注此QQ好友,好友一上QQ马上就会有短信通知你手机!随时随地和TA在QQ上“偶遇”,资费10元/月。%0a当前操作需要通过短信操作完成,选择确定将发送一条短信,请根据收到的短信提示完成操作。
其中:
SERERCONFIG_NUM表示目前可以使用的服务器的个数
SERVERCONFIG_TYPE表示服务器类型:KQQTCP表示TCP服务器,KQQHTTP表示HTTP服务器
SERVERCONFIG_URL:服务器地址
......(不重要)
注意:各值以逗号分隔; 键值对间使用&符号连接.
至于手机QQ要使用哪个服务器, 现在还不知道, 反正是其中一个, 过滤试一下就知道了.
2.过滤通信协议
先打开Wireshark进行网络封包过滤: 选择一张活动网卡, 然后start.
应该马上就可以看到, wireshark已经显示了很多数据包... 数据太多, 不便查看, 于是过滤显示一下,
以QQ的第一个tcp服务器为例: 在Wireshark的Filter Expression里面输入过滤表达式, 并点击Apply应用:
用kEmulator运行手机QQ并登录:
接下来, 如果QQ使用了第一个服务器的话, 那么Wireshark将会显示以下信息:
没错, 你没有看错, 协议可以说全部是明文的, 完全就像是HTTP那样的...
比如VER=1.4代表版本号为1.4, CMD=Login表示命令为登录, UIN=***表示QQ...............
3.协议分析
好了, QQ和服务器的所有通信都是基于这种HTTP的键值对方式, 下面列出一些常见的键值对:
VER=1.4 | 表示当前使用的协议版本, 不过服务器返回的版本可能有所不一致 |
CON=1 | 这个不知道是什么意思, 通过查看QQ的代码可见其貌似是固定值1 |
CMD=*** | 当前的命令, 有Login, Logout, Query_Stat2等等. |
SEQ=*** | 当前命令的序列号, 以此来标识不同的命令序号, 每下一次发送时, 在上一次的基础上加1 |
UIN=*** | 当前登录的QQ号, 不变 |
命令的规定:
每个命令以\r\n结束, 这就意味着命令字段中不能再出现其它的\r\n, 还有,要把\n换成%0D,\r换成%0D
每条命令都使用UTF-8编码, 同时命令中出现的逗号,&等要转换url编码, 不然要误解释命令.
1. 登录命令: VER=1.4&CON=1&CMD=Login&SEQ={seq}&UIN={qq}&PS={ps}&M5=1&LG=0&LC={lc}&GD={gd}&CKE=\r\n
服务器响应:VER=1.1&CMD=Login&SEQ={seq}&UIN=2933278370&RES=0&RS=0&HI=60&LI=300&SG=***&SSG=207390864\r\n
SEQ序号应该和你发送时一样, 不然就是错的.
RES=0代表服务器成功响应
RS=0代表登录成功,其它均为失败; 若失败:则有相应的RA字段标识错误信息, 比如Password Error!
如果有SID或者COMP字段, 记得保存起来, 因为后面的某些搞不懂的命令还要用它们. 其它的比如什么HI和LI就不知道是什么东西了.
如果出现了VC字段, 则说明需要验证码信息, 我测试了很久, 一直没遇到, 暂时不说这个...
2.取得所有QQ好友及其基本信息
命令:VER=1.4&CON=1&CMD=SimpleInfo2&SEQ={seq}&UIN={qq}&SID={sid}&XP={xp}&UN=0&TO=0\r\n
回复:VER=1.1&CMD=SIMPLEINFO2&SEQ={seq}&&UIN={qq}&RES=0&NP=65535&SN=0&UN=&FC=&NK=\r\n
XP是前面根据login命令计算出来的, 具体可以看我后面的代码.
RES=0同样代表服务器正确响应.
NP=65535表示当前返回了所有的好友信息, 如不是, 需要多次查询, 你应该没有那么多好友吧?
SN表示当前返回的好友基础信息的个数, (注:我全没有写出来)
UN表示好友QQ号, 以逗号分隔(我没列出)
FC表示头像ID
NK表示昵称, 以逗号分隔
3.取得好友状态
命令:VER=1.4&CON=1&CMD=Query_Stat2&SEQ={seq}&UIN={qq}&SID=&XP={xp}&CM=2&UN=0\r\n
回复:VER=1.1&CMD=QUERY_STAT2&SEQ={seq}&UIN={qq}&RES=0&FN=1&SN=2&ST=10,30&UN=..\r\n
FN是不是finish的意思? 不知道.
SN返回信息的个数
ST状态:10-在线,20-离线,30-离开,40-隐身
UN:QQ号, 与状态一一对应
4.发送消息给好友
命令:VER=1.4&CON=1&CMD=CLTMSG&SEQ={seq}&UIN={qq}&SID={sid]&XP={xp}&UN={qq2}&MG={mg}\r\n
回复:VER=1.1&CMD=CLTMSG&SEQ={seq}&UIN={qq}&RES=0
UN代表好友的QQ
MG表示消息, 必须是部分url编码的
5. .............
还有几条命令, 比如加好友, 删除好友, 我就不写了 , 加上有些也没做测试.
4.模拟登录
1. 可以先用GET方式获取到服务器IP信息
GET /newConf/kjava/aubin2.jsp HTTP/1.1
Accept: text/html, application/xhtml+xml,*/*
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/6.0)
Accept-Encoding: utf8
Host: conf.3g.qq.com
Connection: Keep-Alive
[blankline]
2. 然后建立socket并连接到服务器
3.向服务器发送命令
4.处理服务器返回
5...............
5.效果展示
3.完结
文章里面只是简要介绍了一些协议方面的东西, 细节处理, 以及完全的命令列表(我也不全知道)也没有完全列出来,若
需要参考, 还请查看下面的源代码.
参考文章:
手机QQ协议分析 (一)登陆
HTTP/1.1 Request - w3school
未完结的源代码: http://pan.baidu.com/s/1c0iYYDa
作者:女孩不哭
时间:2014年4月6日
联系:QQ-191035066
地址:http://www.cnblogs.com/memset
参考:
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": "" }