Skip to content
Obfuscationadvanced

Control-Flow Flattening

Replacing a function's natural branching with a single dispatcher loop driven by a state variable, destroying the original control-flow graph that decompilers rely on.

Control-flow flattening rewrites a function into a while (state) switch (state) dispatcher. Every original basic block becomes a case that computes the next state value, so the decompiler sees one giant loop instead of the real CFG.

Shape

c
int state = 0x1A2B;
while (state != DONE) {
  switch (state) {
    case 0x1A2B: /* block A */ state = cond ? 0x77F0 : 0x0C31; break;
    case 0x77F0: /* block B */ state = 0x4ED2; break;
    /* ... */
  }
}

In disassembly the whole function collapses into one dispatcher loop driven by a state register that indexes a jump table:

asm
        mov     dword [rbp-4], 0x1A2B   ; state = entry value
.dispatch:                              ; top of the dispatcher loop
        mov     eax, dword [rbp-4]      ; load current state
        cmp     eax, 0x4ED2             ; DONE sentinel?
        je      .exit
        ; map state -> table index, then indirect jump
        lea     rcx, [rel jmp_table]
        movsxd  rdx, dword [rcx+rax*4]  ; signed table entry
        add     rdx, rcx
        jmp     rdx                     ; switch(state)
.case_1A2B:                             ; block A
        ; ... original block A body ...
        test    eax, eax
        mov     edx, 0x0C31             ; next state if false
        mov     ecx, 0x77F0             ; next state if true
        cmovne  edx, ecx
        mov     dword [rbp-4], edx      ; store next state
        jmp     .dispatch
.case_77F0:                             ; block B
        ; ... original block B body ...
        mov     dword [rbp-4], 0x4ED2   ; -> DONE
        jmp     .dispatch
.exit:

Recovering the original flow

  • Symbolic execution of the dispatcher recovers the state-transition graph (e.g. D-810, HexRaysDeob, or custom miasm/Triton scripts).
  • Identify the state variable feeding the switch, enumerate reachable case values, then stitch blocks back into a real CFG.
  • Frequently paired with opaque predicates to make the transitions harder to constant-fold.
Votes

Comments(0)