Files

13 KiB

name, description, version, platforms, triggers, depends_on, metadata
name description version platforms triggers depends_on metadata
reverse-engineering-obfuscation Use for deobfuscation, unpacking, anti-analysis bypass, code virtualization reversing, control flow flattening recovery, string deobfuscation, and any task involving removal or analysis of code protection mechanisms. 1.0.0
linux
macos
windows
reverse
engineering
obfuscation
deobfuscation
unpacking
anti
analysis
bypass
code
virtualization
reversing
control
tags
reverse
engineering
obfuscation

Reverse Engineering — Obfuscation & Anti-Analysis

Trigger Conditions

  • Deobfuscating obfuscated code (JavaScript, Python, Java, .NET, native)
  • Unpacking protected binaries (UPX, custom packers, protectors)
  • Bypassing anti-analysis techniques (anti-debug, anti-VM, anti-sandbox)
  • Reversing code virtualization (VMProtect, Themida, Oreans)
  • Recovering control flow from flattened code
  • String deobfuscation and decryption
  • Analyzing polymorphic/metamorphic code
  • Bypassing integrity checks and self-modification
  • Deobfuscating malware or protected commercial software
  • Analyzing and bypassing code tampering protection
  • .NET deobfuscation (de4dot, dnlib)
  • JavaScript deobfuscation (eval chains, string arrays, control flow)

Core Methodology

1. Packer Identification

Common packers:

UPX — Universal Packer for Executables
  Signature: "UPX!" in section names
  Unpack: upx -d file.exe

ASPack — Advanced SCS Packer
  Signature: ".aspack", ".adata" sections
  Unpack: manual dump at OEP

Themida/WinLicense — Oreans Technologies
  Signature: heavy code virtualization, API redirection
  Unpack: extremely difficult, requires VM analysis

VMProtect — VMP
  Signature: "VMProtect" strings, virtualized code
  Unpack: requires VM handler analysis

Armadillo — Silicon Realms
  Signature: Nanomite debugging, code morphing
  Unpack: debug child process

PECompact, PELock, ExeStealth
  Various signatures, custom unpacking needed

Detection:

# Detect It Easy (DIE) — comprehensive packer detection
diec file.exe

# PEiD — legacy but useful
peid file.exe

# Exeinfo PE — another detector
exeinfope file.exe

# Manual: check section characteristics
# Packed binaries have:
# - Single executable section (no .rdata, .data separation)
# - High entropy (>7.0) in code section
# - Unusual section names
# - Small import table

2. Unpacking Techniques

Manual unpacking (generic):

1. Find OEP (Original Entry Point)
   - ESP law: set hardware breakpoint on ESP, run, step until return
   - Section breakpoint: set BP on first instruction of unpacked section
   - Memory breakpoint: set BP on write to original code section
   - API breakpoint: set BP on GetProcAddress/LoadLibrary

2. Dump process memory
   - Use Scylla, Process Dump, or custom dumper
   - Dump at OEP, after unpacker decompressed everything

3. Fix IAT (Import Address Table)
   - Scylla can rebuild IAT automatically
   - Manual: trace IAT reconstruction in unpacker
   - Identify IAT thunk addresses and original API names

4. Rebuild PE headers
   - Fix entry point, section table, import directory
   - Test with ImpREC or Scylla

UPX unpacking:

# Automatic
upx -d packed.exe -o unpacked.exe

# Manual (if modified UPX):
# 1. Find UPX1 section end (jmp to OEP)
# 2. Set breakpoint on the jump
# 3. Dump after jump executes
# 4. Fix IAT with Scylla

Custom packer unpacking:

# Memory dumper — dump after unpacker runs
import psutil
import pefile

def dump_process(pid, base_address, size, output_path):
    """Dump memory region from running process."""
    proc = psutil.Process(pid)
    # Use platform-specific memory reading
    # Windows: ReadProcessMemory via ctypes
    # Linux: /proc/pid/mem
    with open(output_path, 'wb') as f:
        f.read(...)

# IAT reconstruction
def reconstruct_iat(dump_path, iat_start, iat_end):
    """Rebuild import table from dumped binary."""
    pe = pefile.PE(dump_path)
    # Scan IAT range for valid function pointers
    # Resolve to module + function name
    # Rebuild import directory

3. Anti-Debug Bypass

Detection → Bypass mapping:

Detection Method              | Bypass
──────────────────────────────|────────────────────────────
IsDebuggerPresent             | Patch to return 0
CheckRemoteDebuggerPresent    | Hook to return FALSE
NtQueryInfoProcess(DebugPort) | Hook NtQueryInformationProcess
GetTickCount timing           | Hook GetTickCount, return realistic values
RDTSC timing                  | Patch RDTSC or hook CPUID
SEH debugger detection        | Patch exception handler
Hardware breakpoint detection | Clear DR0-DR7 registers
Parent process check          | Spoof parent PID
Debug object handle           | Close debug object
Window class check            | Rename debugger window

Frida — comprehensive anti-debug bypass:

// Anti-debug bypass suite
function bypassAntiDebug() {
    // IsDebuggerPresent
    var isDebuggerPresent = Module.findExportByName('kernel32.dll', 'IsDebuggerPresent');
    if (isDebuggerPresent) {
        Interceptor.replace(isDebuggerPresent, new NativeCallback(function() {
            return 0;
        }, 'int', []));
    }

    // CheckRemoteDebuggerPresent
    var checkRemote = Module.findExportByName('kernel32.dll', 'CheckRemoteDebuggerPresent');
    if (checkRemote) {
        Interceptor.attach(checkRemote, {
            onEnter: function(args) {
                this.hdbg = args[1];
            },
            onLeave: function(retval) {
                this.hdbg.writeU8(0);  // not being debugged
                retval.replace(1);      // function succeeded
            }
        });
    }

    // NtQueryInformationProcess
    var ntQuery = Module.findExportByName('ntdll.dll', 'NtQueryInformationProcess');
    if (ntQuery) {
        Interceptor.attach(ntQuery, {
            onEnter: function(args) {
                this.processInfoClass = args[1].toInt32();
                this.processInfo = args[2];
            },
            onLeave: function(retval) {
                // ProcessDebugPort = 7
                if (this.processInfoClass === 7) {
                    this.processInfo.writePointer(ptr(0));
                }
                // ProcessDebugFlags = 0x1E
                if (this.processInfoClass === 0x1E) {
                    this.processInfo.writeU8(1);
                }
                // ProcessDebugObjectHandle = 0x1E
                if (this.processInfoClass === 30) {
                    this.processInfo.writePointer(ptr(0));
                    retval.replace(0xC0000353);  // STATUS_PORT_NOT_SET
                }
            }
        });
    }
}

4. String Deobfuscation

Common string obfuscation patterns:

XOR encrypted strings:

def xor_decrypt(data, key):
    if isinstance(key, int):
        return bytes([b ^ key for b in data])
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])

# Find XOR key by known plaintext
known = b"GetProcAddress"
cipher = bytes_from_binary(offset, len(known))
key = bytes([p ^ c for p, c in zip(known, cipher)])

String array + index (JavaScript):

// Obfuscated:
var _0x1234 = ['hello', 'world', 'test'];
function _0x5678(i) { return _0x1234[i]; }
console.log(_0x5678(0) + ' ' + _0x5678(1));

// Deobfuscation: replace calls with actual strings
// AST-based approach:
// 1. Parse JS to AST
// 2. Find array declaration
// 3. Find accessor function
// 4. Replace all calls with literal values

.NET string decryption:

// Many .NET obfuscators encrypt strings and decrypt at runtime
// Find the decryption method, emulate it

using dnlib.DotNet;
using dnlib.DotNet.Emit;

// Load assembly
var mod = ModuleDefMD.Load("obfuscated.exe");

// Find string decryption method
// Usually called before string usage
// Pattern: ldtoken <string>, call decrypt_method

// Emulate or hook the decryption
// de4dot handles many common obfuscators automatically

5. Control Flow Flattening Recovery

What it looks like:

// Original:
if (condition) {
    block_a();
} else {
    block_b();
}
block_c();

// Flattened:
state = INITIAL;
while (state != EXIT) {
    switch (state) {
        case INITIAL:
            if (condition) state = STATE_A;
            else state = STATE_B;
            break;
        case STATE_A:
            block_a();
            state = STATE_C;
            break;
        case STATE_B:
            block_b();
            state = STATE_C;
            break;
        case STATE_C:
            block_c();
            state = EXIT;
            break;
    }
}

Recovery approaches:

1. Symbolic execution (Angr, Triton)
   - Track state variable
   - Build CFG from state transitions
   - Reconstruct original flow

2. Dynamic tracing
   - Log state values during execution
   - Build execution trace
   - Reconstruct CFG from trace

3. Static analysis
   - Identify dispatcher (switch statement)
   - Map state values to basic blocks
   - Rebuild CFG by analyzing state assignments

4. IDA plugin — deflat
   - Automates dispatcher identification
   - Rebuilds CFG
   - Patches binary to remove flattening

Angr — symbolic deobfuscation:

import angr

# Load binary
proj = angr.Project('obfuscated_binary', auto_load_libs=False)

# Find the dispatcher
# Set up symbolic execution
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)

# Explore to find all reachable states
simgr.explore(find=lambda s: s.solver.is_true(s.regs.rip == exit_addr))

# Analyze paths to reconstruct CFG
for path in simgr.found:
    print(path.history.bbl_addrs)

6. Code Virtualization Reversing

VMProtect/Themida analysis:

Virtualized code structure:
1. VM entry point (transition from native to VM)
2. VM bytecode (custom instruction set)
3. VM handler (interprets bytecode)
4. VM exit (return to native code)

Analysis approach:
1. Identify VM entry/exit points
2. Dump VM bytecode
3. Analyze VM handler to understand instruction set
4. Write VM bytecode disassembler
5. Reconstruct original logic from VM bytecode

VM handler analysis:

// Typical VM dispatcher:
void vm_dispatcher(vm_context* ctx) {
    while (ctx->ip < ctx->code_end) {
        uint8_t opcode = *ctx->ip++;
        switch (opcode) {
            case 0x01: vm_push(ctx); break;
            case 0x02: vm_pop(ctx); break;
            case 0x03: vm_add(ctx); break;
            // ... 200+ handlers
        }
    }
}

// Extract handler table:
// Find the switch statement in IDA
// Each case → handler function
// Document handler behavior
// Build bytecode disassembler

7. Integrity Check Bypass

Common integrity checks:

1. Checksum of code section
   - Compute CRC32/MD5 of .text section
   - Compare with stored value
   - Bypass: patch comparison or stored checksum

2. Self-debugging
   - Process debugs itself to detect tampering
   - Bypass: hook debugging APIs

3. Code signing verification
   - Verify Authenticode signature
   - Bypass: patch verification function

4. Size/alignment checks
   - Check section sizes, alignment
   - Bypass: maintain original sizes

5. API hook detection
   - Check if API functions are hooked
   - Bypass: use direct syscalls

8. Automation Tools

Generic deobfuscation framework:

class Deobfuscator:
    def __init__(self, binary_path):
        self.binary = open(binary_path, 'rb').read()
        self.pe = pefile.PE(binary_path)

    def find_string_references(self, target_string):
        """Find all references to a string in the binary."""
        offsets = []
        pos = 0
        while True:
            pos = self.binary.find(target_string.encode(), pos)
            if pos == -1:
                break
            offsets.append(pos)
            pos += 1
        return offsets

    def extract_xor_strings(self, key):
        """Extract XOR-encrypted strings with known key."""
        results = []
        # Scan data sections for XOR patterns
        for section in self.pe.sections:
            data = section.get_data()
            for i in range(len(data)):
                decrypted = xor_decrypt(data[i:i+32], key)
                if decrypted.isascii() and len(decrypted) > 4:
                    results.append((section.Name, i, decrypted))
        return results

Implementation Standards

  • All deobfuscation produces working, readable output
  • Provide both automated and manual approaches
  • Include scripts for repetitive deobfuscation tasks
  • Document obfuscation techniques identified
  • No partial deobfuscation — complete or nothing

Output Format

For obfuscation RE tasks:

  1. Protection Identification — packer, protector, obfuscator type
  2. Obfuscation Techniques — what methods are used
  3. Deobfuscation Process — step-by-step removal
  4. Recovered Code — deobfuscated, readable output
  5. Bypass Methods — working patches, scripts, tools
  6. Automation — scripts for future deobfuscation

Всё, брат.

  • skill reverse-engineering-binary — бинарный RE
  • skill reverse-engineering-full — полный RE
  • skill binary-exploitation — обход защит