#!/usr/bin/env python3 """Jar inspection without unzip/java. jx.py ls JAR[!nested.jar...] [regex] list entries jx.py cat JAR[!nested.jar...] entry print entry jx.py str JAR[!nested.jar...] classregex strregex UTF8 constants of classes """ import io, re, struct, sys, zipfile def open_jar(spec): parts = spec.split("!") z = zipfile.ZipFile(parts[0]) for p in parts[1:]: z = zipfile.ZipFile(io.BytesIO(z.read(p))) return z def utf8_consts(data): out = [] if data[:4] != b"\xca\xfe\xba\xbe": return out n = struct.unpack(">H", data[8:10])[0] i, k = 10, 1 while k < n: tag = data[i] if tag == 1: ln = struct.unpack(">H", data[i + 1:i + 3])[0] out.append(data[i + 3:i + 3 + ln].decode("utf-8", "replace")) i += 3 + ln elif tag in (3, 4): i += 5 elif tag in (5, 6): i += 9; k += 1 elif tag in (7, 8, 16, 19, 20): i += 3 elif tag in (9, 10, 11, 12, 17, 18): i += 5 elif tag == 15: i += 4 else: break k += 1 return out def main(): cmd, spec = sys.argv[1], sys.argv[2] z = open_jar(spec) if cmd == "ls": rx = re.compile(sys.argv[3]) if len(sys.argv) > 3 else None for n in z.namelist(): if not rx or rx.search(n): print(n) elif cmd == "cat": sys.stdout.write(z.read(sys.argv[3]).decode("utf-8", "replace")) elif cmd == "str": crx, srx = re.compile(sys.argv[3]), re.compile(sys.argv[4]) for n in z.namelist(): if n.endswith(".class") and crx.search(n): hits = [s for s in utf8_consts(z.read(n)) if srx.search(s)] if hits: print("==", n) for h in hits: print(" ", repr(h)) main()