#!/usr/bin/env python3 """Inspect a Java/JAR sample statically: structure, manifest, obfuscation, capabilities. Treats the JAR as a ZIP, inventories .class files and embedded payloads, reads the manifest entry point, and scans class bytes for obfuscator fingerprints and dangerous API usage. Reads the archive statically; never launches the JVM. Defangs URLs. """ from __future__ import annotations import argparse import json import re import sys import zipfile from pathlib import Path EMBED_SIGS = [(b"PK\x03\x04", "nested ZIP/JAR"), (b"MZ", "PE executable"), (b"#!", "script"), (b"\x7fELF", "ELF")] OBFUSCATORS = [b"allatori", b"proguard", b"zelix", b"klassmaster", b"stringer", b"dasho"] CAPABILITY_APIS = [b"java/lang/Runtime", b"java/lang/ProcessBuilder", b"java/lang/reflect", b"java/net/URLClassLoader", b"javax/crypto", b"java/net/Socket", b"java/net/URL", b"java/awt/Robot", b"java/util/Base64"] RE_URL = re.compile(rb"https?://[\x21-\x7e]{4,}") def _defang(s: str) -> str: return s.replace("http", "hxxp").replace(".", "[.]") def inspect(path: str) -> dict: data = Path(path).read_bytes() result: dict = {"is_jar": False} if not zipfile.is_zipfile(path): return {"error": "not a ZIP/JAR archive"} result["is_jar"] = True classes = [] resources = [] manifest = "" obf = set() caps = set() urls = set() nested = [] with zipfile.ZipFile(path) as zf: for name in zf.namelist(): if name.endswith(".class"): classes.append(name) try: body = zf.read(name) except Exception: continue for o in OBFUSCATORS: if o in body.lower(): obf.add(o.decode()) for api in CAPABILITY_APIS: if api in body: caps.add(api.decode().replace("/", ".")) for u in RE_URL.findall(body): urls.add(_defang(u.decode("latin-1", "ignore"))) elif name == "META-INF/MANIFEST.MF": manifest = zf.read(name).decode("latin-1", "ignore") else: resources.append(name) try: head = zf.read(name)[:8] except Exception: head = b"" for sig, label in EMBED_SIGS: if head.startswith(sig): nested.append({"name": name, "type": label}) main_class = "" for line in manifest.splitlines(): if line.lower().startswith(("main-class:", "premain-class:")): main_class = line.split(":", 1)[1].strip() break # single-character class names suggest name obfuscation short_names = sum(1 for c in classes if len(Path(c).stem) <= 2) return { "is_jar": True, "class_count": len(classes), "main_class": main_class, "embedded_payloads": nested[:50], "resources": resources[:50], "obfuscators": sorted(obf), "name_obfuscation_suspected": short_names > max(3, len(classes) // 5), "capability_apis": sorted(caps), "urls": sorted(urls)[:50], } def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Inspect a Java/JAR sample") sub = parser.add_subparsers(dest="cmd", required=True) p = sub.add_parser("inspect") p.add_argument("file") args = parser.parse_args(argv) print(json.dumps(inspect(args.file), indent=2)) return 0 if __name__ == "__main__": sys.exit(main())