#!/usr/bin/env python3 """Look at the constant pool of a class of the remake and see what is hidden. The remake obfuscates the string constants: they are not readable in the class file. This script pulls the UTF-8 entries out of the constant pool and tries to work out how they were scrambled, because the same cipher is probably what protects the .obj and .rrs files. Usage: inspect-constants.py [...] """ import struct import sys from pathlib import Path TAGS = { 1: "Utf8", 3: "Integer", 4: "Float", 5: "Long", 6: "Double", 7: "Class", 8: "String", 9: "Fieldref", 10: "Methodref", 11: "InterfaceMethodref", 12: "NameAndType", 15: "MethodHandle", 16: "MethodType", 17: "Dynamic", 18: "InvokeDynamic", 19: "Module", 20: "Package", } def read_pool(data): """The constant pool of a class: a list of (tag, value) pairs.""" if data[:4] != b"\xca\xfe\xba\xbe": raise ValueError("not a class file") count = struct.unpack_from(">H", data, 8)[0] pool = [] pos = 10 i = 1 while i < count: tag = data[pos] pos += 1 if tag == 1: length = struct.unpack_from(">H", data, pos)[0] pos += 2 pool.append((tag, data[pos:pos + length])) pos += length elif tag in (3, 4): pool.append((tag, data[pos:pos + 4])) pos += 4 elif tag in (5, 6): pool.append((tag, data[pos:pos + 8])) pos += 8 elif tag in (7, 8, 16, 19, 20): pool.append((tag, struct.unpack_from(">H", data, pos)[0])) pos += 2 elif tag in (9, 10, 11, 12, 17, 18): pool.append((tag, struct.unpack_from(">HH", data, pos))) pos += 4 elif tag == 15: pool.append((tag, data[pos:pos + 3])) pos += 3 else: raise ValueError("unknown tag %d at %d" % (tag, pos)) if tag in (5, 6): i += 2 else: i += 1 return pool, pos def printable(data): good = 0 for b in data: if 32 <= b < 127: good += 1 return good / max(1, len(data)) def crack(text): """The single byte key that makes a string readable, or None.""" best = None for key in range(1, 256): out = bytes(b ^ key for b in text) score = printable(out) if score > 0.98 and all(32 <= b < 127 or b in (9, 10, 13) for b in out): best = (key, out) break return best def main(argv): if len(argv) < 2: print(__doc__) return 1 for name in argv[1:]: path = Path(name) data = path.read_bytes() pool, _ = read_pool(data) strings = [v for t, v in pool if t == 1] readable = [s for s in strings if printable(s) > 0.95] hidden = [s for s in strings if printable(s) <= 0.95] print("=== %s: %d strings, %d readable, %d hidden" % (path.name, len(strings), len(readable), len(hidden))) for s in readable[:12]: print(" plain %r" % s[:60]) for s in hidden[:8]: cracked = crack(s) if cracked: print(" xor %r key 0x%02X" % (s[:40], cracked[0])) else: print(" hidden %r" % s[:40]) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))