63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Analyze a Go-compiled binary: confirm Go build, recover names, list package paths.
|
|
|
|
Detects the Go build signature/version, scans printable strings for Go function/package names
|
|
(main.*, runtime.*, github.com/... import paths), and reports likely capabilities. Reads bytes
|
|
statically; executes nothing. This is a heuristic string/pclntab-marker scan, not a full pclntab
|
|
parser.
|
|
"""
|
|
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_GOVER = re.compile(rb"go1\.\d+(?:\.\d+)?")
|
|
PCLNTAB_MAGICS = [b"\xfb\xff\xff\xff", b"\xfa\xff\xff\xff", b"\xf0\xff\xff\xff",
|
|
b"\xf1\xff\xff\xff"]
|
|
RE_FUNC = re.compile(r"^(?:main|runtime|net|crypto|os/exec|syscall)[./][\w./*()]+")
|
|
RE_PKG = re.compile(r"(?:github\.com|gitlab\.com|golang\.org|gopkg\.in)/[\w./-]+")
|
|
|
|
CAP_PKGS = {
|
|
"net/http": "HTTP networking", "net": "raw networking", "crypto/aes": "AES crypto",
|
|
"os/exec": "process execution", "syscall": "direct syscalls",
|
|
"golang.org/x/sys": "low-level OS access",
|
|
}
|
|
|
|
|
|
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_go = b"Go build ID" in data or bool(RE_GOVER.search(data)) or b"runtime.main" in data
|
|
versions = sorted({m.decode() for m in RE_GOVER.findall(data)})
|
|
funcs = sorted({s for s in strings if RE_FUNC.match(s)})[:200]
|
|
pkgs = sorted(set(RE_PKG.findall(blob)))[:100]
|
|
caps = sorted({desc for pkg, desc in CAP_PKGS.items() if pkg in blob})
|
|
pcln = [m.hex() for m in PCLNTAB_MAGICS if m in data]
|
|
return {
|
|
"is_go_binary": is_go,
|
|
"go_versions": versions,
|
|
"pclntab_magics_present": pcln,
|
|
"recovered_functions": funcs,
|
|
"package_paths": pkgs,
|
|
"capabilities": caps,
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Analyze a Go 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())
|