59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Detect frozen-Python packers and locate the embedded archive.
|
|
|
|
Scans an executable for PyInstaller, py2exe, and cx_Freeze markers, locates the PyInstaller
|
|
CArchive magic cookie, and extracts embedded Python version strings. Reads bytes statically;
|
|
executes nothing.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
PYI_COOKIE = b"MEI\014\013\012\013\016"
|
|
PYI_MARKERS = [b"pyi-windows-manifest-filename", b"PYZ-00.pyz", b"pyiboot01_bootstrap",
|
|
b"_MEIPASS"]
|
|
PY2EXE_MARKERS = [b"PYTHONSCRIPT", b"py2exe"]
|
|
CXFREEZE_MARKERS = [b"cx_Freeze", b"cx_freeze__init__"]
|
|
RE_PYVER = re.compile(rb"python(\d)\.?(\d{1,2})")
|
|
RE_PYDLL = re.compile(rb"python(\d)(\d{1,2})\.dll", re.I)
|
|
|
|
|
|
def detect(path: str) -> dict:
|
|
data = Path(path).read_bytes()
|
|
pyi = any(m in data for m in PYI_MARKERS) or PYI_COOKIE in data
|
|
py2exe = any(m in data for m in PY2EXE_MARKERS)
|
|
cxfreeze = any(m in data for m in CXFREEZE_MARKERS)
|
|
cookie_off = data.rfind(PYI_COOKIE)
|
|
versions = sorted({f"{m.group(1).decode()}.{m.group(2).decode()}"
|
|
for m in RE_PYVER.finditer(data)})
|
|
dll_versions = sorted({f"{m.group(1).decode()}.{m.group(2).decode()}"
|
|
for m in RE_PYDLL.finditer(data)})
|
|
packer = "PyInstaller" if pyi else "py2exe" if py2exe else \
|
|
"cx_Freeze" if cxfreeze else "unknown"
|
|
return {
|
|
"is_frozen_python": pyi or py2exe or cxfreeze,
|
|
"packer": packer,
|
|
"pyinstaller_cookie_offset": cookie_off if cookie_off != -1 else None,
|
|
"python_versions": versions[:5],
|
|
"python_dll_versions": dll_versions[:5],
|
|
"next_step": "extract the archive (e.g. pyinstxtractor) then decompile matching .pyc",
|
|
}
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Detect frozen-Python packers")
|
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
p = sub.add_parser("detect")
|
|
p.add_argument("file")
|
|
args = parser.parse_args(argv)
|
|
print(json.dumps(detect(args.file), indent=2))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|