Opaque Predicates
Inserting conditional branches whose outcome is known at obfuscation time but hard to resolve statically, breaking control-flow recovery in disassemblers and decompilers.
An opaque predicate is a boolean expression whose value is always known to the obfuscator (always true, always false, or context-dependent) but is expensive for an analyst or decompiler to prove. They pad the control-flow graph with unreachable or always-taken edges, defeating naive CFG recovery.
How it works
A classic always-false predicate uses a number-theoretic identity:
// 7*y*y - 1 == x*x has no integer solutions, so this branch is dead.
if (7 * y * y - 1 == x * x) {
junk_code(); // never executes, but the disassembler must consider it
}
real_code();In assembly the dead branch is fully formed, so a linear sweep disassembler decodes the junk and a recursive one cannot prove the edge is dead without a constraint solver.
Disassembled, the impossible identity is computed at runtime and the never-taken edge still points at fully decoded junk:
; compute 7*y*y - 1 in eax
mov eax, dword [rbp-8] ; y
imul eax, eax ; y*y
imul eax, eax, 7 ; 7*y*y
sub eax, 1 ; 7*y*y - 1
; compute x*x in ecx
mov ecx, dword [rbp-4] ; x
imul ecx, ecx ; x*x
cmp eax, ecx ; 7*y*y-1 == x*x ? (no integer solution)
jne .real_code ; always taken
.junk: ; dead block, never reached
call junk_code ; disassembler must still decode this
.real_code:
call real_code ; the path that always runsDetection & bypass
- Symbolic execution / SMT: tools like angr or Triton can prove the predicate constant and prune the dead edge.
- Decompiler microcode plugins (e.g. HexRaysDeob, D-810) simplify common opaque-predicate patterns automatically.
- Look for arithmetic identities feeding a comparison that no real input can satisfy.
Opaque predicates are frequently combined with control-flow flattening and junk-byte insertion.