When to use / when not to use
- Use: before/after patch comparison (vulnerability localization), family variant correlation, sample tracing, N-day analysis
- Use: same-source code reuse identification (determining shared functions/library code)
- Use: fix coverage audit—compare fixed samples across multiple branch versions one by one, verifying which versions fixed it and which did not
- Use: vulnerability research—full reconstruction of structural and behavioral differences before and after the fix (patch → defect pattern)
- Don't use: in-depth single-sample analysis (follow the [[re-binary-core]] general path)
- Don't use: no comparison baseline (only a single sample, no second version to compare against)—follow the single-sample path first
- Don't use: behavioral-level comparison (dynamic behavior differences go to [[re-behavior]]; this skill only compares static code structure)
- Don't use: comparison requiring semantic-level reconstruction (first use [[re-cpp-abi]] to reconstruct ABI/symbol semantics, then return to this skill for difference localization)
Tool preparation
BinDiff / Diaphora (function matching plugins)
- BinDiff (Google/Zynamics line, Windows commercial tool, official installer): primarily an IDA plugin; since BinDiff 6 it officially supports Ghidra (requires the BinExport plugin for export); BinExport also supports Binary Ninja—the export format can bridge multiple decompilers
- Diaphora: multi-platform open source (
git clone https://github.com/joxeankoret/diaphora), primarily an IDA plugin (Ghidra-side support is continuously progressing) - Alternatives without an IDA environment: qbindiff (Quarkslab, reads BinExport/quokka exports), ghidriff (headless Ghidra diff)
- Typical workflow: BinExport export within the decompiler → load the two samples into the tool → match → export results (similarity/change set) as a report; the BinExport plugin and decompiler versions must match (see [[gotchas]])
- Verification: the plugin can be loaded within the decompiler and can export/import matching results
radiff2 / rz-diff (rizin command-line diff)
- For the installation matrix see [[re-radare2]] (including the radare2 compatibility layer); after installation verify with
rizin -vrizin -v - Verification:
rz-diff --version; radiff2 ships with the radare2 package - Purpose: fast function-level comparison and batch scripting without a GUI (see the step 1 commands); radiff2 does quick byte/instruction-level checks (
radiff2 -sradiff2 -s bytes,-Cinstructions)—use it for quick checks on small files, use rz-diff for function-level comparison
readelf (symbol alignment aid)
- For installation and verification see [[re-cpp-abi]] tool preparation
- Purpose:
readelf -s(symbol table/dynamic symbols) andreadelf -d(dynamic section) for import/export alignment and pairing verification
Pairing aids (pre-comparison)
- Hashing/baseline:
sha256sum; architecture/compiler determination: [[re-imports]] import table and toolchain fingerprint—confirm comparability before pairing (same architecture/same toolchain, see pitfalls 1, 3) - Verification:
sha256sum --version - Packer check: [[re-packer-id]] identifies packing; unpack packed samples first ([[re-unpack-simple]]) before comparing—packer code pollutes matching (see the counterexample group in [[gotchas]])
Operating steps
Execute in order, archive the output of each step (path + sha256, see [[re-triage]]); the final difference list is passed according to the [[re-analyze/analysis-contract]] data contract.
-
Pairing preparation (confirm comparability before comparing):
- Record baseline: sha256 of both samples, architecture (x86_64/ARM, etc.), bitness, compiler fingerprint (version string/imported library set, [[re-imports]])
- Comparability determination: same architecture + same toolchain → full function-level matching; cross-architecture/different compiler → downgrade to logic-layer comparison (call graph/constants/strings), annotate limitations
- Bitness/endianness: mixed 32/64-bit or mixed big/little-endian are not compared directly—unify to the same bitness and endianness before matching
- Symbol state: strip/not stripped determines the matching strategy (if not stripped, align directly by symbol, see step 2)
-
Function matching:
# 命令行快速对比(rizin 系):函数级匹配(-B 自动分析后按函数对比,含相似度列) rz-diff -B -t functions sample_v1 sample_v2 | head -30- Matching dimensions: instruction hash (identical code), call graph (subgraph isomorphism), import/export alignment, string/constant references
- Data reference dimension: global constant/string references serve as function identity fingerprints—more stable than instruction hashes after refactoring/reordering, a reliable anchor for cross-version matching
- Tool (BinDiff/Diaphora) output: matched / changed / new / deleted function sets
- Output interpretation: matched means identical, changed means modified, new/deleted means functionality added/removed—changed goes to step 3, new/deleted indicate differences in functionality surface, give follow-up actions for each category
- Missing symbols after strip: rely on structural matching (entry features/call patterns/constant anchoring)—for matching dimension weight adjustment see pitfall 1 and [[decision-tree]]
- Address difference handling: function addresses differ after PIE/relocation—align by symbol name/function order, do not compare raw addresses (see pitfall 2)
- Name/comment migration: tools support migrating names/comments of already-named functions to matched functions—annotate known samples first and then compare, semi-automatically recover symbols of new samples; annotate the source of migrated names, use only as auxiliary anchors (see [[gotchas]] symbol group)
-
Patch diff (N-day):
- Compare before and after the fix →
changedfunction set = vulnerability point candidates - Classify changed functions by semantics: newly added validation/boundary handling (high priority) > behavior changes > pure refactoring (low priority)—sort candidates by category ([[decision-tree]] grading)
- Deep dive into changed functions: new conditional branches/new validation/newly added calls ([[re-ghidra]] / [[re-ida]] decompilation)
- Infer vulnerabilities: defect patterns in the old code (missing validation/out-of-bounds/use-after-free)
- Fix pattern recognition: common fix patterns (length checks/null pointer checks/tightening out-of-bounds limits) are correspondingly categorized with defect patterns—quickly screen candidate functions by pattern
- Fix coverage verification: compare fixed samples one by one across multi-branch versions to confirm the coverage of vulnerability fixes (which versions fixed it and which did not)
- Output: vulnerable functions + defect pattern inference (with confidence annotation, for evidence grading see [[decision-tree]])
- Compare before and after the fix →
-
Variant tracing:
- Pairwise comparison of samples within a family → similarity matrix → clustering (shared function ratio threshold)
- Evolution chain: sort samples by timeline/similarity (early vs late variants)
- Shared unique functions = family signature (connects with [[re-attribution]] capability evidence)
- Unique function determination: first compute the shared set within the family, then take functions missing from out-of-family samples as family markers—avoid treating common code as a marker
- Association strength: comprehensively determined by the number of shared unique functions + similarity distribution; no single metric is used as the sole basis
- Sample size control: when there are many family samples, the pairwise matrix is O(n²)—first perform coarse clustering by features (architecture/size/import set), then compare in detail within clusters
- Threshold calibration: manually spot-check and calibrate similarity scores before setting the clustering line (see Pitfall 5) to avoid inflated associations
-
Output a difference list and verification:
- Format: changed function table (function name/address/change type/similarity) + conclusion
- Pass according to the [[re-analyze/analysis-contract]] data contract (downstream consumption)
- False-positive prevention verification: exclude shared library code (cross-check against the [[re-imports]] dependency list), recheck with a different compiler (differences from compiling the same source with different compilers), and cross-validate across tools ([[decision-tree]] grading)
- Reproducible report: the difference list includes tools and versions, similarity thresholds, and exclusion rules; important conclusions are stored with matching export files + manual decompilation review records
Cross-domain joint
- [[re-binary-core]] gateway: home of this skill (selection tree "patch/N-day comparison" branch)
- [[re-ghidra]] / [[re-ida]]: decompilation foundation (deep dive into changed functions)
- [[re-attribution]]: connecting capability evidence for variant association
- [[re-imports]]: import table/toolchain fingerprint (pairing comparability and shared library exclusion)
- [[re-cpp-abi]]: symbol semantics recovery (prerequisite for semantic-level comparison)
- [[re-packer-id]] / [[re-unpack-simple]]: unpack packed samples before comparison (packer code pollutes matching)
- [[re-analyze/analysis-contract]]: the difference list is passed according to the data contract
Common pitfalls and traps
- Compiler differences interfere with matching: Symptom—the same source code compiled by different compilers is judged dissimilar; Cause—optimization/code generation differences; Countermeasure—weight by call graph and constant references, and reduce the weight of instruction hashing
- Missing symbols after strip: Symptom—no function names at all; Cause—symbol stripping; Countermeasure—structural matching (entry features/call patterns) and import table anchoring
- Cross-architecture comparison degrades: Symptom—low match rate between x86 and ARM; Cause—different instruction sets; Countermeasure—compare only the logic layer (call graph/constants/strings), and note cross-architecture limitations
- Large numbers of similarities cause false associations: Symptom—shared library code causes inflated similarity; Cause—common dependencies (libc/frameworks); Countermeasure—exclude shared library symbols and compare only business code ([[re-imports]] dependency cross-check)
- Patch diff localization bias: Symptom—many changed functions, and the vulnerability point is drowned out; Cause—the patch contains refactoring; Countermeasure—filter by change semantics (prioritize newly added validation/boundary handling)
- Raw address comparison fails: Symptom—the same function is reported as different; Cause—address drift due to PIE/relocation/version differences; Countermeasure—align by symbols/function order, not raw addresses
- "No diff found" ≠ "not fixed": Symptom—patch comparison has no changed functions; Cause—the fix is inlined into adjacent functions or reorganized across functions; Countermeasure—expand the comparison scope (including instruction hashes after inline expansion), and cross-check with version history
- Name migration contamination: Phenomenon—the function names of the new sample appear to be known; Cause—migrated from matching sources rather than real symbols; Countermeasure—annotate the source of migrated names, use them only as auxiliary anchors, and confirm key functions through decompilation
- Symbol table forged: Phenomenon—symbol names are complete but the structure does not match; Cause—obfuscation/anti-analysis tools rename (forge symbols); Countermeasure—subject symbol alignment results to structural confirmation (call patterns/constants), and do not trust the symbol names themselves
- Blind spot of comparing only .text: Phenomenon—function matches are all identical but behavior differs; Cause—data segment differences (configuration/constant tables) are excluded from the comparison scope; Countermeasure—include the data reference dimension in matching, and compare constant tables/configuration structures separately
- For decision branches (comparability/matching level/candidate ranking), see [[decision-tree]]; for matching algorithms and tool boundaries, see [[gotchas]]