A VM-based JavaScript obfuscator built on the PyRite compiler. PyRiteJS compiles PyRite source code to JSON bytecode, runs it in a JavaScript VM, and obfuscates the bundle for deployment.
PyRiteJS implements a complete JavaScript virtual machine that executes bytecode compiled by the PyRite C++ compiler. The system consists of:
- Runtime VM - A stack-based bytecode interpreter that executes PyRite bytecode in JavaScript
- Bridge System - Bidirectional communication between PyRite and HTML/JS (DOM, events, fetch, reactive state)
- Obfuscation Layer - Opcode shuffle, XOR encryption, and AES encryption to protect the bytecode
- Build Pipeline - A build tool that compiles
.prc.jsonbytecode files into self-contained.jsbundles
PyRiteJS/
├── README.md # This file
├── package.json # Node.js package config
├── build.js # Build pipeline (Section 11)
├── src/
│ ├── index.js # Main entry point
│ ├── runtime/
│ │ ├── BigNumber.js # Arbitrary precision decimals (Section 3)
│ │ ├── PyValue.js # Value system (Section 4)
│ │ ├── Env.js # Environment & scope chain (Section 5)
│ │ ├── CallFrame.js # Call frame management (Section 7)
│ │ ├── Upvalue.js # Closures & upvalues (Section 8)
│ │ ├── VM.js # VM dispatch loop (Section 6)
│ │ ├── builtins.js # Built-in functions (Section 9)
│ │ └── opcodes.js # Opcode definitions (Section 12)
│ ├── bridge/
│ │ ├── VMBridge.js # HTML -> PyRite (Section 13.2)
│ │ ├── JSBridge.js # PyRite -> Browser (Section 13.3)
│ │ ├── events.js # Event handling (Section 13.4)
│ │ ├── dom.js # DOM operations (Section 13.5)
│ │ ├── async.js # fetch & async (Section 13.6)
│ │ └── reactive.js # Reactive state (Section 13.7)
│ └── obfuscate/
│ ├── opcodeShuffle.js # Opcode shuffle (Section 10.1)
│ ├── xor.js # XOR encryption (Section 10.2)
│ └── aes.js # AES encryption (Section 10.3)
├── cpp/
│ └── JsBytecodeSerializer.cpp # C++ JSON serializer (Section 2)
├── examples/
│ └── counter/
│ ├── counter.pr # PyRite source
│ ├── counter.prc.json # Compiled bytecode
│ ├── counter.bundle.js # Obfuscated bundle
│ ├── generate_counter_bytecode.js # Bytecode generator
│ └── index.html # Demo HTML page
└── tests/
├── run_all.js # Run all tests
├── test_vm_hello.js # VM basic test
├── test_vm_add.js # VM arithmetic test
├── test_vm_closure.js # VM closure test
├── test_vm_loop.js # VM loop/conditional test
├── test_vm_list.js # VM list test
├── test_bridge.js # Bridge system test
└── test_counter.js # Counter example test
# Build the counter bundle (with opcode shuffle obfuscation)
node build.js examples/counter/counter.prc.json --output examples/counter/counter.bundle.js --shuffle --seed 42
# Open the HTML file in a browser
open examples/counter/index.html# Run all tests
node tests/run_all.js
# Run specific tests
node tests/test_vm_hello.js
node tests/test_vm_add.js
node tests/test_vm_closure.js
node tests/test_vm_loop.js
node tests/test_vm_list.js
node tests/test_bridge.js
node tests/test_counter.jsThe build.js script compiles a .prc.json bytecode file into a self-contained .js bundle:
node build.js <input.prc.json> [options]
Options:
--output <file> Output bundle file (default: <input>.bundle.js)
--shuffle Enable opcode shuffle obfuscation
--xor Enable XOR encryption
--xor-key-length N XOR key length (default: 16)
--aes Enable AES encryption
--aes-password P AES password (required if --aes)
--seed N Random seed for shuffle/xornode build.js myapp.prc.json \
--output myapp.bundle.js \
--shuffle \
--xor \
--aes --aes-password "my-secret-key" \
--seed 12345The VM executes JSON-format bytecode:
{
"magic": "PRC1",
"version": 1,
"all_functions": [
{
"name": "main",
"code": [0, 4, 0, 0, 0, ...],
"constants": [...],
"arity": 0,
"max_locals": 4,
"upvalue_count": 0,
"upvalue_descriptors": [...],
"params": [...],
"local_names": [...],
"is_method": false,
"is_exposed": false
}
],
"entry_function": 0,
"exports": ["handleClick", "init"]
}All values in the VM are represented as PyValue objects with a type tag and raw value:
| Type | Tag | Description |
|---|---|---|
| Null | 0 | Null value |
| Number | 1 | BigNumber (arbitrary precision) |
| String | 2 | String |
| Boolean | 3 | Boolean (stored as Number 0/1) |
| List | 4 | Array |
| Dict | 5 | Object |
| Function | 6 | Closure |
| Native | 7 | JS function wrapper |
| Class | 8 | Class definition |
| Instance | 9 | Class instance |
| Binary | 10 | Uint8Array |
| Module | 11 | Module proxy |
| Exception | 12 | Exception |
| BoundMethod | 13 | Bound method |
| Promise | 14 | Promise |
| PyElement | 15 | DOM element wrapper |
| State | 16 | Reactive state |
The VM supports 58 opcodes covering:
- Constants (OP_CONSTANT, OP_NULL, OP_TRUE, OP_FALSE)
- Stack manipulation (OP_POP, OP_DUP)
- Variables (OP_GET_LOCAL, OP_SET_LOCAL, OP_GET_GLOBAL, OP_SET_GLOBAL, OP_DEFINE_GLOBAL)
- Upvalues (OP_GET_UPVALUE, OP_SET_UPVALUE, OP_CLOSE_UPVALUE)
- Properties (OP_GET_PROPERTY, OP_SET_PROPERTY, OP_GET_INDEX, OP_SET_INDEX, OP_GET_LEN)
- Arithmetic (OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW, OP_NEGATE)
- Comparison (OP_EQUAL, OP_NOT_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_GREATER, OP_GREATER_EQUAL)
- Control flow (OP_JUMP, OP_JUMP_IF_FALSE, OP_JUMP_IF_TRUE, OP_LOOP)
- Functions (OP_CALL, OP_CLOSURE, OP_RETURN, OP_RETURN_NULL)
- I/O (OP_PRINT, OP_INPUT)
- Misc (OP_CAST, OP_SWAP, OP_RAISE, OP_TRY_BEGIN, OP_TRY_END, OP_CATCH, OP_HALT)
The bridge system enables bidirectional communication between PyRite and the browser:
// Call an exported PyRite function
window.__PyRite_Bridge.call('handleClick', arg1, arg2);
// Register a JS callback that PyRite can call
window.__PyRite_Bridge.expose('myCallback', (data) => {
console.log('Called from PyRite:', data);
});
// Subscribe to VM events
window.__PyRite_Bridge.onEvent('statechange', (data) => {
console.log('State changed:', data);
});PyRite code can call browser functions:
createElement(tag),getElementById(id),querySelector(sel)setText(el, text),getText(el)setAttribute(el, name, value),getAttribute(el, name)appendChild(parent, child),removeChild(parent, child)alert(msg),setTimeout(fn, ms),setInterval(fn, ms)
elem_on(el, eventType, handler)- Bind eventelem_off(el, eventType, handler)- Unbind eventelem_once(el, eventType, handler)- Bind one-time event
Full DOM manipulation API including:
- Element creation & querying
- Content & attribute manipulation
- Class manipulation
- Form values
- Styles
- Tree manipulation
- Dimensions & scrolling
fetch(url, options)- HTTP requestsawait(promise)- Await a promise (Node.js only with deasync)- Response helpers:
response_text,response_json,response_status,response_ok
state(initial)- Create reactive statestate_get(state),state_set(state, value)- Read/write statebindText(el, state)- One-way binding (state -> element text)bindInput(el, state)- Two-way binding (input element <-> state)computed(state, fn)- Computed statewatch(state, fn)- Watch for changes
Randomly permutes opcode numbers. The permutation is embedded in the bundle header and applied in reverse at load time.
XOR-encrypts bytecode bytes with a random key. The key is embedded in the bundle header.
AES-128-ECB encrypts bytecode bytes with a password-derived key. The password is not embedded (must be provided at load time).
The counter example demonstrates the full PyRiteJS workflow:
- Source (
counter.pr): PyRite source code with reactive state and event handlers - Bytecode (
counter.prc.json): JSON bytecode (generated bygenerate_counter_bytecode.js) - Bundle (
counter.bundle.js): Obfuscated, self-contained JS bundle - HTML (
index.html): Demo page that loads the bundle
The counter features:
- Reactive state (
state(0)) - Event binding (
elem_on) - State binding (
bindText) - State updates (
state_set)
The cpp/JsBytecodeSerializer.cpp file shows how to add JSON output to the PyRite C++ compiler. Add this file to the compiler project and call serializeToJson(program) to emit JSON bytecode.
MIT