81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
#!/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:
|
|
# _ZN<len>name<len>name...17h<hash>E -> 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())
|