简体   繁体   English

以无法检测的方式检查 WebSocket 帧

[英]Inspecting WebSocket frames in an undetectable way

How I can read WebSocket frames of a web page in a Chrome extension or Firefox add-on, in a way that cannot be detected by the page?如何以页面无法检测到的方式读取 Chrome 扩展程序或 Firefox 附加组件中网页的 WebSocket 框架?

Inspect WebSockets frames from a Chrome Dev Tools extension formulates a similar question, but developing a NPAPI plugin no longer makes sense because it will soon be removed. 从 Chrome Dev Tools 扩展中检查 WebSockets 框架提出了一个类似的问题,但开发 NPAPI 插件不再有意义,因为它很快就会被删除。

Intercepting the WebSocket data is easy.拦截 WebSocket 数据很容易。 Simply execute the following script before the page constructs the WebSocket.只需在页面构建 WebSocket 之前执行以下脚本即可。 This snippet monkey-patches the WebSocket constructor: When a new WebSocket constructor is created, the snippet subscribes to the message event, from where you can do whatever you want with the data.此代码段对WebSocket构造函数进行了猴子修补:创建新的 WebSocket 构造函数时,该代码段订阅message事件,您可以从这里对数据执行任何您想做的操作。

This snippet is designed to be indistinguishable from native code so the modification cannot easily be detected by the page (however, see the remarks at the end of this post).此代码段旨在与本机代码无法区分,因此页面无法轻易检测到修改(但是,请参阅本文末尾的备注)。

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

In a Chrome extension, the snippet can be run via a content script with run_at:'document_start' , see Insert code into the page context using a content script .在 Chrome 扩展程序中,可以通过带有run_at:'document_start'内容脚本运行该代码段,请参阅使用内容脚本将代码插入页面上下文

Firefox also supports content scripts , the same logic applies (with contentScriptWhen:'start' ). Firefox 也支持content scripts ,同样的逻辑也适用(使用contentScriptWhen:'start' )。

Note: The previous snippet is designed to be indistinguishable from native code when executed before the rest of the page.注意:在页面的其余部分之前执行时,前面的代码段被设计为与本机代码无法区分。 The only (unusual and fragile) ways to detect these modifications are:检测这些修改的唯一(不寻常和脆弱的)方法是:

  • Pass invalid parameters to the WebSocket constructor, catch the error and inspecting the implementation-dependent (browser-specific) stack trace.将无效参数传递给 WebSocket 构造函数,捕获错误并检查依赖于实现的(特定于浏览器的)堆栈跟踪。 If there is one more stack frame than usual, then the constructor might be tampered (seen from the page's perspective).如果堆栈帧比平常多一个,则构造函数可能被篡改(从页面的角度来看)。

  • Serialize the constructor.序列化构造函数。 Unmodified constructors become function WebSocket() { [native code] } , whereas a patched constructor looks like function () { [native code] } (this issue is only present in Chrome; in Firefox, the serialization is identical).未修改的构造函数变成function WebSocket() { [native code] } ,而修补的构造函数看起来像function () { [native code] } (这个问题只存在于 Chrome 中;在 Firefox 中,序列化是相同的)。

  • Serialize the WebSocket.prototype.send method.序列化WebSocket.prototype.send方法。 Since the function is not bound, serializing it ( WebSocket.prototype.send.toString() ) reveals the non-native implementation.由于该函数未绑定,因此对其进行序列化( WebSocket.prototype.send.toString() )会揭示非本机实现。 This could be mitigated by overriding the .toString method of .send , which in turn can be detected by the page by a strict comparison with Function.prototype.toString .这可以通过覆盖.send.toString方法来.send ,反过来可以通过与Function.prototype.toString的严格比较被页面检测到。 If you don't need the sent data, do not override OrigWebSocket.prototype.send .如果您不需要发送的数据,请不要覆盖OrigWebSocket.prototype.send

There is an alternative to Rob W's method that completely masks any interaction with the page ( for Chrome )有一种替代 Rob W 的方法可以完全屏蔽与页面的任何交互(对于 Chrome

Namely, you can take out some heavy artillery and use chrome.debugger .也就是说,您可以取出一些重炮并使用chrome.debugger

Note that using it will stop you from opening Dev Tools for the page in question (or, more precisely, opening the Dev Tools will make it stop working, since only one debugger client can connect).请注意,使用它会阻止您打开相关页面的开发工具(或者,更准确地说,打开开发工具将使其停止工作,因为只有一个调试器客户端可以连接)。 This has been improved since: multiple debuggers can be attached.这已得到改进,因为:可以附加多个调试器。

This is a pretty low-level API;这是一个非常低级的 API; you'll need to construct your queries using the debugger protocol yourself.您需要自己使用调试器协议构建查询。 Also, the corresponding events are not in the 1.1 documentation, you'll need to look at the development version .此外,相应的事件不在 1.1 文档中,您需要查看开发版本

You should be able to receive WebSocket events like those and examine their payloadData :您应该能够接收这些 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 should provide a starting point. 此扩展示例应提供一个起点。

In fact, here's a starting point, assuming tabId is the tab you're interested in:事实上,这是一个起点,假设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
  }
}

I have tested this approach (with the linked sample modified as above) and it works.我已经测试了这种方法(链接示例如上修改)并且它有效。

Just to add an exception to @Xan answer (I don't have enough rep to post a comment on his answer so I add it here cause I believe it can save some time to someone else).只是为@Xan 答案添加一个例外(我没有足够的代表对他的答案发表评论,因此我将其添加到此处,因为我相信它可以为其他人节省一些时间)。

That example won't work if the WebSocket connection is established in a context that was loaded via about: , data: and blob: schemes.如果 WebSocket 连接是在通过about:data:blob:方案加载的上下文中建立的,则该示例将不起作用。

See here for the related bugs: Attach debugger to worker from chrome devtools extension有关相关错误,请参见此处: Attach debugger to worker from chrome devtools extension

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM