Python模拟websocket登陆-拆包封包
解析一个网站
这里所用的网站是我一个内测的网站,主要手段是chrome devtools,用得很多,但我玩的不深,这次上了点干货.
- 首先在网络那一块,找到js或者index.html,右键点击,选择替换内容,在需要分析的地方写上自己的代码。
- 对于ajax加载的js模块,把js在加载地址,换成离线下载到本地的地址,存入本地的服务,原来是http的,还还用http。原来https的必须也在https,本地web开启跨域允许。然后ajax加载部分js也就替换成可定制的了。
- 虽然代码很难读,但是在适合的地方,可以随便写cosole.log.
- 对于js的调试和中断不太懂,还没用,
- 界面的记录器标签,可以记录一组点击,然后能看到json代码,用于回放。
- 记录的动作可以用js实现插入在任何位置,只要调试通过就行。
获取wss原始数据
获取数据的两种方式,各种优缺点:
- 在上一章中,可以看到websocket建立和sendBytes,onMessage之类的函数,在这里启动cosole.log就能得到逐条纪录。不论ws还是wss。这里可以看到语义,并可定制测试代码。
- 在devtools网络标签下的wss://server:port/url地址,对应的respone可以取得全部的来往数据,和代码块所收发的字节按道理是完全一致的,可以做为一个验证和参考。
拆包wss数据
所谓的拆包是理解数据的意义,我还没有修炼到靠数据读含义的深度,只能靠代码,也就js,顺眼化处理的代码,比如以下的登陆数据代码,从1万行里拽出来的。
- 消息头4表字节
c
u.prototype.addHeader = function(e, t, n, a) {
return void 0 === n && (n = 0),
void 0 === a && (a = 0),
e | t << 4 | n << 12 | a << 23
4个参数在长度 e->4bit, t ->,8bit,n->11bit a->9bit.
含义e=command ,t=action, n=length,a=ext
- 登陆消息的主体
js
var a = new z.SyncLogonDto;
a.account = e.account,
a.sn = e.sn,
a.token = e.token,
a.uid = e.userID,
a.localHost = 0,
4 == a.sn.length && 0 < a.uid && "" != a.token ? this.sendMsg(a.getBody(),
z.ServiceType.HALL_CMD, z.ServiceType.HALL_LOGIN_ACT, 2 =
o.prototype.getBody = function() {
var e = new t.BGByteArray;
return e.writeUnsignedInt(this.major),
e.writeUnsignedInt(this.minor),
e.writeUTFBytes(this.sn),
e.writeUnsignedInt(this.localHost),
e.writeLongUint(this.uid),
var a = new z.SyncLogonDto;
a.account = e.account,
a.sn = e.sn,
a.token = e.token,
a.uid = e.userID,
a.localHost = 0,
4 == a.sn.length && 0 < a.uid && "" != a.token ? this.sendMsg(a.getBody(),
z.ServiceType.HALL_CMD, z.ServiceType.HALL_LOGIN_ACT, 2 =
o.prototype.getBody = function() {
var e = new t.BGByteArray;
return e.writeUnsignedInt(this.major),
e.writeUnsignedInt(this.minor),
e.writeUTFBytes(this.sn),
e.writeUnsignedInt(this.localHost),
e.writeLongUint(this.uid),
e.writeFixedLenthString(this.account, 32),
e.writeFixedLenthString(this.token, 32),
e
}
,
a = o,
t.SyncLogonDto = a
e.writeFixedLenthString(this.token, 32),
e
}
,
a = o,
t.SyncLogonDto = a
主要的处理逻辑在·o.prototype.getBody = function()
这里的writeUnsigedInt是四字节无符号整数,而且是小头的。就是低位在前,高位在后
,相同还有,
writeFixedLenthString(this.account, 32),补充位,也是先写入数据,后填充'\x00',在python的bytes使用bytes.ljust(),后面有详细介绍。
这是消息的主体
封包wss数据
根据上面在js可以生成一些python代码用于数据组织
python
def addHeader(bodyl,command,action=0,ext=0):
# Data---\x3e command:" + t + " action:" + n + " size: 4+len(e),ext:a
num= command | action << 4 | bodyl << 12 | ext << 23
#num.to_bytes(length=32,byteorder='little')
return num.to_bytes(length=4,byteorder='little')
def parseHeader(hex4='11c08500'):
# little_byte = b'\x01\x00\x00\x00\x00\x00\x00\x00'
hex4=int.from_bytes(bytes.fromhex(hex4),byteorder='little')
command=hex4 &int('F',16)
action=hex4>>4 & int('FF',16)
l=hex4>>12 & int('7FF',16) #only 11bit
ext=hex4>>23
print (f'command:{command},action:{action},len:{l},ext:{ext}')
def loginbytes(sn,lh,uid,account,token):
re=bytes()
re+=int(b'01',16).to_bytes(4,byteorder='little')
re+=int(b'01',16).to_bytes(4,byteorder='little')
re+=sn.encode()
re+=int(lh).to_bytes(4,byteorder='little')
re+=int(uid).to_bytes(8,byteorder='little')
re+=account.encode().ljust(32,b'\x00')
re+=token.encode().ljust(32,b'\x00')
# print(len(re))
# print (re.hex())
return re
解释
addHeader使用按位或,在返回时byteorder='little')
这是比较原始数据得出的。
parseHeader用按位与,移位来排除前,与来排除后,只留对照有用的。
这两个函数处理4字节32位的头部信息。
loginbytes 是改写的js中的SyncLogonDto getBody其中account.encode().ljust(32,b'\x00')
是对account补足32,int(uid).to_bytes(8,byteorder='little')
是将uid转为长整数8字节长,低位在前高位在后的bytes。
发送接收websocket的常驻后台脚本
本段代码,pip install websocket-client, 版本号1.18,websocket协议13
python
import websocket
import threading
import time
import os
import login
#os.path+=['../']
# 定义当接收到消息时调用的回调函数
def on_message(ws, message):
print(f"Received message: {message}")
# 定义当连接关闭时调用的回调函数
def on_close(ws, close_status_code, close_msg):
print(f"### Closed ###")
# 定义当出现错误时调用的回调函数
def on_error(ws, error):
print(f"### Error ### {error}")
if __name__ == "__main__":
# websocket服务地址
ws_service_address = "ws://your_websocket_server"
ws_service_address = "wss://alidr-311.klwgt.com/data"
# 创建websocket应用实例
websocket.enableTrace(True)
ws = websocket.WebSocketApp(ws_service_address,
on_message=on_message,
on_close=on_close,
on_error=on_error)
# 创建一个线程用于运行websocket客户端
wst = threading.Thread(target=ws.run_forever)
wst.daemon = True
wst.start()
time.sleep(3)
mss=login.getlogin()
ws.send_bytes(mss)
# 主线程做其他事情...
# 例如,主线程可以发送消息到websocket服务器
# ws.send("Your message here")
# 主线程在此处等待,否则程序会立即退出
# 如果你的程序需要在后台运行,则不需要这一行
wst.join()
其实主要就是来自百度AI的一段代码。稍加调整假入了延时的登陆调用。然后就成功登陆了。实现和浏览器js登陆websocket的一样的效果。
总结
虽然这段代码,没有什么业务功能,细节也是基本的类型转换,但是它是从怀疑中不断产生的。因为我开始时怀疑python在websocket库是否能完全仿真js在websocket。在查看js的建立连接的请求头时,发现协议版本是2011年的13,然后查看了websocket-client在pypi,也是支持到这个版本,只是还没有实现gzip功能的扩展。
既然如此,wss的 建立也没要求cookie和其实token。那就用python跑一下。基于开始处对于网站数据的整理,要是没有原本的js脚本,这也是一个不可能完成的事情了。但是虽然登陆成了,以后的业务逻辑还不知道怎么处理。最少,分步骤,实现批处理,是可以的。这就由python建立 了 一个,js代码的客户端。
好吧纯属有病。
再见