【问题标题】:Inspecting WebSocket frames in an undetectable way以无法检测的方式检查 WebSocket 帧
【发布时间】:2015-09-19 19:27:08
【问题描述】:

如何在 Chrome 扩展程序或 Firefox 插件中以页面无法检测到的方式读取网页的 WebSocket 帧?

Inspect WebSockets frames from a Chrome Dev Tools extension 提出了类似的问题,但开发 NPAPI 插件不再有意义,因为它很快就会被删除。

【问题讨论】:

  • 只需连接 WebSocket 构造函数并注册一个消息监听器。无论使用哪种浏览器,这将始终有效。
  • @RobW,感谢您的评论。 WebSocket的构造函数是页面创建的,如果我创建了,我不会得到结果
  • 你可以给构造函数打猴子补丁。
  • @RobW 你这是什么意思?在 WebSocket 函数中注入一些东西?
  • 您的赏金不会改变您的问题指定得很差的事实。目前尚不清楚为什么当前的答案不能满足您的需求,这样 Rob 可能会获得(应得的)一半赏金。

标签: javascript google-chrome google-chrome-extension websocket firefox-addon


【解决方案1】:

拦截 WebSocket 数据很容易。只需在页面构造 WebSocket 之前执行以下脚本。这个 sn-p 猴子修补 WebSocket 构造函数:当创建一个新的 WebSocket 构造函数时,sn-p 订阅 message 事件,您可以从那里对数据做任何您想做的事情。

这个sn-p被设计成与原生代码无法区分,因此页面无法轻易检测到修改(但是,请参阅本文末尾的备注)。

(function() {
    var OrigWebSocket = window.WebSocket;
    var callWebSocket = OrigWebSocket.apply.bind(OrigWebSocket);
    var wsAddListener = OrigWebSocket.prototype.addEventListener;
    wsAddListener = wsAddListener.call.bind(wsAddListener);
    window.WebSocket = function WebSocket(url, protocols) {
        var ws;
        if (!(this instanceof WebSocket)) {
            // Called without 'new' (browsers will throw an error).
            ws = callWebSocket(this, arguments);
        } else if (arguments.length === 1) {
            ws = new OrigWebSocket(url);
        } else if (arguments.length >= 2) {
            ws = new OrigWebSocket(url, protocols);
        } else { // No arguments (browsers will throw an error)
            ws = new OrigWebSocket();
        }

        wsAddListener(ws, 'message', function(event) {
            // TODO: Do something with event.data (received data) if you wish.
        });
        return ws;
    }.bind();
    window.WebSocket.prototype = OrigWebSocket.prototype;
    window.WebSocket.prototype.constructor = window.WebSocket;

    var wsSend = OrigWebSocket.prototype.send;
    wsSend = wsSend.apply.bind(wsSend);
    OrigWebSocket.prototype.send = function(data) {
        // TODO: Do something with the sent data if you wish.
        return wsSend(this, arguments);
    };
})();

在 Chrome 扩展程序中,sn-p 可以通过content scriptrun_at:'document_start' 运行,请参阅Insert code into the page context using a content script

Firefox 也支持content scripts,同样的逻辑也适用(contentScriptWhen:'start')。

注意:之前的 sn-p 设计为在页面其余部分之前执行时与本机代码无法区分。检测这些修改的唯一(不寻常且脆弱的)方法是:

  • 将无效参数传递给 WebSocket 构造函数,捕获错误并检查与实现相关的(特定于浏览器的)堆栈跟踪。如果堆栈帧比平时多一个,那么构造函数可能被篡改(从页面的角度来看)。

  • 序列化构造函数。未修改的构造函数变为function WebSocket() { [native code] },而修补后的构造函数看起来像function () { [native code] }(此问题仅存在于 Chrome;在 Firefox 中,序列化是相同的)。

  • 序列化WebSocket.prototype.send 方法。由于该函数未绑定,因此对其进行序列化 (WebSocket.prototype.send.toString()) 会显示非本机实现。这可以通过覆盖.send.toString 方法来缓解,这反过来又可以通过与Function.prototype.toString 的严格比较被页面检测到。如果您不需要发送的数据,请不要覆盖OrigWebSocket.prototype.send

【讨论】:

  • @wZVanG 我已经编辑了答案并更新了 sn-p。使用stackoverflow.com/questions/9515704/… 中的一种同步方法插入sn-p,实际上无法检测到修改(但是请参阅我答案末尾的注释)。
  • @Noitidart the answer 的方法 2 和 3 是同步的,也可以在 Firefox 中使用。
  • @wZVanG 如果输入是自定义构造函数,您可以覆盖Function.prototype.toString 并返回与“现实”匹配的自定义值。但这反过来又可以通过创建一个新的脚本上下文 (iframe) 并使用该上下文中的Function.protototype.toString 来获得真正的序列化来解决。网页不太可能使用我笔记中的方法,因为它们很复杂并且输出是非标准的(并且在浏览器决定更改内部时可能会中断)。
  • @wZVanG 不,因为正如我在之前的评论中解释的那样,那些专门试图从我的答案中定位代码的人可能会绕过它。如果你只覆盖构造函数而不是.send,那么你是相当安全的,尤其是如果你使用Firefox。
  • @wZVanG 那么检测修改的唯一方法是将错误的参数传递给构造函数并检查错误的堆栈跟踪。这可以通过捕获错误、重写堆栈跟踪然后重新抛出错误来解决。但是在看到你网站上的pastebin之后,我认为不需要这个额外的对策。
【解决方案2】:

Rob W 的方法有一个替代方法,可以完全屏蔽与页面的任何交互(对于 Chrome

也就是说,你可以拿出一些重炮使用chrome.debugger

请注意,使用它会阻止您打开相关页面的开发工具(或者更准确地说,打开开发工具会使其停止工作,因为只有一个调试器客户端可以连接)。 这已得到改进,因为:可以附加多个调试器。

这是一个相当低级的 API;您需要自己使用debugger protocol 构建查询。另外,对应的事件不在1.1文档中,需要看development version

您应该能够接收此类 WebSocket 事件并检查它们的payloadData

{"method":"Network.webSocketFrameSent","params":{"requestId":"3080.31","timestamp":18090.353684,"response":{"opcode":1,"mask":true,"payloadData":"Rock it with HTML5 WebSocket"}}}
{"method":"Network.webSocketFrameReceived","params":{"requestId":"3080.31","timestamp":18090.454617,"response":{"opcode":1,"mask":false,"payloadData":"Rock it with HTML5 WebSocket"}}}

This extension sample 应该提供一个起点。

事实上,这里是一个起点,假设tabId 是您感兴趣的标签:

chrome.debugger.attach({tabId:tab.id}, "1.1", function() {
  chrome.debugger.sendCommand({tabId:tabId}, "Network.enable");
  chrome.debugger.onEvent.addListener(onEvent);
});

function onEvent(debuggeeId, message, params) {
  if (tabId != debuggeeId.tabId)
    return;

  if (message == "Network.webSocketFrameSent") {
    // do something with params.response.payloadData,
    //   it contains the data SENT
  } else if (message == "Network.webSocketFrameReceived") {
    // do something with params.response.payloadData,
    //   it contains the data RECEIVED
  }
}

我已经测试了这种方法(链接示例如上修改)并且它有效。

【讨论】:

  • 问题是,这些事件不是 1.1 协议的一部分。在内部,DevTools 使用更高版本..
  • 那是什么意思?我会有问题吗?
  • 不,我只是不确定它是否有效。现在我测试了它,它确实有效。
  • @wZVanG 有一种启发式方法可以检测调试会话,即通过监视窗口调整大小事件。如果页面顶部出现黄色信息栏,则页面内容变小,理论上可以被页面检测到。在实践中,这种检测方法是牵强附会的。如果你愿意,你可以启用chrome://flags/#silent-debugger-extension-api 标志来防止信息栏出现。
  • 没有。如果你这样做,我会努力取消删除它。因为这些答案对普通观众很有用,而 Stack Overflow 的目的不是回答您个人,而是回答可能需要知道同样事情的其他人。
【解决方案3】:

只是为@Xan 答案添加一个例外(我没有足够的代表来对他的答案发表评论,所以我在这里添加它,因为我相信它可以为其他人节省一些时间)。

如果 WebSocket 连接是在通过 about:data:blob: 方案加载的上下文中建立的,则该示例将不起作用。

相关bug请看这里:Attach debugger to worker from chrome devtools extension

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2016-09-21
  • 1970-01-01
  • 2019-02-14
  • 1970-01-01
  • 2018-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多