平台网址:www.zhanqi.tv
一、登录账号
验证接口 https://www.zhanqi.tv/api/auth/user.login
提交方式POST
参数说明:
使用的验证是极验验证,参数还是很直观的。
提交成功后返回
{
"code": 0,
"message": "登录æˆåŠŸ",
"data": {
"uid": xxxxxx,
"nickname": "xxxxx",
"avatar": "https://img2.zhanqi.tv/avatar/00/000/0_0000000000.jpg",
"status": 0,
"gender": 1,
"uright": 0,
"email": "",
"qq": "",
"account": "xxxxx",
"exp": {
"nums": 0,
"level": 1,
"levelNeed": 15,
"levelNow": 0
},
"istrust": 1,
"letter": 0,
"badNickname": false,
"loginIp": "xxxxxxx",
"isAnchor": false,
"anchorUrl": "",
"roomId": 0,
"fans": 0,
"bindMobile": "1xxxx31xxx",
"bindEmail": "",
"crc32": 2173657370,
"token": "hubc1uqv3sas3gj736qnvhhsa3.715E1448-7151-444F-2903-A99D26EA7551",
"slevel": {
"name": "",
"pos": "0",
"keep": "",
"nextname": "é’é“œ5",
"level": "0",
"nextexp": "15000",
"leftexp": "15000",
"levelexp": "0",
"curexp": "0",
"uid": "xxxxx9",
"opp": 0
},
"permission": 0,
"rich": {
"gold": 0,
"coin": 0
},
"role": {
"level": {
"level": 1,
"current": 0,
"total": 80,
"percent": 0
},
"skill": [
{
"id": 1,
"name": "旗娘çªè¢",
"description": "解é”表情[å–èŒ]",
"icon": "https://img1.zhanqi.tv/uploads/siteIcon/icon_skill_1.png",
"type": 0,
"level": 1,
"unlockLevel": 5,
"effectId": 1,
"effect1": 1,
"effect2": 0,
"effect3": 0,
"effect4": 0,
"next": 5,
"nextDescription": "解é”表情[å–èŒ]",
"typeStr": "被动技能"
},
{
"id": 16,
"name": "弹幕乱舞",
"description": "å‘é€å¼¹å¹•çš„å—数上é™+1",
"icon": "https://img1.zhanqi.tv/uploads/siteIcon/icon_skill_2.png",
"type": 0,
"level": 1,
"unlockLevel": 8,
"effectId": 2,
"effect1": 1,
"effect2": 0,
"effect3": 0,
"effect4": 0,
"next": 8,
"nextDescription": "å‘é€å¼¹å¹•çš„å—数上é™+1",
"typeStr": "被动技能"
},
{
"id": 31,
"name": "能é‡è½¬åŒ–",
"description": "æ¯æ—¥å‰2次é€ç¤¼èŽ·å¾—粉ä¸ç»éªŒé¢å¤–å¢žåŠ 2点",
"icon": "https://img1.zhanqi.tv/uploads/siteIcon/icon_skill_3.png",
"type": 0,
"level": 1,
"unlockLevel": 10,
"effectId": 3,
"effect1": 2,
"effect2": 2,
"effect3": 0,
"effect4": 0,
"next": 10,
"nextDescription": "æ¯æ—¥å‰2次é€ç¤¼èŽ·å¾—粉ä¸ç»éªŒé¢å¤–å¢žåŠ 2点",
"typeStr": "被动技能"
}
]
}
}
}
先获取房间信息
接口:
https://www.zhanqi.tv/api/public/room.viewer?uid=房主Uid&_t=时间戳
房主Uid直接可以在页面源码里获取
接口返回:
{
"code": 0,
"message": "",
"data": {
"uid": xxxxx,
"gid": 1827291574,
"sid": "YzM2YjdiM2JkYWJhZDZmNmEwYmIzN2JiNjU4MDQ0ZDQ=",
"timestamp": 1494308710,
"prop": {
"speaker": 0,
"ticket": 0
},
"isFollow": false,
"roomdata": {
"vlevel": 0,
"vdesc": "",
"slevel": {
"name": "",
"pos": "0",
"keep": "",
"nextname": "é’é“œ5",
"level": "0",
"nextexp": "15000",
"leftexp": "15000",
"levelexp": "0",
"curexp": "0",
"uid": "xxxxx",
"opp": "0"
}
},
"clientIp": xxxxx
}
}
战旗的弹幕服务器是直接通过Flash连接的,通过查看页面源码可以看到Flash的参数
红框内的数据是标准的BASE64加密的,解密后如下:
{"list":[{"ip":"121.43.196.77","port":15010,"id":41,"chatroom_id":41},{"ip":"112.124.38.229","port":15010,"id":64,"chatroom_id":64},{"ip":"120.26.16.38","port":15010,"id":87,"chatroom_id":87}]}
通过截取封包工具可以获得通讯数据
封包是明文的,发现有个loginreq的登录包:
{
"timestamp": 1494309269,
"hideslv": 0,
"tagid": 0,
"thirdaccount": "",
"roomid": 29402,
"cmdid": "loginreq",
"fhost": "",
"gid": 1827291574,
"roomdata": {
"vlevel": 0,
"slevel": {
"opp": "0",
"name": "",
"nextname": "5",
"keep": "",
"curexp": "0",
"nextexp": "15000",
"uid": "xxxxxx",
"level": "0",
"leftexp": "15000",
"pos": "0",
"levelexp": "0"
},
"vdesc": ""
},
"sid": "NjYzNDNkMjM2NDMzNTI1NDVjYzRkOTU4NzZjYjgwZjk=",
"ajp": -2,
"t": 0,
"imei": "553149823",
"uid": xxxxxxx,
"ver": 12,
"ssid": "ZWI3YzViNmU5YjNkYjcwOWExODI0N2Q3YzY1OGUxMDA=",
"fx": 0
}
粗略看了下几个参数还是很明显的,这里特别的分析下SSID的获取方式。
三、SSID的获取
通过调试页面的JS代码找到源头:最终走到:
这里可以看出来是JS与Flash的交互,这个函数直接转进Flash了,通过分析FLash文件,发现做过加密和混淆处理,代码已经很难辨认
看来Flash这条路走不通了,然后转战APP,直接下载了安卓版本的APP,用工具解开APK,直接DEX转jar ,jd-gui跑起
通过搜索loginreq关键字,可以找到这里:
代码还是很明显,可以知道是 uid + gid + getVerStr() + timestamp 拼接成的字符串,然后计算个MD5,最后BASE64加密
然后这个的getVerStr()是什么鬼!? 点进去看看申明:
public static native String getVerStr();
居然是 native ,在java中这个类型的申明表示是接口函数,后面我们在找下这个函数的原型
四、getVerStr()原型获取
分析思路:
1、找到.So文件
2、找到getverstr的导出
3、分析函数执行
通过筛选文件,最终找到了libddmd.so,使用ida静态分析下
直接点开看原型
看起来是直接返回一个字符串, “www.zhanqi.tvWY|qZJYcX(K4zj^%” 。
最后按照算法直接组装一个登陆包即可完成房间的登录,其他的协议均可以在java内找到,基本无难度了。
PS. 1、登录包内的 sid 和 timestamp 必须和查询房间信息返回的数据一致,否则登录会不通过