"""Pure Python port of libksse static string deobfuscation helpers. Recovered native path: * FUN_00150ba0 wraps the decoded bytes into a C++ string. * FUN_001502f0 reads a little-endian uint16 length followed by encrypted bytes. * FUN_0014fe6c seeds three LFSR-like registers from bytes 4..7 of the seed. * FUN_00150128 emits one keystream byte and returns ``(ks + 3) ^ cipher``. The default seed literal is visible in libksse: ``Vuz4fCHxn1CO``. """ from __future__ import annotations from dataclasses import dataclass DEFAULT_KSSE_STRING_SEED = b"Vuz4fCHxn1CO" def _u32(value: int) -> int: return value & 0xFFFFFFFF @dataclass class KsseStringState: ac48: int ac4c: int ac50: int ac54: int = 0x80000062 ac58: int = 0x40000020 ac5c: int = 0x10000002 ac60: int = 0x7FFFFFFF ac64: int = 0x3FFFFFFF ac68: int = 0x0FFFFFFF ac6c: int = 0x80000000 ac70: int = 0xC0000000 ac74: int = 0xF0000000 def init_ksse_string_state(seed: bytes | str = DEFAULT_KSSE_STRING_SEED) -> KsseStringState: if isinstance(seed, str): seed = seed.encode("utf-8") if len(seed) > 20 or not seed: seed = b"quajdsfjasodfue" if len(seed) < 12: seed = seed + seed[: 12 - len(seed)] # Ghidra expression is a 32-bit byte swap of *(uint32_t *)(seed + 4). word = int.from_bytes(seed[4:8], "big") if word == 0: return KsseStringState(ac48=0x13579BDF, ac4c=0x2468ACE0, ac50=0xFDB97531) return KsseStringState(ac48=word, ac4c=word, ac50=word) def decode_ksse_string_byte(cipher_byte: int, state: KsseStringState) -> int: """Port sub_00150128 for a single byte.""" out = 0 bit6 = state.ac50 & 1 bit7 = state.ac4c & 1 reg50 = state.ac50 reg4c = state.ac4c for _ in range(8): if (state.ac48 & 1) == 0: state.ac48 = _u32(state.ac60 & (state.ac48 >> 1)) if (reg50 & 1) == 0: bit6 = 0 reg50 = _u32(state.ac68 & (reg50 >> 1)) state.ac50 = reg50 else: reg50 = _u32(((state.ac5c >> 1) ^ reg50) | state.ac74) bit6 = 1 state.ac50 = reg50 else: state.ac48 = _u32(((state.ac54 >> 1) ^ state.ac48) | state.ac6c) if (reg4c & 1) == 0: reg4c = _u32(state.ac64 & (reg4c >> 1)) bit7 = 0 state.ac4c = reg4c else: reg4c = _u32(((state.ac58 >> 1) ^ reg4c) | state.ac70) bit7 = 1 state.ac4c = reg4c out = ((bit6 ^ bit7) | ((out & 0x7F) << 1)) & 0xFF return ((out + 3) & 0xFF) ^ (cipher_byte & 0xFF) def decode_ksse_string_payload(payload: bytes, seed: bytes | str = DEFAULT_KSSE_STRING_SEED) -> bytes: state = init_ksse_string_state(seed) return bytes(decode_ksse_string_byte(item, state) for item in payload) def decode_ksse_string_blob(blob: bytes, seed: bytes | str = DEFAULT_KSSE_STRING_SEED) -> bytes: if len(blob) < 2: raise ValueError("encoded blob must contain a 16-bit length prefix") length = int.from_bytes(blob[:2], "little") payload = blob[2 : 2 + length] if len(payload) != length: raise ValueError(f"encoded blob length mismatch: need {length}, got {len(payload)}") return decode_ksse_string_payload(payload, seed) def pack_le_values(*items: tuple[int, int]) -> bytes: """Pack decompiler stack constants as little-endian byte fragments.""" return b"".join(value.to_bytes(size, "little") for value, size in items) __all__ = [ "DEFAULT_KSSE_STRING_SEED", "KsseStringState", "decode_ksse_string_blob", "decode_ksse_string_byte", "decode_ksse_string_payload", "init_ksse_string_state", "pack_le_values", ]