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

> Learn how Reverse-Skill's symbol migration workflow enables cross-version binary comparison by transferring names from old to new binaries using LLM assistance, disassembly, and pseudocode anchors.

- Repository: [ZhaoXu/reverse-skill](https://github.com/zhaoxuya520/reverse-skill)
- Tags: internals
- Published: 2026-08-14

---

**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](https://github.com/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)](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/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:

```python
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

```python
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**

```c
{procedure_for_reference}

```

This is the function you need to reverse-engineering:

**Disassembly to reverse-engineering**

```c
{disasm_code}

```

**Procedure code to reverse-engineering**

```c
{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`](https://github.com/zhaoxuya520/reverse-skill/blob/main/skills/binary-diff/SKILL.md)** — Master specification containing the complete prompt template, anchor strategy, and YAML output schema
- **[`skills/routing.md`](https://github.com/zhaoxuya520/reverse-skill/blob/main/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`](https://github.com/zhaoxuya520/reverse-skill/blob/main/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`](https://github.com/zhaoxuya520/reverse-skill/blob/main/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.