#!/usr/bin/env python3 """Read-only PE inventory for locally supplied NGX/DLSS DLLs. This reports metadata, imports/exports, resource metadata, section sizes/entropy, and targeted strings. It never writes or extracts DLL payloads. """ from __future__ import annotations import argparse import hashlib import json import math import re import struct import sys from collections import Counter from pathlib import Path from typing import Any class PEError(ValueError): pass def unpack(fmt: str, data: bytes, offset: int) -> tuple[Any, ...]: size = struct.calcsize(fmt) if offset < 0 or offset + size > len(data): raise PEError(f"truncated PE data at file offset 0x{offset:x}") return struct.unpack_from(fmt, data, offset) def c_string(data: bytes, offset: int, limit: int = 4096) -> str: if offset < 0 or offset >= len(data): return "" end = data.find(b"\0", offset, min(len(data), offset + limit)) if end < 0: end = min(len(data), offset + limit) return data[offset:end].decode("ascii", errors="replace") def entropy(block: bytes) -> float: if not block: return 0.0 counts = Counter(block) size = len(block) return -sum((n / size) * math.log2(n / size) for n in counts.values()) def aligned(value: int, alignment: int = 4) -> int: return (value + alignment - 1) & ~(alignment - 1) def read_utf16z(data: bytes, offset: int, limit: int) -> tuple[str, int]: end = offset max_end = min(len(data), offset + limit) chars = bytearray() while end + 1 < max_end: word = struct.unpack_from(" dict[str, Any]: result: dict[str, Any] = {"strings": {}} def visit(start: int, parent_end: int) -> None: if start + 6 > parent_end or start + 6 > len(blob): return length, value_length, value_type = unpack("= 52 and value_start + 52 <= end: fixed = unpack("<13I", blob, value_start) if fixed[0] == 0xFEEF04BD: file_ms, file_ls, prod_ms, prod_ls = fixed[2:6] result["fixed_file_version"] = [file_ms >> 16, file_ms & 0xFFFF, file_ls >> 16, file_ls & 0xFFFF] result["fixed_product_version"] = [prod_ms >> 16, prod_ms & 0xFFFF, prod_ls >> 16, prod_ls & 0xFFFF] if value_type == 1 and value_length and key not in {"StringFileInfo", "VarFileInfo"}: value = blob[value_start:value_end].decode("utf-16le", errors="replace").rstrip("\0") result["strings"].setdefault(key, value) child = aligned(value_end) while child + 6 <= end: child_len = unpack(" end: break visit(child, end) child = aligned(child + child_len) visit(0, len(blob)) if not result["strings"]: result.pop("strings") return result class PEImage: DIRECTORY_NAMES = { 0: "export", 1: "import", 2: "resource", 3: "exception", 4: "security_certificate_file_offset", 5: "base_relocation", 6: "debug", 9: "tls", 10: "load_config", 12: "iat", 13: "delay_import", } RESOURCE_TYPES = { 1: "CURSOR", 2: "BITMAP", 3: "ICON", 4: "MENU", 5: "DIALOG", 6: "STRING", 9: "ACCELERATOR", 10: "RCDATA", 14: "GROUP_ICON", 16: "VERSIONINFO", 24: "MANIFEST", } STRING_PATTERN = re.compile( r"(?:ngx|dlss|cuda|nvapi|tensor|weight|shader|cubin|fatbin|\.pth|d3d12|dxgi|vulkan|nvml|driver|nvsdk)", re.IGNORECASE, ) MAGIC_MARKERS = { "ELF magic candidate": b"\x7fELF", "SPIR-V little-endian magic": b"\x03\x02\x23\x07", "DXBC container marker": b"DXBC", "DXIL marker": b"DXIL", "Zstandard frame magic": b"\x28\xb5\x2f\xfd", "LZ4 frame magic": b"\x04\x22\x4d\x18", "gzip header": b"\x1f\x8b", "ZIP local-file header": b"PK\x03\x04", "CUDA fatbin symbol": b"__nv_relfatbin", "CUDA fatbin wrapper symbol": b"__cudaFatCubin", } def __init__(self, path: Path, string_limit: int = 100) -> None: self.path = path self.data = path.read_bytes() self.string_limit = string_limit self.sections: list[dict[str, Any]] = [] self.directories: list[tuple[int, int]] = [] self.pointer_size = 8 self.machine = 0 self._parse_headers() def _parse_headers(self) -> None: if self.data[:2] != b"MZ": raise PEError("missing MZ header") pe_offset = unpack(" int: if rva < 0x1000: return rva for section in self.sections: start = section["virtual_address"] size = max(section["virtual_size"], section["raw_size"]) if start <= rva < start + size: offset = section["raw_offset"] + rva - start if offset < len(self.data): return offset raise PEError(f"RVA 0x{rva:x} is not backed by a file section") def directory(self, index: int) -> tuple[int, int]: return self.directories[index] if index < len(self.directories) else (0, 0) def imports(self) -> list[dict[str, Any]]: rva, size = self.directory(1) if not rva or not size: return [] off = self.rva_to_offset(rva) item_size = 20 result = [] for pos in range(off, min(off + size, len(self.data)) - item_size + 1, item_size): original, stamp, chain, name_rva, first_thunk = unpack(" list[dict[str, Any]]: rva, size = self.directory(0) if not rva or size < 40: return [] off = self.rva_to_offset(rva) fields = unpack(" list[dict[str, Any]]: rva, size = self.directory(2) if not rva or not size: return [] root = self.rva_to_offset(rva) items: list[dict[str, Any]] = [] visited: set[int] = set() def parse_name(name_field: int, is_type: bool) -> str | int: if not (name_field & 0x80000000): resource_id = name_field & 0xFFFF return self.RESOURCE_TYPES.get(resource_id, resource_id) if is_type else resource_id rel = name_field & 0x7FFFFFFF name_offset = root + rel units = unpack(" None: if depth > 8 or rel in visited: return visited.add(rel) directory_off = root + rel named, ids = unpack(" 10000: return for index in range(count): name_field, target = unpack("= 0 else None, "size": data_size, "codepage": codepage, } if (16 in new_path or "VERSIONINFO" in new_path) and data_off >= 0 and data_size <= 4 * 1024 * 1024: item["version_info"] = parse_version_info(self.data[data_off : data_off + data_size]) items.append(item) walk(0, []) return items def targeted_strings(self) -> list[dict[str, Any]]: hits: list[dict[str, Any]] = [] seen: set[str] = set() patterns = ( (re.compile(rb"[\x20-\x7e]{6,}"), "ascii"), (re.compile(rb"(?:[\x20-\x7e]\x00){6,}"), "utf16le"), ) for pattern, encoding in patterns: for match in pattern.finditer(self.data): raw = match.group(0) value = raw.decode("ascii", errors="replace") if encoding == "ascii" else raw[::2].decode("ascii", errors="replace") value = value.strip() if value and value not in seen and self.STRING_PATTERN.search(value): seen.add(value) hits.append({"offset": f"0x{match.start():x}", "encoding": encoding, "value": value[:1000]}) if len(hits) >= self.string_limit: return hits return hits def section_for_file_offset(self, offset: int) -> str | None: for section in self.sections: start = section["raw_offset"] if start <= offset < start + section["raw_size"]: return section["name"] return None def binary_markers(self) -> list[dict[str, Any]]: findings = [] for label, marker in self.MAGIC_MARKERS.items(): offsets = [] cursor = 0 count = 0 valid_elf_count = 0 elf_types: Counter[tuple[int, int, int, int]] = Counter() elf_candidates: Counter[tuple[int, int, int, int, int, int]] = Counter() while True: offset = self.data.find(marker, cursor) if offset < 0: break count += 1 if len(offsets) < 20: offsets.append({"file_offset": f"0x{offset:x}", "section": self.section_for_file_offset(offset)}) if label.startswith("ELF image") and offset + 64 <= len(self.data): header = self.data[offset : offset + 64] elf_class, elf_data = header[4], header[5] if header[6] == 1 and elf_class in (1, 2) and elf_data in (1, 2): endian = "<" if elf_data == 1 else ">" elf_type = struct.unpack_from(endian + "H", header, 16)[0] machine = struct.unpack_from(endian + "H", header, 18)[0] elf_version = struct.unpack_from(endian + "I", header, 20)[0] expected_header_size = 64 if elf_class == 2 else 52 header_size = struct.unpack_from(endian + "H", header, 52 if elf_class == 2 else 40)[0] elf_candidates[(elf_class, elf_data, elf_type, machine, elf_version, header_size)] += 1 if elf_version == 1 and header_size == expected_header_size: valid_elf_count += 1 elf_types[(elf_class, elf_data, elf_type, machine)] += 1 cursor = offset + 1 if count: finding: dict[str, Any] = {"marker": label, "count": count, "sample_offsets": offsets} if label.startswith("ELF"): finding["valid_elf_header_count"] = valid_elf_count finding["elf_header_types"] = [ { "elf_class": "ELF64" if key[0] == 2 else "ELF32", "endianness": "little" if key[1] == 1 else "big", "type": key[2], "machine": key[3], "machine_name": "EM_CUDA" if key[3] == 190 else "unknown", "count": value, } for key, value in elf_types.items() ] finding["candidate_header_fields"] = [ { "elf_class": "ELF64" if key[0] == 2 else "ELF32", "endianness": "little" if key[1] == 1 else "big", "type": key[2], "machine": key[3], "machine_name": "EM_CUDA" if key[3] == 190 else "unknown", "e_version": key[4], "e_ehsize": key[5], "count": value, } for key, value in elf_candidates.most_common(10) ] findings.append(finding) for label, pattern in ( ("PTX .version directive candidate", re.compile(rb"(? dict[str, Any]: directories = {} for index, (rva, size) in enumerate(self.directories): if rva or size: directories[self.DIRECTORY_NAMES.get(index, str(index))] = {"rva_or_file_offset": f"0x{rva:x}", "size": size} hash_digest = hashlib.sha256(self.data).hexdigest() return { "path": str(self.path), "size_bytes": len(self.data), "sha256": hash_digest, "format": { "machine": f"0x{self.machine:04x}", "pe_type": "PE32+" if self.pointer_size == 8 else "PE32", "image_base": f"0x{self.image_base:x}", "coff_timestamp": self.timestamp, "coff_characteristics": f"0x{self.characteristics:04x}", "data_directories": directories, }, "sections": self.sections, "imports": self.imports(), "exports": self.exports(), "resources": self.resources(), "targeted_strings": self.targeted_strings(), "candidate_binary_markers": self.binary_markers(), } def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, help="write JSON report to this local path") parser.add_argument("--strings-limit", type=int, default=100, help="maximum targeted strings per DLL") parser.add_argument("dll", nargs="+", type=Path) args = parser.parse_args() reports = [] failed = False for path in args.dll: try: reports.append(PEImage(path, max(0, args.strings_limit)).report()) except (OSError, PEError, struct.error) as exc: failed = True reports.append({"path": str(path), "error": str(exc)}) encoded = json.dumps(reports, indent=2, ensure_ascii=False) + "\n" if args.output: args.output.write_text(encoded, encoding="utf-8") print(f"Wrote read-only metadata report for {len(reports)} input(s) to {args.output}") else: sys.stdout.write(encoded) return 1 if failed else 0 if __name__ == "__main__": raise SystemExit(main())