#!/usr/bin/env python3 """Analyze a Rust-compiled binary: confirm Rust, demangle symbols, enumerate crates. Detects Rust toolchain markers, demangles legacy (_ZN...E) Rust symbols heuristically, and extracts crate names/versions from embedded .cargo registry paths. Reads bytes statically; executes nothing. v0 (_R) demangling is reported as detected but full v0 decoding is delegated to a dedicated tool. """ from __future__ import annotations import argparse import json import re import sys from pathlib import Path ASCII_RE = re.compile(rb"[\x20-\x7e]{3,}") RE_LEGACY = re.compile(r"_ZN([0-9A-Za-z$._]+)17h[0-9a-f]{16}E") RE_V0 = re.compile(r"_R[0-9A-Za-z_]{4,}") RE_CRATE = re.compile(r"registry/src/[^/]+/([A-Za-z0-9_-]+)-(\d+\.\d+\.\d+)") RE_RUSTC_PATH = re.compile(r"/rustc/[0-9a-f]{6,}/library/") CAP_CRATES = { "reqwest": "HTTP client", "hyper": "HTTP", "tokio": "async runtime", "aes": "AES crypto", "rsa": "RSA crypto", "winapi": "Windows API", "windows": "Windows API", "sysinfo": "host enumeration", "tonic": "gRPC", } def _demangle_legacy(sym: str) -> str: # _ZNnamename...17hE -> name::name::... body = sym[len("_ZN"):] parts = [] i = 0 while i < len(body): j = i while j < len(body) and body[j].isdigit(): j += 1 if j == i: break length = int(body[i:j]) seg = body[j:j + length] if seg.startswith("17h"): break parts.append(seg) i = j + length return "::".join(parts) if parts else sym def info(path: str) -> dict: data = Path(path).read_bytes() strings = [m.decode("latin-1", "ignore") for m in ASCII_RE.findall(data)] blob = "\n".join(strings) is_rust = bool(RE_RUSTC_PATH.search(blob)) or "cargo/registry" in blob or "rustc" in blob \ or "called `Result::unwrap()`" in blob legacy = sorted({_demangle_legacy("_ZN" + m) for m in RE_LEGACY.findall(blob)})[:200] v0_present = sorted(set(RE_V0.findall(blob)))[:50] crates = sorted({f"{n}-{v}" for n, v in RE_CRATE.findall(blob)})[:100] caps = sorted({desc for c, desc in CAP_CRATES.items() if any(cr.startswith(c + "-") for cr in crates)}) return { "is_rust_binary": is_rust, "legacy_demangled": legacy, "v0_symbols_present": v0_present, "crates": crates, "capabilities": caps, } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Analyze a Rust binary") sub = parser.add_subparsers(dest="cmd", required=True) p = sub.add_parser("info") p.add_argument("file") args = parser.parse_args(argv) print(json.dumps(info(args.file), indent=2)) return 0 if __name__ == "__main__": sys.exit(main())