websocket逆向案例

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

一、websocket逆向

  1. 相关的知识可以看看k哥爬虫 上的文章,我就是从这里看的,【JS 逆向百例】WebSocket 协议爬虫,智慧树扫码登录案例分析
  2. 因为websocket通过实时交互获取信息,所以需要关注的地方是寻找发送接收函数,可以通过hook来定位页面中发送和接收的位置,然后观察交互规律即可
  3. hook代码,主要是 sendonmessage , 但是 onmessage 是一个接收返回数据时触发的事件,页面会绑定它指定一个回调函数来获取事件数据,所以只能 hook send ,hook 到后可以根据 websocket对象 找到 onmessage
javascript 复制代码
 // 保存原始的WebSocket.prototype.send方法
    let sendCache = WebSocket.prototype.send;

    // 重写WebSocket.prototype.send方法
    WebSocket.prototype.send = function (data) {
        console.info("Hook WebSocket send => ", data);
        debugger;
        return sendCache.apply(this, arguments);
    };

二、案例地址

aHR0cHM6Ly9kYi5zeGF1LmVkdS5jbi90emdnLmh0bQ==

三、分析流程

  1. 查找标题,打开f12 控制台,但是进入了debugger ,追栈发现是通过构造函数执行debugger,但是去除debugger后查找不到标题。

    javascript 复制代码
    0?1:function(){return !0}["constructor"]("debugger")["call"]["action"]
    javascript 复制代码
    // 	hook构造函数去掉debugger
    (function () {
        let constructorCache = Function.prototype.constructor;
        Function.prototype.constructor = function (string) {
            if (string === "debugger") {
                // console.log("Hook constructor debugger!");
                return function () {};
            }
            return constructorCache(string);
        };
    })();
  2. 查看浏览器发送的请求,有一个响应内容加密的session链接,但是不太像,通过翻页发现有一个 wss 链接,保持会话进行传输数据,但是传输数据的内容是二进制,翻页的话,这里也会有变化,所以定位这里应该就是加密的位置

  3. 分析wss请求,有三个参数会一直发生变化

  4. 这三个参数来自 session 请求中的 set-cookie ,所以需要先解决 session 请求,session请求中需要扣 1. headers 参数中的 etag 2. 请求参数中的 data

四、逆向参数

  1. 追栈,在 e.sessionData 那点进去,打上断点,然后调试就到了data生成的位置

  2. 可以发现是调用了 E 函数, 观察 E 函数,它是包含了许多key, value,; key是函数名, value是函数体,s.default里进行的定义;

  3. 追栈发现调用 E 的上一栈来自 n(755) , 通过加载器的方式抠出来,发现会卡住,不知道什么原因,所以E 拿出来单独扣

  4. E是一个大的加密函数, 抠出来后 补充下 s.default , 缺啥补啥, 补完后调用发现执行是空,页面上完成这一步去调用也是空,所以应该是还需要经历一些步骤


  5. 全部抠出来后进行调试(生成data那里的一堆加密函数返回的值就是 getKey),发现报错了

  6. 因为 p() 函数里的数组进行了移位,所以把移位的函数找到即可,并且这个操作要放在 E 函数执行的上方

  7. 然后又补充了几个变量的值

  8. 报错 u 找不到, 找到位置,发现是个加载器进来的函数,这个通过扣webpack的方式扣就可以

  9. 从加载器开始的那个函数那打断点,因为好多东西是一开始加载进去的

  10. 然后进到加载器后,在hook加载器, 然后再 u 结束的位置打上断点

  11. 拿出来后 替换下 ' == ' , \ = \ , 放在加载器中就可以调用了var i = n(548); var u = i(n(235));

  12. 再根据报错补充几个变量值就可以成功调用了

    javascript 复制代码
    E['getKey']()
    window.etag = E['genrandomString']()
  13. etag 参数扣取, etag就是上面的 genrandomString 生成的, 就是 E 函数中的 iv值

  14. tabId 生成,加密的参数中还有个 tabId, 这个直接搜索就可以搜索到, 是每次随机生成的, 并且这个 tabId 也是 wss链接中的参数,也是 wss 向服务器发送信息中需要携带的参数

五、webSocket 交互位置

  1. wss链接组成参数

  2. 服务器客户端信息交互的位置,通过文章开头的 hook函数可以定位到 send 的位置,定位到两个位置,一个是定期发送信息保持交互的 send , 一个就是要获取相关信息时发送时的 send , 需要关注第二个

  3. 然后打开网页,查看日志,发现接收的数据中能找到标题,但是找不到链接

  4. 经过研究发现,需要点击 标题后,客户端会发送 一系列的信息,然后才能获取链接 ,然后会跳转到对应的链接 页面

  5. tabid 前面已经可以拿到了, 标题id,在加密生成wss的时候已经将 当前页面的 链接传入了, 在绑定wss链接与服务器交互的时候,会接收到服务器发送的信息,这个时候标题以及对应的标题id都会拿到

  6. 然后将上面的发送信息里面的 tabid标题id 替换,发送就可以拿到链接了,最后发现只需要发送 mousedownclick 两个事件就可以。

  7. 代码

python 复制代码
import time
import _thread
import requests
import websocket
import random
import subprocess
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import json

class WebSocketClinet():
    def __init__(self):
        self.count = 0
        self.tabId = ""
        self.web_socket_url = ""

        # 读取解密js代码
        with open("test4.js", encoding="utf8") as f:
            self.jsCode = f.read()

    def get_wss_first(self):
        url = 'https://**'
        js_resul = execjs.compile(self.jsCode).call('js_sdk', "encrypt", url)
        headers = {
            "Accept": "application/json, text/javascript, */*; q=0.01",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "zh-CN,zh;q=0.9",
            "Connection": "keep-alive",
            "Content-Type": "application/json; charset=UTF-8",
            "etag": js_resul['etag'],
            "Fetch-Mode": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
            "Host": "",
            "Origin": "",
            "Referer": "",
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
        }
        url = "https://**/api/v1/sessions"
        data = {
            "data": js_resul['data']
        }
        data = json.dumps(data, separators=(',', ':'))
        response = requests.post(url, headers=headers, data=data, verify=False)
        cookie_dict = response.cookies.get_dict()
        p1 = cookie_dict['FW9****']
        p2 = cookie_dict['dGg2****']
        self.web_socket_url = "wss://***/***/pr/{}/b/ws/{}/{}".format(p2, js_resul['tabId'], p1)
        self.tabId = js_resul['tabId']


    def wss_on_message(self, ws, message):
        print("=============== [message] ===============")
        js_resul = execjs.compile(self.jsCode).call('js_sdk', "decode", list(message))['data']
        print("recv message: ", js_resul)
        print("recv count: ", js_resul[0][0])
        if js_resul[0][0] != -1:
            self.count = js_resul[0][0]


    def wss_on_error(self, ws, error):
        print("=============== [error] ===============")
        print(error)
        ws.close()


    def wss_on_close(self, ws, close_status_code, close_msg):
        print("=============== [closed] ===============")
        print(close_status_code)
        print(close_msg)


    def wss_on_open(self, ws):
        msg_list= [
            [
                426,
                [
                    self.tabId,
                    "1",
                    4,
                    "1",
                    "498",
                    "mousedown",
                    29,
                    29,
                    1,
                    0,
                    1,
                    0,
                    "",
                    701,
                    177,
                    0,
                    0,
                    29,
                    29,
                    704,
                    1265,
                    704,
                    65,
                    True
                ]
            ],
            [
                431,
                [
                    self.tabId,
                    "1",
                    4,
                    "1",
                    "594",
                    "click",
                    29,
                    29,
                    1,
                    0,
                    0,
                    0,
                    "",
                    701,
                    177,
                    0,
                    0,
                    29,
                    29,
                    704,
                    1265,
                    704,
                    65,
                    True
                ]
            ]
        ]
        def run(*args):
            count_client = 1
            # for arg in msg_list:
            time.sleep(3)
            for arg in msg_list:
                if arg[0] == -1:
                    print('count', self.count)
                    arg[1][3] = self.count
                else:
                    arg[0] = count_client
                    count_client += 1
                data_resu = execjs.compile(self.jsCode).call('js_sdk', 'data', arg)['data']
                bytes_object = bytes(data_resu['data'])
                print('发送', arg)
                ws.send(bytes_object, opcode=websocket.ABNF.OPCODE_BINARY)
                if arg[0] == -1:
                    time.sleep(3)
                else:
                    time.sleep(random.random())
        _thread.start_new_thread(run, (msg_list,))


    def wss(self):
        # websocket.enableTrace(True)  # 是否显示连接详细信息
        ws = websocket.WebSocketApp(
            self.web_socket_url, on_open=self.wss_on_open,
            on_message=self.wss_on_message, on_error=self.wss_on_error,
            on_close=self.wss_on_close
        )
        print('start')
        ws.run_forever()



if __name__ == '__main__':
    W = WebSocketClinet()
    # 第一次获取 cookie,包含 INGRESSCOOKIE、JSESSIONID、SERVERID、acw_tc
    W.get_wss_first()
    W.wss()

总结

相关推荐
徐子颐3 小时前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭3 小时前
如何理解HTML语义化
前端·html
jump6803 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信3 小时前
我们需要了解的Web Workers
前端
brzhang3 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu4 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花4 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋4 小时前
场景模拟:基础路由配置
前端
六月的可乐4 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程
Q_Q5110082854 小时前
python+django/flask的莱元元电商数据分析系统_电商销量预测
spring boot·python·django·flask·node.js·php