The Two Attack Surfaces
Obfuscation must defend against two distinct approaches an analyst can take:Static Analysis
Static analysis reads the file without executing it. Tools include decompilers, AST parsers, grep, and bytecode disassemblers. The analyst examines the file on disk.Dynamic Analysis
Dynamic analysis runs the file under a debugger, instrumented VM, or hook framework. The analyst observes behavior at runtime to extract secrets or reconstruct logic.Technique Mapping
A complete pipeline includes at least one technique from each layer.
A Recommended Layering Order
The order in which you apply transformations matters. Some transforms create patterns that later transforms should hide. Others expand code size in ways that make subsequent steps unnecessarily expensive.1
Start with Original Source
Begin with clean, well-tested source code. Obfuscation should be the final build step, applied only after all functionality is verified.
2
Apply AST-Level Transforms
Perform scope-aware renaming, expression decomposition, and statement reordering at the AST level. These restructure the program without changing semantics and provide the foundation for later obfuscation.
3
Encode Sensitive Strings
Apply XOR or byte-array encoding to all sensitive strings. This hides plaintext secrets from static inspection. The encoder itself should use randomized keys per build.
4
Obfuscate Numeric Constants
Replace numeric literals with opaque expressions. For example, replace
42 with (17 * 3) - 9 or a lookup in a precomputed constant table.5
Flatten Control Flow
Apply control flow flattening with opaque predicates. Replace structured control flow with state-machine dispatch loops guarded by always-true or always-false conditions that confuse decompilers.
6
Inject Junk Code and Identifier Flooding
Add dead code branches and flood the namespace with dummy identifiers. Do this after control flow flattening so the junk is also flattened and harder to distinguish from real logic.
7
Wrap Sensitive Sections in a Custom VM
For the most sensitive logic (license checks, cryptographic keys), compile to a custom bytecode and embed a register-based VM interpreter in Lua. The analyst must reverse the VM instruction set before they can analyze the protected section.
8
Compile to Bytecode and Strip Debug Info
Compile the final Lua source to bytecode and strip debug symbols. This removes line numbers, local variable names, and upvalue names from the bytecode, making decompilation harder.
9
Add Anti-Debug and Sandbox Detection
Inject runtime checks for debuggers, hooks, and sandboxed environments into the final output. These must use standard library names that have not been renamed.
10
Test the Output
Run your full test suite against the obfuscated output. Obfuscation can introduce subtle bugs, especially when control flow flattening reorders statements or when opaque predicates have edge-case behavior.
Layer Interaction and Ordering Pitfalls
Applying transformations in the wrong order reduces effectiveness or inflates output size unnecessarily.Junk Code Before Control Flow Flattening
If you inject junk code before flattening, the flattener treats every junk branch as real code and generates additional dispatch states for it. This produces larger output than necessary. Inject junk after flattening, or ensure your flattener can mark dead branches.String Encoding Before Renaming
If your string encoder introduces identifiable patterns (for example,decode_string("\x41\x42")), the variable renamer will also rename the decoder function name, which may make the pattern harder to spot. However, if the encoder uses global names for helper functions, rename those globals first so the encoded strings do not reference obfuscated names.
Anti-Debug After All Other Transforms
Anti-debug checks call standard library functions likedebug.gethook, os.clock, and tostring. If you rename identifiers globally before injecting anti-debug code, these checks will fail because they reference the renamed functions. Always inject anti-debug and sandbox detection code after renaming, or exempt standard library names from renaming.
