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()

总结

相关推荐
迷雾漫步者20 分钟前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-1 小时前
验证码机制
前端·后端
燃先生._.2 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖3 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235243 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240254 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar4 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人4 小时前
前端知识补充—CSS
前端·css
GISer_Jing5 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试