A standalone, high-performance deobfuscator for Roblox Luau scripts protected by Luraph v15.
Luraph v15 compiles Luau scripts into an interpreted virtual machine (VM) with flattened dispatch loops, encrypted memory buffers (LPH_ENCSTR, LPH_ENCFUNC), and anti-tamper crash traps (LPH_CRASH()).
This tool acts as a reverse compiler:
- Recovers original control flow structures (
if/else,while,for,repeat/until) from low-level VM dispatchers. - Decrypts lazy in-memory constants using a live sandboxed Luau runtime.
- Isolates and bypasses anti-tamper traps to discover hidden code paths.
The deobfuscator operates across 5 stages:
┌────────────────────────┐
│ Protected Luraph Script│
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 1. AST Hooking │ ➔ Discovers VM dispatchers & hooks closure makers
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 2. Sandboxed Simulation│ ➔ Runs in offline Roblox VM; isolates anti-tamper traps
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 3. Live REPL Decryption│ ➔ Queries in-memory Luau VM to decrypt lazy constants
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 4. CFG & SSA Lifting │ ➔ Reconstructs control flow graph & loop structures
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 5. Polish & Naming │ ➔ Infers local variable names from Roblox API usage
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ Clean Luau Source Code │
└────────────────────────┘
-
AST Analysis & Hooking (
vmmap): Parses the input script withluau-astto map out the VM interpreter loop and closure constructors. Injects non-destructive telemetry hooks to track proto execution and register states. -
Sandboxed Simulation (
harness): Executes the script inside an isolated, offline Roblox engine emulator (envlog.luau). If execution encounters an anti-tamper trap (LPH_CRASH()), the engine identifies the responsible function, disables the trap path, and reruns along stable branches. -
Live REPL Memory Decryption (
devirt): Luraph's lazy encryption requires dynamic decryption keys. The engine communicates directly with an active Luau REPL process via IPC to decrypt string and numerical constants live in memory. -
Control Flow Graph (CFG) Reconstruction (
structure,loops): Uses Sparse Conditional Constant Propagation (SCCP) to trace virtual register values. Basic blocks are assembled into a control flow graph, restoring nested loops (while,for,for-in) and branching logic (if/elseif/else). -
Polish & Variable Naming (
backend,names): Virtual registers are analyzed across basic blocks using Static Single Assignment (SSA) web analysis. Variables are assigned context-aware names based on Roblox API usage (e.g.Players,TweenService,ReplicatedStorage), and the output is verified through syntax compilation.
For compiler theory, opcode mechanics, and detailed analysis passes, see TECHNICAL.md.
- Full Devirtualization: Recovers high-level control flow, closures, and scoping instead of simple trace logging.
- Optimized Symbolic Engine: Shared mode-dispatch cache, iterative AST traversal, and declaration memoization deliver up to 2.1x faster devirtualization.
- Robust Table Lifting: Handles
table.pack-style packed lists ({n = count}) in VM registers, plus symbolic indexing, comparison, and boolean coercion on packed values. - Fast-Path Engine: Skips redundant verification passes once all constants are decoded live.
- Offline Roblox Sandbox: Includes offline models for Roblox services,
Path2D,UDim2,Vector3,CFrame, and executor globals. No Roblox client needed. - Self-Contained: Luau binaries, runtime emulators, and analysis modules are bundled directly inside this repository.
Support for older Luraph builds is experimental and does not yet recover full source for every script. Honest status per version:
| Version | Status |
|---|---|
| v14.7 | Lifts real source for many scripts (verified on multiple samples, e.g. a full hub script recovered statement-for-statement). Some samples still fall back to the behaviour trace or hit parser limits. |
| v14.8 | Experimental. Detection and bytecode walking work; the lift often stops at runtime-built helper closures and falls back to the behaviour trace. |
| v14.9 | Experimental. Same as v14.8, plus 1-2 MB blob decodes that are slow in the emulator. Full source recovery is not guaranteed. |
The v14 engine shares the v15 core (trace harness, SCCP walk, structuring, codegen) and layers the v14 quirks on top:
- version detection from the header or the
return({...})/local init = (function(...shapes; - VM chunk extraction: for bytecode-wrapper samples the VM lives inside a loadstring'd chunk, which is captured and instrumented automatically (the first pass aborts right after the chunk dump instead of executing the raw VM);
- dispatch loops of both shapes (
while true do local op=(ARR[pc]); ...and v14.8/v14.9'srepeat ... until false), with parenthesised and type-asserted opcode fetches; - closure factories whose prototype is maker argument 0, often installed inside an initializer whose locals the VM closes over (recovered from the
__venvruntime captures where the initializer builds them statically); - flattened handler state that reads an unresolved table or does arithmetic on a missing value stays symbolic instead of aborting the walk;
- native
LPH_NO_VIRTUALIZEclosures are preserved as Luau source with their upvalue bindings rebuilt.
What is still missing for full v14.8/v14.9 source recovery: the runtime-built
VM-object tables whose methods are re-indexed numerically at run time (e.g.
obj[54]) are not yet bound back to their AST definitions, so those lifts
abort and the behaviour trace is written instead.
node deob.js sample/v14/v14.7/<sample>.lua -o out.luaLarge v14.8/v14.9 samples (1-2 MB) need longer budgets because the bytecode blob decode is slow in the emulator:
node deob.js sample.lua --timeout 1200 --budget 1200 -o out.luaBatch mode can process a folder with parallel jobs:
node deob.js sample/v14/v14.7 --jobs 4- Node.js: v18.0.0 or higher
- Python: 3.10 or higher (powers the backend symbolic execution engine)
Clone the repository and install dependencies:
git clone https://github.com/caomod2077/Deobfuscator-Luraph-V15.git
cd Deobfuscator-Luraph-V15
npm install# Deobfuscate a single script (saves to ./output/<filename>)
node deob.js input.lua
# Save to a specific output path
node deob.js input.lua -o output.lua
# Batch mode: process a whole folder of scripts
node deob.js ./my_scripts_folder/
# Batch mode: process multiple files at once
node deob.js file1.lua file2.lua file3.lua
# Fast trace-only mode (~2 seconds, skips bytecode lifting)
node deob.js input.lua --no-devirt
# Detect if a script is protected by Luraph without running it
node deob.js input.lua --detectNote: If your system uses a different Python command (
py,python3, or a virtual environment binary), set thePYTHON_BINenvironment variable:set PYTHON_BIN=py # Windows export PYTHON_BIN=python3 # Linux / macOS
The engine has been heavily optimized (symbolic AST caching, iterative tree walks, single-pass state analysis, shared dispatch-loop cache, and a bounded SCCP walk). Measured on the bundled sample/ scripts, the current build is 1.6x to 1.8x faster than the previous release, and scripts that previously crashed now lift completely:
| Benchmark | Size | Functions | Before | After | Speedup |
|---|---|---|---|---|---|
Blox Fruit.lua |
646 KB | 520 | 2m 04s | 1m 02s | 2.0x |
+1 Speed Keyboard Escape.lua |
543 KB | 316 | 1m 17s | 37s | 2.1x |
Grow A Garden 2 (1).lua |
645 KB | 545 | 3m 51s | 2m 25s | 1.6x |
Grow a Garden.lua |
804 KB | 767 | 1m 54s | 1m 51s | 1.0x |
9eccab05cff67267.lua |
222 KB | 36 | crashed | 4m 02s | fixed |
5ae248d6527b5c01.lua |
170 KB | 1 | 3.7s | 3.7s | — |
Additional stress tests on fresh scripts (run once with the current build, no baseline):
| Stress Test | Size | Functions | Time | Result |
|---|---|---|---|---|
StealAnEgg.lua |
1.62 MB | 1,723 | 12m 21s | 32,435 lines (1.8 MB) |
Steal-a-Brainrot.lua |
1.07 MB | 1,138 | 6m 59s | 19,407 lines (463 KB) |
JumpForAnimals.lua |
503 KB | 457 | 1m 38s | 7,217 lines (160 KB) |
RideAPet.lua |
397 KB | 328 | 55s | 5,170 lines (113 KB) |
Every input and its reference deobfuscated output is committed in sample/ and sample/output/. All outputs pass luau-ast syntax validation (COMPILE OK). StealAnEgg.lua lifts 99.97% of its 1,723 functions; the remaining 46 blocks (0.03%) are guarded with an explicit error("devirt: unexplored successor ...") marker instead of silently emitting wrong code.
Reproduce it yourself — every input in the table above ships in sample/ and the expected deobfuscated result for each one is committed in sample/output/, so you can verify both the timings and the output quality on your own machine:
node deob.js "sample/Blox Fruit.lua" -o "output/Blox Fruit.lua"
node deob.js "sample/9eccab05cff67267.lua" -o "output/9eccab05cff67267.lua"Hardware used for the table: consumer Windows machine, Node.js 24, Python 3.12. Exact times vary with CPU and background load; the relative speedup holds.
| Script Size | Function Count | Average Time | Mode |
|---|---|---|---|
| Small / Micro Script | 1 – 20 functions | 1 – 5 seconds | Full Devirtualize |
| Medium Script (Hubs) | 50 – 200 functions | 15 – 35 seconds | Full Devirtualize |
| Large Script (Full Games / Complex) | 500 – 750+ functions | 1 – 2 minutes | Full Devirtualize |
| Any Script (Trace Mode) | Any size | 1 – 3 seconds | --no-devirt |
Luraph v15 is a virtual machine obfuscator, not simple encryption. The deobfuscator performs full compiler-grade reverse compilation:
- It symbolically simulates virtual registers across thousands of instruction paths.
- It sends requests to an in-memory Luau VM to decrypt 2,000–2,600 constants on-the-fly.
- It performs dominator tree analysis to reconstruct nested control flow loops.
- It analyzes register lifetimes to assign clean local variables across 10,000+ lines of code.
The current build cuts this cost with a shared dispatch-loop cache (mode resolution is computed once per VM instead of once per function), iterative AST walkers with declaration caching, a single-pass state-variable analysis, and a converged SCCP walk limit that keeps CFG minimization proportional to real code size.
If you only need to see what a script does (URLs fetched, remotes fired, UI setup, webhooks) without needing the full lifted source code, use the --no-devirt flag:
node deob.js input.lua --no-devirtThis runs the script in the sandboxed Luau runtime and extracts the complete execution trace in ~2 seconds.
| Option | Short | Default | Description |
|---|---|---|---|
--output <file> |
-o |
./output/<name> |
Custom destination path for the deobfuscated file |
--no-devirt |
false |
Fast execution trace only (skips VM bytecode lifting) | |
--no-hooks |
false |
Disable VM closure entry instrumentation | |
--no-fold |
false |
Disable loop and helper folding in trace mode | |
--timeout <sec> |
90 |
Hard timeout per Luau execution pass | |
--budget <sec> |
30 |
Soft execution budget for script runtime | |
--max-runs <n> |
12 |
Maximum reruns allowed for anti-tamper traps | |
--devirt-rounds |
200 |
Maximum constant decryption rounds | |
--detect |
false |
Detect and print obfuscator type, then exit | |
--debug |
false |
Keep intermediate disassembly files in output folder |
For in-depth explanations of the reverse-engineering methodology, SCCP symbolic evaluation, opcode dispatcher mapping, and SSA register renaming, see TECHNICAL.md.
This project is open-source under the MIT License.
Thanks source to ccjvwsod on Discord This version was rebuilt by me using Node so that multiple Deobf instances can run simultaneously
If this tool saved you time, consider buying me a coffee. Every donation keeps the updates coming (new obfuscator versions, faster lifts, better structuring).
Bitcoin (BTC)
Ethereum (ETH)




