Skip to main content
This guide explains how the deobfuscation process works, from obfuscated JavaScript to readable code using AST transformations.

Overview

The deobfuscation pipeline uses jscodeshift to perform multiple AST (Abstract Syntax Tree) transformations on bet365’s obfuscated JavaScript. The process is automated but understanding each step helps when bet365 updates their obfuscation techniques.
bet365 frequently updates their obfuscation patterns. This project maintains multiple transformation scripts (numbered 0-10) to handle different obfuscation techniques.

The Transformation Pipeline

Entry Point

The main transformation script is refactor-obfuscated-code-jscodeshift.js:
const rawObfuscatedJsFileName = argv[2];  // Input file
const deobfuscatedJsFileName = argv[3];   // Output file

const rawObfuscatedJsCode = fs.readFileSync(rawObfuscatedJsFileName).toString();
const transformedJscodeshiftAst = transform(rawObfuscatedJsCode);
const ast = esprima.parseScript(transformedJscodeshiftAst.toSource());

const refactoredJsCode = escodegen.generate(ast);
fs.writeFile(deobfuscatedJsFileName, refactoredJsCode, ...);

Transformation Stages

The deobfuscation happens through multiple chained transformations:
  1. refactor-obfuscated-code-jscodeshift-0.js - Initial cleanup
  2. refactor-obfuscated-code-jscodeshift-1.js - Variable renaming
  3. refactor-obfuscated-code-jscodeshift-2.js - Function inlining
  4. refactor-obfuscated-code-jscodeshift-3.js - Expression simplification
  5. …through refactor-obfuscated-code-jscodeshift-10.js
Each script is tested with corresponding .test.js files.
The transformation scripts are modular. You can add new transformations by creating refactor-obfuscated-code-jscodeshift-11.js and importing it into the chain.

Manual Deobfuscation Process

For analyzing new obfuscation patterns or developing new transformations:
1

Place obfuscated code

Copy the obfuscated JavaScript to:
mitmproxy/src/javascript/obfuscated-original.js
You can get this file from the output/ directory after intercepting a request.
2

Run transformation manually

Execute the transformation directly:
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  mitmproxy/src/javascript/obfuscated-original.js \
  mitmproxy/src/javascript/deobfuscated-output.js
The deobfuscated code will be written to deobfuscated-output.js.
3

Review the output

Open the deobfuscated file to verify the transformation:
code mitmproxy/src/javascript/deobfuscated-output.js
# or use your preferred editor

Development Workflow with Auto-Recompilation

When developing new transformations, use watchexec for automatic recompilation:
watchexec -e js "touch mitmproxy/src/python/download-payload.py && \
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
mitmproxy/src/javascript/obfuscated-original.js \
mitmproxy/src/javascript/deobfuscated-output.js && \
node mitmproxy/src/javascript/pre-transform-code.js"
This command:
  • Watches for changes to any .js file (except obfuscated-original.js and deobfuscated.js)
  • Automatically runs the transformation pipeline when you save changes
  • Touches download-payload.py to trigger mitmproxy reload
Make sure you have watchexec installed: brew install watchexec (macOS) or check your package manager for other systems.

Understanding the Code Structure

Pre-Transform Code

The pre-transform-code.js file is injected before the deobfuscated code and provides:
var consoleLogger = function(string) {
    if (console) {
        console.log(string);
    }
};

var print = function(label, input) {
    // Handles logging arrays, objects, and primitives
    consoleLogger(label + ": " + toJsonString(input));
};

var tapeKeywords = {};  // For tracking execution context
var states = [];        // For tracking state changes
These utilities help debug the deobfuscated code in the browser.

Post-Transform Code

The post-transform-code.js file is injected after the deobfuscated code and contains:
  • Runtime patches for behavior modifications
  • Additional logging hooks
  • Workarounds for browser detection bypass

Transformed Code Replacement

The system supports replacing specific code sections with improved versions:
transformed_files = Path(self.javascript_base_path).glob('**/transformed-*.js')
for transformed_file in transformed_files:
    identifier = transformed_file.name.removeprefix("transformed-").removesuffix(".js")
    post_transform_file = Path(self.javascript_base_path + f'/post-transform-{identifier}.js')
    if post_transform_file.is_file():
        self.replace_contents[jsmin(transformed_file_contents)] = jsmin(post_transform_file_contents)
This allows you to create transformed-*.js and post-transform-*.js file pairs for specific code replacements.

AST Manipulation Tools

AST Explorer

Use AST Explorer to understand JavaScript AST structure:
  1. Paste obfuscated code in the left panel
  2. Select “JavaScript” → “esprima” or “recast” as the parser
  3. Select “jscodeshift” as the transformer
  4. Write transformation code in the transform panel
AST Explorer is invaluable for prototyping new transformations. Once you have working code, copy it into a new refactor-obfuscated-code-jscodeshift-*.js file.

jscodeshift API

Common jscodeshift patterns used in the transformations:
j(ast)
  .find(j.Identifier, { name: '_0x123a' })
  .forEach(path => {
    j(path).replaceWith(j.identifier('readableName'));
  });

Testing Transformations

Each transformation script has a corresponding test file:
npm test
# Runs all jest tests for transformation scripts
Test file structure:
// refactor-obfuscated-code-jscodeshift-1.test.js
const transform = require('./refactor-obfuscated-code-jscodeshift-1');
const testUtil = require('./refactor-obfuscated-code-jscodeshift-test-util');

describe('Transformation 1', () => {
  it('should rename obfuscated identifiers', () => {
    const input = `var _0x123a = function() {}`;
    const expected = `var readableName = function() {}`;
    const result = testUtil.applyTransform(transform, input);
    expect(result).toBe(expected);
  });
});
Always add tests when creating new transformations. This helps catch regressions when bet365 updates their code.

Handling Obfuscation Changes

bet365 rotates their obfuscation code based on:
  • Location (IP address)
  • Time (periodic rotation)
  • Browser fingerprint

Detecting Changes

When deobfuscation fails:
1

Collect the new obfuscated code

# Run the interception
./mitmproxy.sh
# Navigate to bet365 in the proxied browser
# Save the obfuscated code
python mitmproxy/src/python/save_obfuscated_code.py
2

Compare with previous versions

# List historical obfuscated files
ls -lh mitmproxy/src/javascript/obfuscated/

# Use a diff tool to compare
diff -u mitmproxy/src/javascript/obfuscated/previous.js \
        mitmproxy/src/javascript/obfuscated-new-raw.js
3

Identify the new pattern

Look for:
  • New obfuscation wrapper functions
  • Changed variable name patterns
  • New control flow obfuscation techniques
  • Additional string encoding layers
4

Update transformations

Create or modify transformation scripts to handle the new pattern, then test:
npm test  # Run all tests

# Test manually
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  mitmproxy/src/javascript/obfuscated-new-raw.js \
  test-output.js

Debugging Transformation Issues

Enable Intermediate Output

Modify refactor-obfuscated-code-jscodeshift.js to save intermediate steps:
const outputIntermediateSteps = true;  // Set to true
This creates files showing the AST after each transformation stage.

Inspect AST Directly

Use Node.js REPL to explore the AST:
const j = require('jscodeshift');
const fs = require('fs');

const code = fs.readFileSync('obfuscated.js', 'utf8');
const ast = j(code);

// Explore the AST
ast.find(j.FunctionDeclaration).forEach(path => {
  console.log(path.value.id.name);
});

Next Steps

Build docs developers (and LLMs) love