Kong: Agentic Reverse Engineering Framework

repository·main·Indexed 21 days ago

https://github.com/amruth-sn/kong

Kong is an LLM-orchestrated framework for automated binary reverse engineering that integrates with Ghidra to recover symbols, types, and structures from stripped or obfuscated binaries. It features a five-phase pipeline (Triage, Analysis, Cleanup, Synthesis, and Export) and supports architectures including x86, x86-64, ARM, AArch64, MIPS, and PowerPC for languages such as C, C++, Go, and Rust. The framework includes kong-ml for machine learning-based function boundary detection using XdaDetector.

Tokens
19.3K
Snippets
62
Records
87
Agent score
77%

What's inside Kong

  1. What is Kong: The Agentic Reverse Engineer

    main

    Kong is an LLM-orchestrated framework designed for automated binary reverse engineering. It automates the mechanical layers of analysis by integrating with Ghidra to perform a full analysis pipeline on stripped or obfuscated binaries.

    Key capabilities include:

    • Function Triage: Classifying functions and building call-graph context.
    • Context-Aware Decompilation: Using Ghidra's program database (call graphs, cross-references, string references) to build rich context windows for LLMs.
    • Bottom-Up Analysis: Analyzing functions in dependency order (leaf functions first) so that callers benefit from already-resolved names and types.
    • Agentic Deobfuscation: Identifying and removing obfuscation techniques like control flow flattening or string encryption.
    • Semantic Synthesis: Unifying naming conventions and synthesizing struct definitions across the entire binary.
    • Ghidra Writeback: Automatically applying recovered names, types, and signatures back into the Ghidra program database.
  2. Identify VM-Based Protection patterns

    main

    VM-based protection (like VMProtect or Themida) replaces native code with custom bytecode interpreted by an embedded virtual machine. You can identify these patterns by looking for a dispatcher loop that fetches, decodes, and executes bytecodes.

    Key indicators include:

    • A large switch statement or function pointer table inside a loop.
    • A "virtual instruction pointer" (VIP) that increments after each dispatch.
    • A "virtual stack pointer" (VSP) or register file used for operands.
    • Handler cases that are small (1-5 operations) and uniform in structure.
    • Bytecode data stored in a separate section or embedded inline.

    Distinguishing VM from Control Flow Flattening (CFF):

    • CFF: The state variable is reassigned to large hex constants, and cases contain actual program logic.
    • VM: The dispatch variable is loaded from a data pointer (e.g., *vip++), and cases contain generic operations (push, pop, add) while the real logic resides in the bytecode data.
    while (1) {
        opcode = *vip++;
        switch (opcode) {
            case 0x01: /* push */ ...
            case 0x02: /* pop */ ...
            case 0x03: /* add */ ...
            case 0x10: /* jmp */ ...
            case 0xFF: return;
        }
    }
  3. Identify Instruction Substitution patterns

    main

    Instruction Substitution is an obfuscation technique where simple arithmetic and logical operations are replaced with complex, mathematically equivalent expressions. This preserves the function's logic while making it difficult to read.

    Common patterns to look for include:

    • Unnecessarily complex arithmetic where a simpler equivalent exists.
    • Multiple operations used where one would suffice.
    • Deeply nested arithmetic where substituted expressions contain further substituted sub-expressions.
  4. Future VM Recovery Strategy

    main

    Kong's roadmap includes dedicated VM-lifting infrastructure to move beyond detection toward full recovery. The planned recovery strategy involves:

    1. Identifying the VM dispatcher and handler table.
    2. Mapping each handler to virtual opcode semantics (e.g., push, pop, add, sub, jmp, call).
    3. Extracting the bytecode stream from the embedded data.
    4. Disassembling the bytecodes using the identified handler semantics.
    5. Lifting the virtual instruction trace back to C-like pseudocode.
    6. Naming and typing the recovered function.
  5. Identify string encryption patterns

    main

    String encryption involves encrypting string literals at compile time and decrypting them at runtime just before use. Instead of plain text, you will see byte arrays or calls to decryption routines.

    Common patterns to look for include:

    • Byte arrays: Initialized with non-ASCII or non-printable data used as arguments where strings are expected.
    • XOR loops: Iterating over a local or global byte array before passing it to functions like printf, strcmp, or puts.
    • Helper functions: Small functions that take encrypted data and a key to return a char *.
    • Specific algorithms:
      • Single-byte XOR: buf[i] ^= key
      • Multi-byte XOR: buf[i] ^= key[i % key_len]
      • RC4 decryption: KSA + PRGA applied to a byte array
      • Stack strings: Characters pushed one-by-one onto the stack.
      • Base64 decode followed by XOR.
  6. How to identify Control Flow Flattening (CFF)

    main

    Control Flow Flattening (CFF) is an obfuscation technique where a function's natural control flow (if/else, loops) is replaced by a single dispatcher loop. You can identify CFF by looking for these patterns:

    • Dispatcher Loop: A single while(1), while(true), or for(;;) loop wrapping the entire function body.
    • Switch Statement: A large switch statement inside that loop (typically containing 5 or more cases).
    • State Variable: A local integer variable that is reassigned at the end of every switch case to determine the next block to execute.
    • Large Hex Constants: State values are often large, non-sequential hex constants (e.g., 0x3a2b, 0x9e22).
    • Lack of Structure: The function lacks natural if/else or loop structures outside of the central switch block.
  7. Understand Kong analysis output

    main

    When an analysis completes, results are written to a directory named kong_output_{binary_name}/.

    This directory contains:

    • analysis.json: Contains all recovered function names, types, and parameters.
    • events.log: A trace of the pipeline execution.
  8. How the Kong analysis pipeline works

    main

    Kong operates through a five-phase pipeline orchestrated by a supervisor. The process moves from initial triage to final export:

    1. Triage: Enumerates functions, classifies them by size, builds the call graph, detects the source language, and performs signature matching for known standard libraries/crypto functions.
    2. Analysis: Processes functions bottom-up from the call graph. For each function, Kong builds a context window (decompilation, XREFs, string refs, and callee signatures), normalizes the output, and uses an LLM for recovery. If obfuscation is detected, an agentic deobfuscation pass is triggered.
    3. Cleanup: Unifies struct types and retries failed signature applications.
    4. Synthesis: Performs a global pass to unify naming conventions and refine names based on the broader binary context.
    5. Export: Writes the final analysis.json and performs the Ghidra writeback to update the program database.
  9. Identify Bogus Control Flow (BCF) patterns

    main

    Bogus Control Flow (BCF) obfuscation injects fake branches into functions using opaque predicates—conditions that always evaluate to the same value regardless of input. The actual logic remains unchanged, but the control flow graph is artificially expanded with 'dead' branches containing junk code or modified copies of real code.

    Common Opaque Predicate Patterns

    Look for mathematical identities or bitwise operations that are constant:

    • (x * (x + 1)) % 2 == 0 (Always true)
    • (x * x) % 2 == x % 2 (Always true)
    • (x | ~x) == -1 (Always true)
    • (x ^ x) != 0 (Always false)
    • (x & ~x) != 0 (Always false)
    • Conditions involving global variables that are never modified.

    Identifying Dead Branches

    Dead branches often exhibit these characteristics:

    • Unreachable code that still references valid memory addresses.
    • Cloned/modified copies of nearby legitimate code.
    • Random arithmetic that produces no visible side effects.
  10. Quick Start with Kong

    main

    Follow these steps to perform your first binary analysis:

    1. Install Kong: uv pip install kong-re
    2. Set API Keys: Export your ANTHROPIC_API_KEY or OPENAI_API_KEY as environment variables.
    3. Run Setup: Execute kong setup to configure your default LLM provider. Kong will attempt to auto-detect Ghidra and JDK installations.
    4. Analyze: Run kong analyze ./path/to/binary to start the agentic reverse engineering pipeline.
    # 1. Install Kong
    uv pip install kong-re
    
    # 2. Set your API key(s)
    export ANTHROPIC_API_KEY="sk-ant-..."
    # and/or
    export OPENAI_API_KEY="sk-..."
    
    # 3. Run the setup wizard (first time only)
    kong setup
    
    # 4. Analyze a binary
    kong analyze ./path/to/stripped_binary
  11. Recover structured code from Control Flow Flattening (CFF)

    main

    To de-obfuscate a function protected by Control Flow Flattening, follow this recovery strategy:

    1. Identify the state variable: Find the local integer reassigned in every switch case.
    2. Extract transitions: Use the trace_state_machine tool to automatically extract the state transition graph.
    3. Trace state transitions: For every case, record the mapping of (current_state → next_state) and any logic that determines the next state.
    4. Build a state transition graph: Map out all states and their connections.
    5. Collapse linear chains: If states transition unconditionally from one to another, combine them into sequential statements.
    6. Identify conditional branches: Cases where the next state depends on a condition should be converted into if/else blocks.
    7. Identify loops: Any cycles found in the state graph should be reconstructed as while or for loops.
    8. Reconstruct structured code: Map the processed graph back into standard high-level programming constructs.
    9. Finalize: Name and type the function based on the newly recovered logic.
  12. Recover code from Bogus Control Flow (BCF)

    main

    To recover the original logic from a function obfuscated with Bogus Control Flow, follow this recovery strategy:

    1. Scan all branch conditions within the target function.
    2. Evaluate predicates: Use the simplify_expression tool on each condition to determine if it is an opaque predicate.
    3. Prune branches based on the result:
      • If the predicate is always true: Keep the then branch and discard the else branch.
      • If the predicate is always false: Keep the else branch and discard the then branch.
    4. Clean up: Use the eliminate_dead_code tool to remove the discarded unreachable branches from the decompilation.
    5. Finalize: Re-analyze the simplified code to expose the real logic, then proceed to name and type the functions.