# Manual Deobfuscation Techniques ## Overview When automated tools fail, manual deobfuscation is necessary. This guide covers techniques for recovering the original functionality from obfuscated malware code. ## String Decryption ### Identifying Encrypted Strings Look for these patterns in disassembly: 1. **XOR loops**: A loop that reads from one buffer, XORs with a key, writes to another 2. **Array of encrypted data**: Large byte arrays followed by a decryption routine 3. **Call-before-use pattern**: A function called just before string use (e.g., before passing to `CreateFileA`, `InternetOpenUrlA`) 4. **Wrapper functions**: Functions that take an index and return a decrypted string ### Common Decryption Patterns **Single-byte XOR:** ``` ; Typical pattern mov ecx, string_length lea esi, encrypted_data xor_loop: xor byte [esi], 0x5A ; XOR key inc esi loop xor_loop ``` **Multi-byte XOR (repeating key):** ``` ; Key stored in array or hardcoded mov ecx, string_length xor edx, edx lea esi, encrypted_data lea edi, key_array xor_loop: mov al, [edi + edx] xor [esi], al inc esi inc edx cmp edx, key_length jb no_reset xor edx, edx no_reset: loop xor_loop ``` **Stack strings (anti-disassembly):** ``` ; Characters pushed individually to the stack mov [ebp-14h], 'c' mov [ebp-13h], 'm' mov [ebp-12h], 'd' mov [ebp-11h], '.' mov [ebp-10h], 'e' mov [ebp-0Fh], 'x' mov [ebp-0Eh], 'e' mov [ebp-0Dh], 0 ; Result: "cmd.exe" on the stack ``` **API hashing:** ``` ; Hash computed at runtime, compared against known hash push hash_value call resolve_api ; Custom function that walks PEB/export tables call eax ; Call resolved API ``` ### Decryption Approaches 1. **Static**: Identify algorithm, extract key and data, decrypt offline 2. **Emulation**: Use Unicorn Engine to emulate the decryption function 3. **Dynamic**: Set breakpoint after decryption, read plaintext from memory 4. **Scripting**: Write IDA/Ghidra script to decrypt all instances in-place **Ghidra script example (Java):** ```java // Decrypt XOR-encoded strings in Ghidra byte xorKey = 0x5A; Address start = toAddr(0x00401000); // encrypted data start int length = 100; for (int i = 0; i < length; i++) { Address addr = start.add(i); byte b = getByte(addr); setByte(addr, (byte)(b ^ xorKey)); } ``` ## Control Flow Recovery ### Control Flow Flattening **What it looks like:** - A dispatcher block with a switch/state variable - Multiple case blocks that each set the next state before jumping back - The original sequential flow is converted to a state machine **Recovery technique:** 1. Identify the dispatcher variable and its location 2. Map each state value to its corresponding code block 3. For each block, note what state it transitions to 4. Reconstruct the original flow: block A -> block B -> block C 5. Replace the dispatcher with direct jumps between blocks **Identification signs:** - Large switch statement or computed jump at the start of a function - Many blocks ending with assignment to the same variable - All blocks jump back to the same dispatcher ### Opaque Predicates **What they are:** Conditional branches where the condition is always true or always false, used to confuse disassemblers and analysts. **Common patterns:** - `x * (x - 1) % 2 == 0` (always true for any integer) - `x^2 >= 0` (always true) - `x^2 + x` is always even - Constants disguised as computations **Removal technique:** 1. Use symbolic execution (angr, Triton) to evaluate predicates 2. Replace opaque predicates with NOP or unconditional JMP 3. In IDA: patch bytes; in Ghidra: use byte patching ### Indirect Jumps and Calls **Pattern:** Jump target computed at runtime ``` mov eax, [ebp+computed_offset] add eax, base_address jmp eax ``` **Resolution:** 1. Trace execution to determine actual targets 2. Use emulation to resolve computed addresses 3. Add cross-references manually in disassembler ## Dead Code Elimination ### Junk Code Insertion **Characteristics:** - Instructions whose results are never used - Sequences that cancel each other (push/pop, add/sub same value) - Calls to empty functions - Complex arithmetic that produces a constant **Examples:** ``` ; Junk: pushes and pops cancel out push eax push ebx mov eax, 12345678h xor eax, eax pop ebx pop eax ; Real code continues here ``` **Removal:** 1. Identify instructions with no side effects on subsequent code 2. Check if register/memory modifications are overwritten before use 3. NOP out or delete dead code blocks 4. Use data flow analysis to automate identification ### Anti-Disassembly Tricks **Jump into middle of instruction:** ``` ; Disassembler sees: jmp $+1; db 0xE8 (looks like CALL) ; Actual execution: jmp $+1; skips the 0xE8 byte eb 01 ; jmp short $+1 e8 xx xx xx xx ; Disassembler thinks this is CALL ; Real instruction starts at the xx bytes ``` **Fix:** Force correct disassembly at the true target address. **Fake conditional jump:** ``` xor eax, eax ; eax = 0 jnz fake_target ; Never taken, but disassembler follows both paths ; Real code here ``` ## API Resolution Reconstruction ### Dynamic API Resolution Malware often resolves API addresses at runtime to avoid listing them in the import table. **PEB Walking (Windows):** ``` ; Access PEB -> PEB_LDR_DATA -> InMemoryOrderModuleList mov eax, fs:[30h] ; PEB mov eax, [eax+0Ch] ; PEB_LDR_DATA mov eax, [eax+14h] ; InMemoryOrderModuleList ; Walk linked list to find kernel32.dll ; Parse export table to find functions by hash ``` **API hashing algorithms:** - **ROR13**: Most common, rotate-right-13 hash of function name - **CRC32**: Standard CRC32 of function name - **DJB2**: Simple multiply-and-add hash - **FNV-1a**: Fowler-Noll-Vo hash variant - **Custom**: Any hash algorithm; identify by hash constants **Reconstruction steps:** 1. Identify the hashing algorithm used 2. Build a lookup table: hash all Windows API function names 3. Match hashes found in the malware to API names 4. Annotate the disassembly with resolved function names 5. Tools: HashDB (IDA plugin), shellcode_hashes (FLARE) ### GetProcAddress Resolution ``` ; Simpler dynamic resolution push offset "CreateFileA" push offset "kernel32.dll" call LoadLibraryA push offset "CreateFileA" push eax call GetProcAddress ; eax now contains CreateFileA address ``` **Reconstruction:** Log all LoadLibrary/GetProcAddress calls during dynamic analysis. ## Automation Tools | Tool | Purpose | Platform | |------|---------|----------| | FLOSS | Automatic string deobfuscation | Cross-platform | | angr | Symbolic execution for deobfuscation | Python | | Triton | Dynamic symbolic execution | Python/C++ | | Unicorn Engine | CPU emulation for decryption | Python/C | | Miasm | Reverse engineering framework | Python | | Binary Ninja HLIL | High-level IL for pattern matching | Cross-platform | | IDAPython scripts | IDA Pro automation | IDA Pro | | Ghidra scripts | Ghidra automation | Ghidra | | de4dot | .NET deobfuscator | .NET | | dnSpy | .NET debugger/decompiler | Windows |