Skip to content

Instantly share code, notes, and snippets.

@JeremyRH
Last active January 30, 2021 05:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JeremyRH/425d476eb5109e17ab39ba81e2894418 to your computer and use it in GitHub Desktop.
Save JeremyRH/425d476eb5109e17ab39ba81e2894418 to your computer and use it in GitHub Desktop.

Bypassing a Browser Game's Client-Side Security

Not long ago, I used to play a real-time multiplayer browser game called TagPro. The architecture is fairly standard for real-time multiplayer games. The browser opens a websocket connection to a server and user input is sent over the open connection. The server runs all the game logic and sends the game state to each browser several times a second.

Client-Side Security

There are two separate versions of the JavaScript bundle that powers the TagPro client: a competitive version with client-side security and a casual version without. A toggle in private games enables the competitive version. The JavaScript for TagPro's competitive scene has a few security measures in place to prevent third-party script execution. The first security measure is not exposing the game object globally by wrapping the game.js bundle in a self-executing function:

(function init() {
  // game.js
  var tagpro = {
  ...
  // etc.
})();

(This is not implemented in the casual version because many third-party mods for the game depend on window.tagpro.)

The second security measure consisted of sending the stringified length of the wrapped function to the server for validation:

(function init() {
  // game.js
  var tagpro = {
  ...
  // Ensure init function hasn't been tampered with
  ws.send("6941e8c29f:" + init.toString().length);

6941e8c29f is a generated socket message key. It's something that changes every time the bundle changes, most likely a hash of the bundle.

These security measures can effectively stop someone from tampering with the bundle and enforce everyone is running the same game.

Bypassing the Security

The first thing I did was play the game with logging in place. I was able to gather logs of all websocket messages being sent by monkey patching Websocket.prototype.send and playing the game normally. I then checked the logs for anything unique or unexpected which lead me to the discovery of the second security measure. I didn't want to set off any alarms by testing on TagPro's server so I reimplimented the security measures locally.

My goal was to expose the tagpro object globally to allow any third-party script to be run in competitive games. I couldn't just replace var tagpro = with window.tagpro =, that would change the init function length. Also keeping a modified static copy of the bundle around would require manual updates when TagPro updates.

Solving the first issue was fairly simple. I added a three letter alias for window and replaced var tagpro:

window.win = window;
(function init() {
  win.tagpro = {
  ...
})();

This kept the length check the same. The next thing I needed to do was replace the bundle on the fly. I redirected the request to point at a local proxy. The proxy downloaded the original script from TagPro's server, added window.win = window; to the top, and replaced var tagpro with win.tagpro. This seemed to work just fine and I was able to use any third-party mod without being detected. Unfortuanatly, I couldn't stop other players from becoming suspicious of my play style and was eventually banned from competetive play.

@chrisstevensjunior
Copy link

Hey, do you have an example script I could look at as reference?

@JeremyRH
Copy link
Author

Sorry I don't. Also sharing code would make it easier for people to cheat.
If you're interested in this stuff, use that interest as motivation to learn. There are so many free resources available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment