$npx -y skills add Prohao42/aimy-skill --skill code-obfuscation-deobfuscationCode obfuscation analysis and deobfuscation playbook. Use when reversing binaries protected by junk code, opaque predicates, self-modifying code, control flow flattening, VM protection, or string encryption.
| 1 | # SKILL: Code Obfuscation & Deobfuscation — Expert Analysis Playbook |
| 2 | |
| 3 | > **AI LOAD INSTRUCTION**: Expert techniques for identifying, classifying, and defeating code obfuscation in native binaries. Covers junk code, opaque predicates, SMC, control flow flattening, movfuscator, VM protectors (VMProtect/Themida/Code Virtualizer), string encryption, import hiding, and anti-disassembly tricks. Base models often conflate packing with obfuscation and miss the distinction between static and dynamic deobfuscation strategies. |
| 4 | |
| 5 | ## 0. RELATED ROUTING |
| 6 | |
| 7 | - [anti-debugging-techniques](../anti-debugging-techniques/SKILL.md) when the obfuscated binary also has anti-debug layers |
| 8 | - [symbolic-execution-tools](../symbolic-execution-tools/SKILL.md) when using angr/Z3 for automated deobfuscation |
| 9 | - [vm-and-bytecode-reverse](../vm-and-bytecode-reverse/SKILL.md) for deep VM protector bytecode analysis |
| 10 | |
| 11 | ### Quick identification picks |
| 12 | |
| 13 | | Symptom in IDA/Ghidra | Likely Obfuscation | Start With | |
| 14 | |---|---|---| |
| 15 | | Flat CFG, single giant switch | Control flow flattening | Symbolic execution to recover CFG | |
| 16 | | Only `mov` instructions | movfuscator | demovfuscation / trace-based lifting | |
| 17 | | pushad/pushfd → VM entry | VM protector | Handler table extraction | |
| 18 | | XOR loop before code execution | SMC / string encryption | Dynamic analysis, breakpoint after decode | |
| 19 | | Impossible conditions (opaque predicates) | Junk code insertion | Pattern-based removal | |
| 20 | | All strings unreadable | String encryption | Hook decryption routine, or emulate | |
| 21 | | No imports in IAT | Import hiding | Trace GetProcAddress / hash resolution | |
| 22 | |
| 23 | --- |
| 24 | |
| 25 | ## 1. JUNK CODE & OPAQUE PREDICATES |
| 26 | |
| 27 | ### 1.1 Junk Code Insertion |
| 28 | |
| 29 | Dead code that never affects program output, added to increase analysis time. |
| 30 | |
| 31 | **Identification**: |
| 32 | - Instructions that write to registers/memory never read afterward |
| 33 | - Function calls whose return values are discarded and have no side effects |
| 34 | - Loops with invariant bounds that compute unused results |
| 35 | |
| 36 | **Removal strategy**: |
| 37 | 1. Compute def-use chains (IDA/Ghidra data flow analysis) |
| 38 | 2. Mark instructions with no downstream use as dead |
| 39 | 3. Verify removal doesn't change program behavior (trace comparison) |
| 40 | |
| 41 | ### 1.2 Opaque Predicates |
| 42 | |
| 43 | Conditional branches where the condition is always true or always false, but this is non-obvious. |
| 44 | |
| 45 | | Type | Example | Always Evaluates To | |
| 46 | |---|---|---| |
| 47 | | Arithmetic | `x² ≥ 0` | True | |
| 48 | | Number theory | `x*(x+1) % 2 == 0` | True (product of consecutive ints) | |
| 49 | | Pointer-based | `ptr == ptr` after aliasing | True | |
| 50 | | Hash-based | `CRC32(constant) == known_value` | True | |
| 51 | |
| 52 | **Deobfuscation**: |
| 53 | - Abstract interpretation: prove the condition is constant |
| 54 | - Symbolic execution: Z3 proves `∀x: predicate(x) = True` |
| 55 | - Pattern matching: recognize known opaque predicate families |
| 56 | - Dynamic: trace and observe the branch is never taken / always taken |
| 57 | |
| 58 | ```python |
| 59 | import z3 |
| 60 | x = z3.BitVec('x', 32) |
| 61 | s = z3.Solver() |
| 62 | s.add(x * (x + 1) % 2 != 0) |
| 63 | print(s.check()) # unsat → always true |
| 64 | ``` |
| 65 | |
| 66 | --- |
| 67 | |
| 68 | ## 2. SELF-MODIFYING CODE (SMC) |
| 69 | |
| 70 | Runtime code patching: encrypted code is decrypted just before execution. |
| 71 | |
| 72 | ### 2.1 XOR Decryption Loop (Most Common) |
| 73 | |
| 74 | ```asm |
| 75 | lea esi, [encrypted_code] |
| 76 | mov ecx, code_length |
| 77 | mov al, xor_key |
| 78 | decrypt_loop: |
| 79 | xor byte [esi], al |
| 80 | inc esi |
| 81 | loop decrypt_loop |
| 82 | jmp encrypted_code ; now decrypted |
| 83 | ``` |
| 84 | |
| 85 | ### 2.2 Analysis Strategy |
| 86 | |
| 87 | ``` |
| 88 | 1. Identify the decryption routine (look for XOR/ADD/SUB in loops writing to .text) |
| 89 | 2. Set breakpoint AFTER the loop completes |
| 90 | 3. At breakpoint: dump the decrypted memory region |
| 91 | 4. Re-analyze the dumped code in IDA/Ghidra |
| 92 | 5. For multi-layer: repeat for each decryption stage |
| 93 | ``` |
| 94 | |
| 95 | ### 2.3 Automated Unpacking via Emulation |
| 96 | |
| 97 | ```python |
| 98 | from unicorn import * |
| 99 | from unicorn.x86_const import * |
| 100 | |
| 101 | mu = Uc(UC_ARCH_X86, UC_MODE_32) |
| 102 | mu.mem_map(0x400000, 0x10000) |
| 103 | mu.mem_write(0x400000, binary_code) |
| 104 | mu.emu_start(decrypt_entry, decrypt_end) |
| 105 | decrypted = mu.mem_read(code_start, code_length) |
| 106 | ``` |
| 107 | |
| 108 | --- |
| 109 | |
| 110 | ## 3. CONTROL FLOW FLATTENING (CFF) |
| 111 | |
| 112 | ### 3.1 Structure |
| 113 | |
| 114 | Original sequential blocks are transformed into a dispatcher loop: |
| 115 | |
| 116 | ``` |
| 117 | Original: A → B → C → D |
| 118 | |
| 119 | Flattened: ┌──────────────────┐ |
| 120 | │ dispatcher │ |
| 121 | │ switch(state) │◄─────┐ |
| 122 | ├──────────────────┤ │ |
| 123 | │ case 1: block A │──────┤ |
| 124 | │ case 2: block B │──────┤ |
| 125 | │ case 3: block C │──────┤ |
| 126 | │ case 4: block D │──────┘ |
| 127 | └──────────────────┘ |
| 128 | ``` |
| 129 | |
| 130 | Each block sets `state = next_state` before jumping back to the dispatcher. |
| 131 | |
| 132 | ### 3.2 Recovery Techniques |
| 133 | |
| 134 | | Technique | Tool | Effectiveness | |
| 135 | |---|---|---| |
| 136 | | Symbolic execution | angr, Triton, miasm | High — traces all state transitions | |
| 137 | | Trace-based |