解析一个网站

这里所用的网站是我一个内测的网站,主要手段是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表字节
    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

  • 登陆消息的主体
           
        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代码用于数据组织

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

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代码的客户端。
好吧纯属有病。
再见

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部