平臺網址: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 必須和查詢房間信息返回的數據一致,否則登錄會不通過