5

I'm trying to create a Chrome extension content script which can hook the WebSocket of a specific page. Currently when the page is accessed, the content script injects another script into the page like so:

    var s = document.createElement('script');
    s.src = chrome.extension.getURL('Script.js');
    s.onload = function () {
        this.remove();
    };
    (document.head || document.documentElement).appendChild(s);

Then my Script.js contains an addEventListener for the websockets.

Window.WebSocket.addEventListener("message",function(event){
console.log("Websocket read");
})

But when I run the extension I receive an error can not read property addEventListener of undefined. So I'm not entirely certain if that's the correct way to do it. I have verified that the script does get injected the page but how would I override or create my own listener on top of original onmessage WebSocket from the page. Is it possible?

Ahmed Hany
  • 952
  • 6
  • 12
Irelia
  • 3,407
  • 2
  • 10
  • 31
  • can you try with lower caps window like `window.WebSocket.addEventListener`? – Arun Kumar Oct 04 '18 at 15:35
  • Hello, when I change it to `window.WebSocket.addEventListener` I receive this error: `Uncaught TypeError: window.WebSocket.addEventListener is not a function` – Irelia Oct 04 '18 at 15:38
  • use `onmessage` instead like `window.WebSocket.onmessage = function(event) { console.debug("WebSocket message received:", event); };` – Arun Kumar Oct 04 '18 at 15:41
  • I made the changes as you said, but it doesn't seem to have done anything. I tried with console.debug, console.log and even alert. ```window.WebSocket.onmessage = function(event){ alert(event.message); }``` – Irelia Oct 04 '18 at 15:46
  • are you testing it through client? you need to create websocket client which will send message then you will get that message in `onmessage` event. Check this link for reference https://www.pegaxchange.com/2018/03/23/websocket-client/ – Arun Kumar Oct 04 '18 at 15:54
  • Yes the webpage I am injecting this into is a WebSocket client. What I would like to have is when the WebSocket server sends a message to the WebSocket client (The web page) to see those requests before they get passed onto the original message handler. The WebSocket client is running over SSL, if it makes any difference. But I doubt it should. – Irelia Oct 04 '18 at 16:05
  • Were you able to achieve this? – deepg Mar 04 '19 at 09:08
  • I was not, do you have a solution? – Irelia Mar 07 '19 at 15:51
  • 1
    where did you deifined `Window.WebSocket` ? I'm not expert of chrome extension but I think it is wrong. In javascript you should first create an instance of websocket using `new` keyword. – pouyan Nov 27 '21 at 10:38
  • @pouyan The point of this isn't to create and hook a new instance but hook an existing instance – Irelia Nov 28 '21 at 01:45
  • @Akali please share [MWE](https://stackoverflow.com/help/minimal-reproducible-example). – Chandan Nov 28 '21 at 16:58
  • 1
    Does this answer your question? https://stackoverflow.com/questions/22868897/access-websocket-traffic-from-chrome-extension – skara9 Nov 28 '21 at 20:42
  • @Chandan No idea how you want me to share an MWE when I'm literally asking on how to do it with the code I posted above – Irelia Nov 29 '21 at 03:37
  • @skara9 I'll look into that thank you – Irelia Nov 29 '21 at 03:37
  • @Akali did you created a repository for the project if possible please can you share the link to it – Chandan Nov 29 '21 at 08:10

3 Answers3

3

WebSocket is a class. The addEventListener function is only available in an instance of the class. So you must find the variable the web page creates and add the listener to that. For example:

var mySocket = new WebSocket("wss://127.0.0.1:8080");

You would do something like:

mySocket.addEventListener("message", (e) => console.log(e));

However, this will miss all the initial messages and it depends on you being able to get access to the variable which may not be possible in all situations.

Instead, what you can do is override the class with your own class so that when they create a WebSocket instance, its actually your class.

An example of that would be something like:

class MyWebSocket extends WebSocket
{
    __constructor()
    {
        super(...arguments);
    }

    addEventListener(name, callback)
    {
        // call the original event listener
        super.addEventListener(name, function()
        {
            console.log("Im sneakily listening in here", arguments);

            // call their callback
            callback(...arguments);
        });
    }
}

// Override the real web socket with your version
window.WebSocket = MyWebSocket;

Keep in mind the above code is pseudo and untested.

If you just want to see the socket data. You can open chrome dev tools, click the "ws" filter. Then click on the web socket listed and view the raw data being transferred.

Here is a working sample that should work from your context script.

while(document == null || document.head == null);

var s = document.createElement("script");

function hookWebSocket()
{
    window.RealWebSocket = window.WebSocket;

    window.WebSocket = function()
    {
        var socket = new window.RealWebSocket(...arguments);

        socket.addEventListener("message", (e) =>
        {
            console.log("The message is: ", e);
        });

        return socket;
    }
}

s.innerHTML = `
    ${hookWebSocket.toString()}

    hookWebSocket();
`;

document.head.prepend(s);
John
  • 5,942
  • 3
  • 42
  • 79
  • Yes I am aware it's possible to do if you find the variable however it's not a generic solution finding websocket variables for every possible site that relies on it. Which is why I was hoping for a way to hook it internally. But I do think overriding the WebSocket class so mine gets instantiated instead of the original is probably the only solution. – Irelia Nov 30 '21 at 17:47
  • @Akali I added a working sample for you to use. – John Nov 30 '21 at 23:35
  • So this essentially appends or inserts the script tag into the document head then hooks the websocket from there? – Irelia Nov 30 '21 at 23:41
  • @Akali that's a bingo – John Nov 30 '21 at 23:43
1

You can use wshook which overrides the original WebSocket class and add callbacks which can be used for the task

You can directly copy the script wsHookjs and paste before your code or import like normal javascript and then start using it.

Import the library

<script src='wsHook.js'></script>

then you can just add callbacks for either before or after or both to intercept websocket send request:

wsHook.before = function(data, url, wsObject) {
    console.log("Sending message to " + url + " : " + data);
}

// Make sure your program calls `wsClient.onmessage` event handler somewhere.
wsHook.after = function(messageEvent, url, wsObject) {
    console.log("Received message from " + url + " : " + messageEvent.data);
    return messageEvent;
}

Note: Content Script can be loaded before page load. For more information about how to load content script before page load you can check this post

Chandan
  • 11,465
  • 1
  • 6
  • 25
  • This doesn't appear to be the solution I'm looking for since I'm not the one who deployed or published the website or page. Rather I'm trying to inject a generic script into other websites or pages which utilises websockets and hooking that specific instance. – Irelia Nov 30 '21 at 17:46
  • @Akali you can inject script before page loads, for more info how to load content script before page loads check [here](https://stackoverflow.com/questions/19191679/chrome-extension-inject-js-before-page-load). – Chandan Nov 30 '21 at 18:09
  • Are you implying I inject the enterity of wsHook.js? – Irelia Nov 30 '21 at 18:11
  • Even if my content script runs right before the page loads, would I then inject the – Irelia Nov 30 '21 at 18:13
  • @Akali you don't need to inject script tag in any website just you need to override `WebSocket` which is done by the `wshook` script after you content script is loaded the `WebSocket` would be wrapped by the library then page would be loaded which would use the updated `WebSocket`. – Chandan Nov 30 '21 at 18:22
  • Okay I'll give this a try and see if it works – Irelia Nov 30 '21 at 18:23
  • @Akali you can also load the wshook from content script which may probably update `WebSocket` – Chandan Nov 30 '21 at 18:42
  • so my content script should just contain the code in wsHookjs which would ultimately override WebSocket object? – Irelia Nov 30 '21 at 18:56
  • @Akali yes then the page would use the wrapped `WebSocket` which would intern call the before and after callbacks. – Chandan Nov 30 '21 at 18:58
  • I added the contents wsHookjs in my content.js as well as the before and after and added run at: document_start in my manifest.json as well as gave it permission to run on a specific site but its still not hooking the websocket.. – Irelia Nov 30 '21 at 19:15
  • @Akali Context scripts run in a sandbox with a fake window element/object. In order to access and overwrite the real `window` object. You need to prepend the script's content to the document before any call to WebSocket is made. You can do this by putting the script's content via script element using innerHTML and prepending to the head element. Its a race to get your script to execute before theirs do. – John Nov 30 '21 at 23:24
  • @John that's what I was thinking. So would the run_at : document_start and then attemping to inject the script work? I can attempt it – Irelia Nov 30 '21 at 23:37
  • @Akali yes, but I would not try to load the script normally through an src attribute since that could take some time for it to process and you have milliseconds to get it right. It should be an inline script element. Also it should be prepended to the head element rather than appended to the body so that its first in line to get executed. I added a working sample to my answer. – John Nov 30 '21 at 23:39
0

Could you try with this?

s.onload = function () {
  this.parentNode.removeChild(this);
};
Samir Abis
  • 36
  • 2