Skip to main content
Learn how to create custom jscodeshift transforms to handle new obfuscation patterns in bet365’s JavaScript.

Transform Architecture

The deobfuscation process uses a chain of transforms that progressively simplify obfuscated code. Each transform extends the AstTransformer base class.

Base Transformer Class

import escodegen from "escodegen";
import * as esprima from "esprima";
import fs from "node:fs";

class AstTransformer {
    constructor(stepNumber, jscodeshiftAst, output, outputBaseName) {
        this.stepNumber = stepNumber;
        this.jscodeshiftAst = jscodeshiftAst;
        this.output = output;
        this.outputBaseName = outputBaseName;
        
        if (this.outputBaseName == null) {
            this.outputFileName = `deobfuscated-output-${this.stepNumber}.js`;
        } else {
            this.outputFileName = `${outputBaseName}-${this.stepNumber}.js`;
        }
    }

    performTransform() {
        throw new Error("implement");
    }

    transform() {
        this.performTransform();
        if (this.output) {
            this.outputToFile();
        }
        return this.jscodeshiftAst;
    }

    outputToFile() {
        const jsCode = escodegen.generate(
            esprima.parseScript(this.jscodeshiftAst.toSource())
        );
        fs.writeFileSync(`${__dirname}/${this.outputFileName}`, jsCode);
    }
}
The performTransform() method must be implemented by subclasses. It should modify this.jscodeshiftAst in place.

Transform Chain

Transforms are executed in sequence by the ChainedTransformer:
import {AstTransformer} from "./refactor-obfuscated-code-jscodeshift-common";
import {Void0Transformer} from "./refactor-obfuscated-code-jscodeshift-0";
import {UnaryExpressionTransformer} from "./refactor-obfuscated-code-jscodeshift-1";
import {RefactorVariableTransformer} from "./refactor-obfuscated-code-jscodeshift-2";
// ... more imports

class ChainedTransformer extends AstTransformer {
    performTransform() {
        let ast = this.jscodeshiftAst;
        ast = new Void0Transformer(0, ast, this.output, this.outputBaseName).transform();
        ast = new UnaryExpressionTransformer(1, ast, this.output, this.outputBaseName).transform();
        ast = new RefactorVariableTransformer(2, ast, this.output, this.outputBaseName).transform();
        // ... more transforms
        return ast;
    }
}

Built-in Transform Examples

Transform 0: Void 0 Replacement

Replaces void 0 expressions with undefined:
import {AstTransformer} from "./refactor-obfuscated-code-jscodeshift-common";
import j from "jscodeshift";

class Void0Transformer extends AstTransformer {
    constructor(stepNumber, jscodeshiftAst, output, outputBaseName) {
        super(stepNumber, jscodeshiftAst, output, outputBaseName);
    }

    performTransform() {
        this.jscodeshiftAst
            .find(j.UnaryExpression, {
                operator: 'void',
                argument: {value: 0}
            })
            .replaceWith(path => j.identifier('undefined'));
    }
}

module.exports = {Void0Transformer};
Before:
var x = void 0;
After:
var x = undefined;

Transform 1: Unary Expression Simplification

Simplifies unary expressions with constant operands:
import {AstTransformer} from "./refactor-obfuscated-code-jscodeshift-common";
import j from "jscodeshift";

const operators = {
    "!": [[0, true], [1, false]]
};

class UnaryExpressionTransformer extends AstTransformer {
    performTransform() {
        Object.entries(operators).forEach(([operator, values]) => {
            values.forEach(valueArray => {
                this.jscodeshiftAst
                    .find(j.UnaryExpression, {
                        operator: operator,
                        argument: {value: valueArray[0]}
                    })
                    .replaceWith(path => j.booleanLiteral(valueArray[1]));
            });
        });
    }
}
Before:
var a = !0;
var b = !1;
After:
var a = true;
var b = false;

Transform 2: Variable Renaming

Renames obfuscated variables to meaningful names:
import {AstTransformer} from "./refactor-obfuscated-code-jscodeshift-common";

var refactorVariables = {
    '_0x1e16': 'keywords',
    '_0x35ab': 'getKeywordName',
    '_0x588211': 'globalState',
    '_0x478891': 'tape',
    '_0x4951e9': 'functions',
    // ... many more mappings
};

class RefactorVariableTransformer extends AstTransformer {
    performTransform() {
        Object.entries(refactorVariables).forEach(([key, refactoredVariableName]) => {
            this.jscodeshiftAst
                .findVariableDeclarators(key)
                .renameTo(refactoredVariableName);
        });
    }
}
Before:
var _0x588211 = [];
_0x588211[35] = context;
After:
var globalState = [];
globalState[35] = context;

Creating a Custom Transform

1

Create Transform File

Create a new file following the naming convention:
touch mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift-11.js
2

Import Dependencies

Import the base class and jscodeshift:
import {AstTransformer} from "./refactor-obfuscated-code-jscodeshift-common";
import j from "jscodeshift";
3

Define Your Transformer

Extend AstTransformer and implement performTransform():
class MyCustomTransformer extends AstTransformer {
    constructor(stepNumber, jscodeshiftAst, output, outputBaseName) {
        super(stepNumber, jscodeshiftAst, output, outputBaseName);
    }

    performTransform() {
        // Your transformation logic here
        this.jscodeshiftAst
            .find(j.CallExpression, {
                callee: {
                    type: 'Identifier',
                    name: 'obfuscatedFunction'
                }
            })
            .forEach(path => {
                // Transform each matching node
            });
    }
}

module.exports = {MyCustomTransformer};
4

Add to Chain

Import and add your transformer to the chain:
// In refactor-obfuscated-code-jscodeshift-chained.js
import {MyCustomTransformer} from "./refactor-obfuscated-code-jscodeshift-11";

performTransform() {
    let ast = this.jscodeshiftAst;
    // ... existing transforms
    ast = new MyCustomTransformer(11, ast, this.output, this.outputBaseName).transform();
    return ast;
}
5

Test Your Transform

Test with a sample file:
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  test-input.js test-output.js true
Enable intermediate output (true) to see the result at each step.

Common Transform Patterns

Pattern 1: Simple Replacement

Replace one AST node with another:
performTransform() {
    this.jscodeshiftAst
        .find(j.Identifier, {name: 'oldName'})
        .replaceWith(path => j.identifier('newName'));
}

Pattern 2: Conditional Replacement

Replace based on context or conditions:
performTransform() {
    this.jscodeshiftAst
        .find(j.CallExpression)
        .filter(path => {
            const callee = path.value.callee;
            return callee.type === 'Identifier' && 
                   callee.name.startsWith('_0x');
        })
        .forEach(path => {
            // Transform this call expression
        });
}

Pattern 3: Parent-Child Manipulation

performTransform() {
    this.jscodeshiftAst
        .find(j.MemberExpression)
        .forEach(path => {
            const object = path.value.object;
            const property = path.value.property;
            
            // Access parent node
            const parent = path.parent;
            
            // Modify based on parent context
            if (parent.value.type === 'CallExpression') {
                // This is a method call
            }
        });
}

Pattern 4: Multi-Node Patterns

Find and transform complex patterns:
performTransform() {
    this.jscodeshiftAst
        .find(j.BinaryExpression, {
            operator: '+',
            left: {type: 'StringLiteral'},
            right: {type: 'StringLiteral'}
        })
        .replaceWith(path => {
            // Concatenate string literals at transform time
            const left = path.value.left.value;
            const right = path.value.right.value;
            return j.stringLiteral(left + right);
        });
}

jscodeshift Query API

Finding Nodes

// Find by node type
ast.find(j.VariableDeclaration)

// Find with property matching
ast.find(j.Identifier, {name: 'specificName'})

// Find nested properties
ast.find(j.MemberExpression, {
    object: {name: 'obj'},
    property: {name: 'prop'}
})

Filtering Results

ast.find(j.CallExpression)
    .filter(path => {
        return path.value.arguments.length > 0;
    })

Traversing the AST

// Find all functions
ast.find(j.FunctionDeclaration)
    .forEach(path => {
        // Access the node
        const node = path.value;
        
        // Get the function name
        const name = node.id.name;
        
        // Get function body
        const body = node.body.body;
        
        // Modify the node
        node.async = true;
    })

Building Node Objects

jscodeshift provides builder functions for creating AST nodes:
// Identifiers
j.identifier('variableName')

// Literals
j.stringLiteral('hello')
j.numericLiteral(42)
j.booleanLiteral(true)

// Expressions
j.callExpression(
    j.identifier('func'),
    [j.identifier('arg1'), j.identifier('arg2')]
)

j.memberExpression(
    j.identifier('object'),
    j.identifier('property')
)

// Statements
j.variableDeclaration('const', [
    j.variableDeclarator(
        j.identifier('x'),
        j.numericLiteral(10)
    )
])

Testing Transforms

Unit Testing

Create test files alongside your transforms:
// refactor-obfuscated-code-jscodeshift-11.test.js
import {MyCustomTransformer} from './refactor-obfuscated-code-jscodeshift-11';
import j from 'jscodeshift';

test('transforms obfuscated pattern', () => {
    const input = `
        obfuscatedFunction(arg1, arg2);
    `;
    
    const ast = j(input);
    const transformer = new MyCustomTransformer(11, ast, false, null);
    const result = transformer.transform();
    
    const output = result.toSource();
    expect(output).toContain('deobfuscatedFunction');
});

Integration Testing

Test the entire transform chain with real samples:
node mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  mitmproxy/src/javascript/obfuscated/sample-1.js \
  test-output.js

# Verify output is valid JavaScript
node --check test-output.js

Best Practices

Order Matters: Place transforms in logical order. Simple replacements should come before complex pattern matching.
Test Incrementally: Enable intermediate output to see the effect of each transform step.
Use AST Explorer: Test your node matching patterns in astexplorer.net before implementing them.
Be careful with replacements that could create invalid JavaScript. Always validate output after transforms.

Handling Obfuscation Updates

bet365 frequently updates their obfuscation patterns. When this happens:
1

Capture New Obfuscated Code

Use save_obfuscated_code.py to save the latest version:
python mitmproxy/src/python/save_obfuscated_code.py
This finds the obfuscated file in /output and saves it to /mitmproxy/src/javascript/obfuscated/.
2

Identify Pattern Changes

Compare the new obfuscated code with previous versions to identify what changed:
diff mitmproxy/src/javascript/obfuscated/old-version.js \
     mitmproxy/src/javascript/obfuscated-new-raw.js
3

Update or Create Transforms

Modify existing transforms or create new ones to handle the new patterns.
4

Update Variable Mappings

If variable names changed, update the refactorVariables mapping in Transform 2.

Transform Performance

Transforms run every time a request is intercepted. To optimize:
  • Avoid redundant traversals
  • Use specific node matching instead of broad searches
  • Cache results when possible
  • Profile with --inspect to find bottlenecks
node --inspect --inspect-brk \
  mitmproxy/src/javascript/refactor-obfuscated-code-jscodeshift.js \
  input.js output.js

Resources

Build docs developers (and LLMs) love