Search code examples
javascriptgoogle-chromegoogle-chrome-extensionwebsocketfirefox-addon

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?

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.


Solution

  • There is an alternative to Rob W's method that completely masks any interaction with the page (for Chrome)

    Namely, you can take out some heavy artillery and use 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; 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.

    You should be able to receive WebSocket events like those and examine their 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:

    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.