Junk-code (dead-code) injection adds realistic-looking but functionally useless code — fake variables, opaque predicates, decoy computations — to bury the real logic in noise an attacker can't cheaply strip. Done well, the junk is indistinguishable from real code. Our obfuscator inflated a 194-character function to 4,451 characters of plausible junk that still returns ('positive', 10).
"Just add fake code" sounds like the weakest obfuscation trick — and done naively it is. But good junk-code injection isn't random garbage; it's decoy code that *looks exactly like the real thing*, so an attacker can't tell signal from noise without analyzing every line. Our Python Obfuscator does this with its Junk layer, so I ran it and measured what you actually get.
Real output below, and the honest answer to "does it help?"
What junk-code injection is
Junk injection inserts code that runs (or looks like it could run) but never affects the result: dead variables, decoy functions, and opaque predicates — conditions crafted to always be true or false, guarding blocks of plausible-but-pointless computation. The goal isn't to break decompilers; it's to waste a human's time. If the junk is statistically indistinguishable from real logic, the analyst has to read and reason about all of it to find the ~10% that matters.
def classify(n):if n < 0:label = "negative"else:label = "positive"total = 0for i in range(n):total += ireturn label, total
The real injected output
With only the Junk layer on, the 194-character function ballooned to 4,451 characters. It prepends a decoy "runtime" (fake decoder lambdas, a hash-like constant, chained computations) and injects a dead branch *inside* the function. Trimmed excerpt of the real output:
IIIlllIIIlllIllIlIIlI = lambda *a: ''.join(map(chr, [x ^ 30 for x in a]))IlllIllIIllIIIlII = getattr(__builtins__, IIIlllIIIlllIllIlIIlI(65, 65, 122, 119, 125, 106, 65, 65), __builtins__)lllIllllIIlllIllllIllllI = '06497bdb9a4872e3f30f73832d02f7bf080418fee4c40d80b7b35a8c99c8655e'# … ~15 more lines of decoy decoders and chained computations …def classify(n):if llIlllIllllIIIIIIIll[22] * 62 + llIlllIllllIIIIIIIll[23] == 5912:lIIIIIllIlIlIIllI = llIlllIllllIIIIIIIll[23] * 147463 + llIlllIllllIIIIIIIll[1]IIIllIIllIIIIIlIIll = (lIIIIIllIlIlIIllI * 4 ^ 6977735) & 4294967295# … dead computation whose result is never used …if n < 0:label = 'negative'else:label = 'positive'total = 0for i in range(n):total += ireturn (label, total)print(classify(5))
The if llIlll…[22] * 62 + … == 5912: guard is an opaque predicate — a computation on junk data whose branch never influences the return. But it *reads* like a real feature flag. That's the point: you can't skip it without proving it's dead.
So… does it actually help?
Honestly: it helps against humans, not against tooling. Junk raises the cost of *manual* reading — 23× more lines here, most of it plausible — and the opaque predicates defeat quick pattern-matching. But a determined analyst can trace data flow: anything whose result never reaches an output or side effect is dead, and automated dead-code elimination or a good debugger can prune a lot of naive junk. Its value depends entirely on how *indistinguishable* the junk is from real code.
Junk is far stronger combined with control-flow flattening: when the whole function is already a state-machine dispatcher, each junk block becomes just another indistinguishable state, so it can't be cheaply spotted and stripped. Our obfuscator injects junk *before* flattening for exactly this reason. See control-flow flattening.
The trade-offs
- Size: the biggest cost — 23× here, and it compounds across a codebase. Inject junk into sensitive functions, not everything.
- Runtime: opaque predicates and decoy computations execute, so there's a small speed penalty.
- Not a secret-hider: like flattening and renaming, junk obscures *logic*, not *data* — a plaintext key is still a plaintext key.
- Best as a layer: on its own it's the weakest technique; stacked with renaming, string encryption and flattening it meaningfully raises the total effort.
That's the honest verdict: junk-code injection is a *force multiplier* for the other layers, not a standalone defense. For the full stack and how the layers combine, see how to obfuscate Python code and is Python obfuscation secure?.
Bury your logic in junk — free
Run your code through our Python Obfuscator with the Junk layer on (best combined with Flatten): it injects indistinguishable decoy code and opaque predicates while keeping the exact behavior. In your browser.
Open the Python ObfuscatorFree tools mentioned here
Related guides
Frequently asked questions
What is junk-code injection in Python obfuscation?
It's adding realistic-looking but functionally useless code — dead variables, decoy functions, and opaque predicates (conditions that are always true or false) — to bury the real logic in noise. The program behaves identically; the junk exists only to waste an analyst's time.
Does junk-code injection actually improve protection?
It helps against manual analysis by multiplying the code a human must read (about 23× in my test) and defeating quick pattern-matching, but it's weaker against tooling — dead-code elimination and data-flow analysis can prune naive junk. It's strongest combined with control-flow flattening, where junk blends into indistinguishable dispatcher states.
What is an opaque predicate?
An opaque predicate is a condition the author knows the value of (always true or always false) but that's hard for an analyst to evaluate without work — e.g. a computation over junk data. It guards decoy code that never runs or never matters, so the reader can't safely skip it without proving it's dead.
How do I add junk code to Python online?
Enable the Junk layer in our free Python Obfuscator. It injects decoy runtime code and opaque-predicate branches (and, combined with the Flatten layer, blends them into the control-flow state machine), returning runnable Python — the transform shown here — entirely in your browser.