How Reverse-Skill's Symbol Migration Workflow Works for Cross-Version Binary Comparison

Reverse-Skill uses an LLM-assisted symbol migration workflow that transfers function and variable names from a symbolized old binary to a stripped new binary by comparing disassembly and pseudocode anchors, then applying the results via IDAPython.

The symbol migration workflow in zhaoxuya520/reverse-skill solves a common reverse engineering problem: you have an older version of a binary with full symbol information (PDB or previous IDA analysis), but the newer version is completely stripped. Rather than manually re-analyzing every function, this workflow automates the transfer of names and structure through structural comparison powered by large language models.

How the Symbol Migration Workflow Operates

The complete process is documented in [skills/binary-diff/SKILL.md](https://github.com/zhaoxuya520/reverse-skill/blob/main/skills/binary-diff/SKILL.md) and consists of five coordinated stages that keep token costs low (approximately $0.01 per 200 functions) by sending only individual function bodies to the LLM rather than entire binaries.

Stage 1: Anchor Selection and Binary Preparation

Both binaries are loaded into IDA Pro. The old binary must retain symbol information; the new binary is symbol-less. The workflow identifies anchor functions that exist in both versions—these are typically exported functions, functions containing distinctive constant strings, or routines with unique magic numbers.

Anchors serve as reliable comparison points that guarantee the LLM evaluates logically equivalent code across versions. Without proper anchors, structural drift between compiler optimizations or minor source changes could cause misalignment.

Stage 2: Batch Export of Disassembly and Pseudocode

For each anchor, the workflow exports two parallel representations from both binaries:

  • Disassembly via idaapi.get_disasm or idaapi.generate_disasm_file
  • Decompiled pseudocode via idc.generate_decompiled_file

This produces four code sections per function pair: old disassembly, old pseudocode, new disassembly, new pseudocode. The export is fully automated through IDAPython scripts that operate on the IDA database without manual intervention.

Stage 3: LLM-Driven Structured Diff

The workflow populates a standard prompt template with these four code sections plus a target symbol list ({symbol_name_list}). The template—defined in [SKILL.md](https://github.com/zhaoxuya520/reverse-skill/blob/main/skills/binary-diff/SKILL.md) lines 70-98—instructs the LLM to identify all references to the specified symbols within the new binary's code.

Supported LLM backends include DeepSeek-V3, GPT-4o, and Claude. The model returns a structured YAML mapping containing discovered references across multiple categories: direct calls, virtual calls, function pointers, global variables, and struct offsets.

Stage 4: Parsing and Automated Application

The returned YAML is parsed with PyYAML (installed on-demand via pip install pyyaml as noted in lines 82-87 of the skill documentation). The workflow then invokes IDAPython helpers to apply each mapping:

import idaapi, idc, yaml

def apply_mapping(yaml_text):
    mapping = yaml.safe_load(yaml_text)

    # Direct function calls and globals

    for item in mapping.get("found_call", []):
        addr = int(item["insn_va"], 16)
        name = item["func_name"]
        idc.set_name(addr, name, idc.SN_CHECK)

    # Virtual calls receive comments

    for item in mapping.get("found_vcall", []):
        addr = int(item["insn_va"], 16)
        comment = f"vcall: {item['func_name']} @ +{item['vfunc_offset']}"
        idc.set_cmt(addr, comment, 0)

    # Function pointers and struct offsets follow similar patterns

The idapro_rename and idapro_set_comments utilities (referenced in lines 16-22 of the skill) handle the actual IDA database modifications.

Stage 5: Iterative Enrichment

After the initial migration round completes, newly-named functions become additional anchors. The workflow repeats stages 2-4 until achieving the desired coverage. This bootstrap expansion continuously improves the symbol density of the new binary without requiring manual anchor selection beyond the initial seed set.

Core Implementation Components

Component Technology Purpose
Data collection IDAPython, IDA Pro Export disassembly and pseudocode from both binaries
Diff engine DeepSeek-V3, GPT-4o, Claude Structural comparison and symbol inference
Result processing Python 3, PyYAML Parse YAML output and batch-apply to IDA
Orchestration Python 3, async/await, ThreadPoolExecutor Anchor management, concurrent LLM calls, caching
Dependency bootstrap pip install On-demand installation of PyYAML and other tools

Practical Code Implementation

Building the LLM Prompt

def build_prompt(old_disasm, old_proc, new_disasm, new_proc, symbols):
    template = """I have disassembly outputs and procedure code of the same function.

This is the function for reference:

**Disassembly for Reference**

```c
{disasm_for_reference}

Procedure code for Reference

{procedure_for_reference}

This is the function you need to reverse-engineering:

Disassembly to reverse-engineering

{disasm_code}

Procedure code to reverse-engineering

{procedure}

What you need to do is to collect all references to "{symbol_name_list}" in the function you need to reverse-engineering and output those references as YAML. """ return template.format( disasm_for_reference=old_disasm, procedure_for_reference=old_proc, disasm_code=new_disasm, procedure=new_proc, symbol_name_list=",".join(symbols), )


### Concurrent Batch Processing

The skill recommends 10-20 parallel LLM calls for efficiency:

```python
from concurrent.futures import ThreadPoolExecutor

anchors = discover_anchors(old_idb, new_idb)
symbols_to_migrate = collect_symbols(old_idb)

def migrate_one(anchor):
    old_disp, old_proc = export_from_ida(old_idb, anchor)
    new_disp, new_proc = export_from_ida(new_idb, anchor)
    prompt = build_prompt(old_disp, old_proc, new_disp, new_proc, symbols_to_migrate)
    yaml_out = query_llm(prompt)
    apply_mapping(yaml_out)

with ThreadPoolExecutor(max_workers=10) as pool:
    pool.map(migrate_one, anchors)

Key Source Files

  • skills/binary-diff/SKILL.md — Master specification containing the complete prompt template, anchor strategy, and YAML output schema
  • skills/routing.md — Route configuration that triggers this skill on "symbol migration" and "cross-version compare" keywords (lines 107-108)
  • skills/ida-reverse/SKILL.md — IDA export helper functions that the migration workflow depends upon

Summary

  • Symbol migration transfers names from a symbolized old binary to a stripped new binary using LLM-powered structural comparison
  • Anchor functions ensure correct cross-version alignment without sending whole binaries to external APIs
  • Four-section prompts (old/new disassembly + pseudocode) provide sufficient context for accurate symbol matching
  • YAML output enables automated batch application through IDAPython
  • Iterative enrichment expands coverage by promoting newly-named functions to anchor status
  • Concurrent processing at 10-20 parallel calls keeps migration time practical for large binaries

Frequently Asked Questions

What makes a good anchor function for symbol migration?

Good anchors are functions that survive cross-version compilation with minimal semantic changes—exported APIs, functions containing unique string literals, or routines with distinctive constant values. The skill specifically recommends these patterns in lines 85-92 of SKILL.md because they provide reliable matching points even when compiler optimizations differ between builds.

Why does the workflow use YAML instead of JSON for LLM output?

YAML handles multi-line strings and comments more naturally than JSON, which matters when the LLM needs to embed disassembly snippets or explanatory notes about ambiguous references. The PyYAML parser also tolerates minor formatting inconsistencies that occasionally occur in LLM-generated structured text.

How does this approach protect binary confidentiality?

By design, the workflow never transmits complete binaries to any LLM API. Only individual function bodies—already disassembled into text form—are sent. This keeps token usage low (approximately 1 cent per 200 functions) and ensures that proprietary code never leaves your controlled environment in executable form.

Can this workflow handle obfuscated or heavily optimized code?

The workflow performs best on binaries where function boundaries remain recognizable. Heavy obfuscation that destroys control flow integrity may break anchor selection. However, the LLM component can sometimes resolve symbol identities even through moderate optimization differences because it compares semantic structure rather than raw bytes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →