# Kong Kong is an AI-powered reverse engineering tool that automates the analysis of stripped and obfuscated binaries using LLM orchestration. It integrates directly with Ghidra's analysis engine via PyGhidra to recover function names, types, parameters, and struct definitions from binaries that have lost all their symbolic information. Kong's five-phase pipeline—triage, analysis, cleanup, synthesis, and export—processes functions in call-graph order so that each function benefits from its callees already being named. The tool supports multiple LLM providers (Anthropic Claude and OpenAI GPT-4o) and includes a first-of-its-kind agentic deobfuscation pipeline that can identify and remove common obfuscation techniques like control flow flattening, bogus control flow, instruction substitution, string encryption, and VM protection. Kong also provides built-in evaluation metrics to score analysis output against ground-truth source code, tracking both symbol accuracy and type accuracy. ## CLI Commands ### Analyze a Binary The main entry point for analyzing a stripped binary. Runs the full autonomous pipeline including function triage, LLM-guided analysis, semantic synthesis, and export. ```bash # Basic analysis with default provider kong analyze ./stripped_binary # Specify output directory and provider kong analyze ./binary --output ./results --provider anthropic # Use specific model and headless mode (for CI/Docker) kong analyze ./binary --provider openai --model gpt-4o-mini --headless # Full options kong analyze ./malware_sample \ --output ./kong_output_malware \ --format source \ --format json \ --provider anthropic \ --model claude-opus-4-6 \ --ghidra-dir /opt/ghidra ``` ### Show Binary Information Display metadata and function statistics about a binary without running full analysis. ```bash # Get binary info kong info ./binary # Output: # Binary: sample.exe # Path: /path/to/sample.exe # Arch: x86 # Format: PE # Endianness: little # Word Size: 32-bit # Compiler: visualstudio:unknown # Functions: 847 # trivial: 234 # small: 312 # medium: 201 # large: 100 ``` ### Setup Wizard Interactive configuration wizard to set up LLM providers and verify Ghidra installation. ```bash # Run setup wizard kong setup # The wizard will: # 1. Ask which LLM providers to use (Anthropic, OpenAI, or both) # 2. Check for API keys in environment variables # 3. Set a default provider # 4. Verify Ghidra installation ``` ### Evaluate Analysis Results Score Kong's analysis output against ground-truth source code to measure accuracy. ```bash # Score analysis against original source kong eval ./kong_output/analysis.json ./original_source.c # Output: # Binary: sample # Functions: 45 analyzed / 50 in source # Symbol Accuracy: 87.2% # Type Accuracy: 72.5% # # Per-Function Scores: # OK parse_http_header -> parse_http_header sym=1.00 type=0.85 # ~~ process_data -> handle_data sym=0.67 type=0.50 # NO FUN_00401a30 -> init_context sym=0.00 type=0.00 ``` ## Python API ### GhidraClient In-process Ghidra client that runs the analysis engine via PyGhidra/JPype with direct access to the program database. ```python from kong.ghidra.client import GhidraClient # Open a binary for analysis with GhidraClient("/path/to/binary", install_dir="/opt/ghidra") as client: # Get binary metadata info = client.get_binary_info() print(f"Architecture: {info.arch}") print(f"Format: {info.format}") print(f"Compiler: {info.compiler}") # List all functions functions = client.list_functions() for func in functions[:10]: print(f"{func.address_hex}: {func.name} ({func.size} bytes)") # Get decompiled C source for a function decomp = client.get_decompilation(functions[0].address) print(decomp) # Get detailed function info with parameters func_info = client.get_function_info(0x00401000) print(f"Name: {func_info.name}") print(f"Return type: {func_info.return_type}") print(f"Calling convention: {func_info.calling_convention}") for param in func_info.params: print(f" Param {param.ordinal}: {param.data_type} {param.name}") ``` ### Cross-References and Call Graph Query cross-references and call relationships between functions. ```python from kong.ghidra.client import GhidraClient with GhidraClient("/path/to/binary") as client: func_addr = 0x00401000 # Get all cross-references TO this address xrefs_to = client.get_xrefs_to(func_addr) for xref in xrefs_to: print(f"{xref.from_hex} -> {xref.to_hex} ({xref.ref_type})") # Get all cross-references FROM this address xrefs_from = client.get_xrefs_from(func_addr) # Get functions that call this function callers = client.get_callers(func_addr) print(f"Called by: {[hex(a) for a in callers]}") # Get functions called by this function callees = client.get_callees(func_addr) print(f"Calls: {[hex(a) for a in callees]}") # Get all defined strings in the binary strings = client.get_strings() for s in strings[:5]: print(f"{s.address_hex}: \"{s.value}\" (referenced by {len(s.xref_addrs)} functions)") ``` ### Modifying the Ghidra Database Rename functions, set signatures, add comments, and create struct types. ```python from kong.ghidra.client import GhidraClient from kong.ghidra.types import StructDefinition, StructField with GhidraClient("/path/to/binary") as client: # Rename a function client.rename_function(0x00401000, "parse_http_header") # Set a function's full signature client.set_function_signature( 0x00401000, "int parse_http_header(char *buffer, int length, http_header_t *out)" ) # Add a comment to a function client.add_comment( 0x00401000, "Parses HTTP headers from raw buffer into structured format", comment_type="plate" # Options: plate, pre, post, eol, repeatable ) # Create a custom struct type http_header = StructDefinition( name="http_header_t", size=32, fields=[ StructField(name="method", data_type="char *", offset=0, size=8), StructField(name="path", data_type="char *", offset=8, size=8), StructField(name="content_length", data_type="int", offset=16, size=4), StructField(name="flags", data_type="uint32_t", offset=20, size=4), ] ) client.create_struct(http_header) # Apply struct type to a function parameter client.apply_type_to_param(0x00401000, param_ordinal=2, type_name="http_header_t") # List all custom types created by Kong custom_types = client.list_custom_types() for t in custom_types: print(f"struct {t.name} ({t.size} bytes, {t.field_count} fields)") ``` ### Control Flow Graph Analysis Extract basic blocks and control flow graphs for advanced analysis. ```python from kong.ghidra.client import GhidraClient with GhidraClient("/path/to/binary") as client: func_addr = 0x00401000 # Get basic blocks for a function blocks = client.get_basic_blocks(func_addr) for block in blocks: print(f"Block {block.start_hex} - {block.end_hex}") for instr in block.instructions: print(f" {instr}") # Get complete control flow graph cfg = client.get_control_flow_graph(func_addr) print(f"Function has {cfg.block_count} basic blocks") for block in cfg.blocks: succs = cfg.successors(block.start_addr) preds = cfg.predecessors(block.start_addr) print(f"{block.start_hex}: {len(preds)} predecessors, {len(succs)} successors") for edge in cfg.edges: print(f"{hex(edge.from_addr)} --{edge.edge_type.value}--> {hex(edge.to_addr)}") # Get high-level pcode operations pcode_ops = client.get_pcode_ops(func_addr) for op in pcode_ops[:10]: print(f"{hex(op.address)}: {op.mnemonic} {op.inputs} -> {op.output}") ``` ### Supervisor Pipeline Run the full analysis pipeline programmatically with event callbacks. ```python from pathlib import Path from kong.config import KongConfig, GhidraConfig, LLMConfig, OutputConfig, LLMProvider from kong.ghidra.client import GhidraClient from kong.agent.supervisor import Supervisor from kong.agent.events import Event, EventType from kong.llm.client import AnthropicClient # Configure the analysis config = KongConfig( ghidra=GhidraConfig(install_dir="/opt/ghidra"), llm=LLMConfig(provider=LLMProvider.ANTHROPIC, model="claude-opus-4-6"), output=OutputConfig(directory=Path("./output"), formats=["source", "json"]), headless=True, ) # Open binary and create LLM client client = GhidraClient("/path/to/binary", install_dir=config.ghidra.install_dir) client.open() llm_client = AnthropicClient(model="claude-opus-4-6") # Create supervisor with event handler supervisor = Supervisor(client, config, llm_client=llm_client) def handle_event(event: Event): if event.type == EventType.FUNCTION_COMPLETE: print(f"Analyzed: {event.data['original_name']} -> {event.data['name']}") elif event.type == EventType.PHASE_COMPLETE: print(f"Phase complete: {event.phase.value}") elif event.type == EventType.DEOBFUSCATION_DETECTED: print(f"Obfuscation detected: {event.data['techniques']}") supervisor.on_event(handle_event) # Run the full pipeline results = supervisor.run() # Access results print(f"Analyzed {supervisor.stats.named}/{supervisor.stats.total_functions} functions") print(f"Total cost: ${llm_client.total_cost_usd:.4f}") for addr, result in results.items(): if result.name and not result.skipped: print(f"{hex(addr)}: {result.original_name} -> {result.name} ({result.confidence}%)") client.close() ``` ### AnthropicClient LLM client for function analysis using Claude models. ```python from kong.llm.client import AnthropicClient from kong.agent.analyzer import Analyzer from kong.ghidra.client import GhidraClient # Initialize client llm = AnthropicClient( model="claude-opus-4-6", max_tokens=2048, api_key="sk-ant-..." # Optional, defaults to ANTHROPIC_API_KEY env var ) # Analyze a single function prompt = """ Binary: x86-64 ELF (gcc) ## Target Function: FUN_00401234 (0x00401234) Size: 156 bytes ### Decompilation ```c void FUN_00401234(char *param_1, int param_2) { int local_10 = 0; while (local_10 < param_2) { param_1[local_10] = param_1[local_10] ^ 0x42; local_10 = local_10 + 1; } return; } ``` """ response = llm.analyze_function(prompt) print(f"Name: {response.name}") # e.g., "xor_decrypt_buffer" print(f"Signature: {response.signature}") # e.g., "void xor_decrypt_buffer(char *data, int len)" print(f"Confidence: {response.confidence}%") print(f"Classification: {response.classification}") print(f"Reasoning: {response.reasoning}") # Check token usage and cost print(f"Input tokens: {response.input_tokens}") print(f"Output tokens: {response.output_tokens}") print(f"Total cost: ${llm.total_cost_usd:.4f}") ``` ### Deobfuscator Classify and remove obfuscation techniques from decompiled code. ```python from kong.agent.deobfuscator import ( classify_obfuscation, ObfuscationType, Deobfuscator, load_patterns ) from kong.ghidra.client import GhidraClient from kong.llm.client import AnthropicClient # Classify obfuscation in decompiled code decompiled = """ void FUN_00401000(void) { int state = 0x12345678; while(1) { switch(state) { case 0x12345678: /* ... */ state = 0xABCDEF01; break; case 0xABCDEF01: /* ... */ state = 0x87654321; break; case 0x87654321: /* ... */ return; case 0xDEADBEEF: /* ... */ state = 0x12345678; break; } } } """ techniques = classify_obfuscation(decompiled) for tech in techniques: print(f"Detected: {tech.value}") # Output: Detected: cff (control flow flattening) # Load pattern documentation for detected techniques pattern_docs = load_patterns(techniques) # Run full deobfuscation with LLM + symbolic tools with GhidraClient("/path/to/obfuscated_binary") as client: llm = AnthropicClient() deobfuscator = Deobfuscator(client, llm) # Build analysis context (normally done by Analyzer) from kong.agent.analyzer import AnalysisContext from kong.ghidra.types import BinaryInfo, FunctionInfo context = AnalysisContext( function=FunctionInfo(address=0x00401000, name="FUN_00401000", size=500), decompilation=decompiled, binary_info=BinaryInfo(arch="x86", format="ELF", endianness="little", word_size=4), ) # Deobfuscate returns (LLMResponse, tool_call_count) response, tool_calls = deobfuscator.deobfuscate(context, techniques) print(f"Recovered name: {response.name}") print(f"Tool calls used: {tool_calls}") ``` ### Symbolic Analysis Tools Low-level symbolic analysis tools for expression simplification and dead code elimination. ```python from kong.symbolic.simplifier import simplify_expression from kong.symbolic.dead_code import eliminate_dead_code, ResolvedPredicate, Resolution # Simplify expressions and detect opaque predicates result = simplify_expression("(x * (x + 1)) % 2 == 0", bit_width=32) print(f"Original: {result.original}") print(f"Simplified: {result.simplified}") print(f"Is opaque predicate: {result.is_opaque}") # True - always evaluates to true print(f"Predicate kind: {result.predicate_kind.value}") # "always_true" # Another example - always false predicate result2 = simplify_expression("(x ^ x) != 0", bit_width=32) print(f"Is opaque: {result2.is_opaque}, kind: {result2.predicate_kind.value}") # Output: Is opaque: True, kind: always_false # Eliminate dead code based on resolved predicates obfuscated_code = """ void func(int x) { if ((x * (x + 1)) % 2 == 0) { real_code(); } else { fake_dead_code(); } if ((x ^ x) != 0) { more_dead_code(); } } """ predicates = [ ResolvedPredicate(expression="(x * (x + 1)) % 2 == 0", resolution=Resolution.ALWAYS_TRUE), ResolvedPredicate(expression="(x ^ x) != 0", resolution=Resolution.ALWAYS_FALSE), ] cleaned_code = eliminate_dead_code(obfuscated_code, predicates) print(cleaned_code) # Output: cleaned code with dead branches removed ``` ### Evaluation Metrics Score analysis quality against ground truth source code. ```python from pathlib import Path from kong.evals.harness import score, extract_ground_truth, load_analysis from kong.evals.metrics import symbol_accuracy, type_accuracy, match_functions # Extract ground truth from original source ground_truth = extract_ground_truth(Path("./original_source.c")) print(f"Found {len(ground_truth.functions)} functions in source") for func in ground_truth.functions[:5]: print(f" {func['name']}: {func['signature']}") # Load Kong's analysis results predicted = load_analysis(Path("./kong_output/analysis.json")) # Compute full scorecard scorecard = score( analysis_path=Path("./kong_output/analysis.json"), source_path=Path("./original_source.c") ) print(f"Symbol Accuracy: {scorecard.symbol_accuracy:.1%}") print(f"Type Accuracy: {scorecard.type_accuracy:.1%}") print(f"Functions analyzed: {scorecard.functions_analyzed}/{scorecard.total_functions}") print(f"LLM calls: {scorecard.llm_calls}") print(f"Duration: {scorecard.duration_seconds:.1f}s") print(f"Cost: ${scorecard.cost_usd:.4f}") # Per-function scores for pf in scorecard.per_function: print(f"{pf['predicted_name']:30s} -> {pf['truth_name']:20s} " f"sym={pf['symbol_accuracy']:.2f} type={pf['type_accuracy']:.2f}") # Compute individual metrics sym_score = symbol_accuracy("parse_http_header", "parse_http_headers") print(f"Symbol similarity: {sym_score:.2f}") # Uses word-based Jaccard type_score = type_accuracy( "int parse_http_header(char *buf, int len)", "int parse_http_headers(const char *buffer, size_t length)" ) print(f"Type similarity: {type_score:.2f}") # Signature component scoring ``` ### Configuration Management Configure Kong programmatically using dataclasses. ```python from pathlib import Path from kong.config import KongConfig, GhidraConfig, LLMConfig, OutputConfig, LLMProvider from kong.db import save_setup, get_default_provider, is_setup_complete # Create configuration config = KongConfig( ghidra=GhidraConfig( install_dir="/opt/ghidra", project_dir="/tmp/kong_ghidra", project_name="my_analysis" ), llm=LLMConfig( provider=LLMProvider.ANTHROPIC, model="claude-opus-4-6", api_key=None # Uses ANTHROPIC_API_KEY env var ), output=OutputConfig( directory=Path("./kong_output"), formats=["source", "json", "ghidra"] ), headless=True, verbose=False ) # Save provider setup to persistent config (~/.config/kong/) save_setup( enabled=[LLMProvider.ANTHROPIC, LLMProvider.OPENAI], default=LLMProvider.ANTHROPIC ) # Check setup status if is_setup_complete(): default = get_default_provider() print(f"Default provider: {default.display_name}") ``` ## Output Format ### analysis.json Structure Kong exports structured JSON with binary metadata, statistics, and function analysis results. ```json { "binary": { "name": "sample.exe", "path": "/path/to/sample.exe", "arch": "x86", "format": "PE", "endianness": "little", "word_size": 4, "compiler": "visualstudio:unknown" }, "stats": { "total_functions": 847, "analyzed": 613, "named": 598, "renamed": 412, "confirmed": 186, "high_confidence": 389, "medium_confidence": 156, "low_confidence": 53, "skipped": 234, "errors": 15, "llm_calls": 42, "duration_seconds": 324.7, "cost_usd": 2.4532 }, "functions": [ { "address": "0x00401000", "original_name": "FUN_00401000", "name": "parse_http_header", "signature": "int parse_http_header(char *buffer, int length, http_header_t *out)", "confidence": 92, "classification": "parser", "comments": "Parses HTTP headers from raw buffer", "reasoning": "String references to 'Content-Length', 'GET', 'POST' indicate HTTP parsing", "obfuscation_techniques": [] } ] } ``` ## Summary Kong is designed for security researchers, malware analysts, and reverse engineers who need to recover meaningful context from stripped binaries at scale. The tool excels at automated triage of large binaries, where manually renaming hundreds of functions would be impractical. Its call-graph-ordered analysis ensures that each function's context window includes already-recovered names from its dependencies, dramatically improving naming accuracy. Integration patterns include using Kong as a preprocessing step before manual analysis in Ghidra (the recovered names and types are written directly back to the program database), incorporating it into automated malware analysis pipelines, or using the evaluation framework to benchmark different LLM models for reverse engineering tasks. The modular Python API allows embedding Kong's capabilities into larger security toolchains, while the CLI provides a simple interface for one-off analysis tasks.