reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user