diff --git a/MSTAR/FindKeyBox/find_keybox.py b/MSTAR/FindKeyBox/find_keybox.py new file mode 100644 index 0000000..9be3716 --- /dev/null +++ b/MSTAR/FindKeyBox/find_keybox.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +import mmap +import os +import sys +import hashlib +from typing import List, Iterable, Tuple + +PREFIX_HEX = "4D 53 54 41 52 5F 53 45 43 55 52 45 5F 53 54 4F 52 45 5F 46 49 4C 45 5F 4D 41 47 49 43 5F 49 44" + +POSTFIX_HEX_LIST = [ + "00 00", +] + +EXTRACT_LEN = 228 +OUT_BASE = "keybox.bin" +PRINT_HEXVIEW = True +HEXVIEW_WIDTH = 16 +MAX_HITS = 0 + +REQUIRE_POSTFIX_AT_EXTRACT_END = True + +MAX_ZERO_FRACTION = 0.25 +MAX_ZERO_RUN = 16 +TAIL_START = 0x90 +MIN_TAIL_NONZERO_RATIO = 0.70 + + +def clean_hex(s: str) -> bytes: + s = s.replace("0x", "").replace("0X", "") + s = "".join(s.split()) + if not s: + return b"" + if len(s) % 2 != 0: + raise ValueError("Hex string must have an even number of hex digits.") + return bytes.fromhex(s) + + +def iter_all(mm: mmap.mmap, needle: bytes, start: int = 0) -> Iterable[int]: + i = start + while True: + pos = mm.find(needle, i) + if pos == -1: + return + yield pos + i = pos + 1 + + +def safe_out_name(base: str, idx: int) -> str: + root, ext = os.path.splitext(base) + if idx == 1: + return base + return f"{root}_{idx-1}{ext}" + + +def is_printable_ascii(b: int) -> bool: + return 32 <= b <= 126 + + +def hexview(data: bytes, base_offset: int = 0, width: int = 16) -> str: + lines = [] + for i in range(0, len(data), width): + chunk = data[i:i + width] + hex_part = " ".join(f"{x:02X}" for x in chunk) + hex_part_padded = hex_part.ljust(width * 3 - 1) + ascii_part = "".join(chr(x) if is_printable_ascii(x) else "." for x in chunk) + lines.append(f"{base_offset + i:08X} {hex_part_padded} |{ascii_part}|") + return "\n".join(lines) + + +def best_postfix_window(postfixes: List[bytes]) -> int: + return max((len(p) for p in postfixes if p), default=0) + + +def find_postfix_after_extract(mm: mmap.mmap, postfixes: List[bytes], prefix_pos: int, file_size: int) -> Tuple[int, bytes]: + start = prefix_pos + EXTRACT_LEN + if start >= file_size: + return -1, b"" + window = best_postfix_window(postfixes) + end = min(file_size, start + window) + for pf in postfixes: + if not pf: + continue + if start + len(pf) > file_size: + continue + pos = mm.find(pf, start, end) + if pos != -1: + return pos, pf + return -1, b"" + + +def max_zero_run(b: bytes) -> int: + best = 0 + cur = 0 + for x in b: + if x == 0: + cur += 1 + if cur > best: + best = cur + else: + cur = 0 + return best + + +def zero_fraction(b: bytes) -> float: + if not b: + return 1.0 + z = sum(1 for x in b if x == 0) + return z / len(b) + + +def nonzero_ratio(b: bytes) -> float: + if not b: + return 0.0 + nz = sum(1 for x in b if x != 0) + return nz / len(b) + + +def passes_filters(block: bytes) -> bool: + if zero_fraction(block) > MAX_ZERO_FRACTION: + return False + if max_zero_run(block) > MAX_ZERO_RUN: + return False + tail = block[TAIL_START:] if TAIL_START < len(block) else b"" + if tail and nonzero_ratio(tail) < MIN_TAIL_NONZERO_RATIO: + return False + return True + + +def main() -> int: + if len(sys.argv) != 2: + print("Usage: python find_keybox.py ", file=sys.stderr) + return 2 + + path = sys.argv[1] + if not os.path.isfile(path): + print("ERROR: file not found", file=sys.stderr) + return 2 + + try: + prefix = clean_hex(PREFIX_HEX) + postfixes = [clean_hex(x) for x in POSTFIX_HEX_LIST] + except ValueError as e: + print(f"ERROR: {e}", file=sys.stderr) + return 2 + + postfixes = [p for p in postfixes if p] + if not prefix or not postfixes or EXTRACT_LEN <= 0: + return 2 + + print(f"EXTRACT_LEN={EXTRACT_LEN} bytes (0x{EXTRACT_LEN:X})") + + file_size = os.path.getsize(path) + saved = 0 + seen = set() + + with open(path, "rb") as f: + mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) + try: + for ppos in iter_all(mm, prefix, 0): + if ppos + EXTRACT_LEN > file_size: + continue + + qpos, qpf = find_postfix_after_extract(mm, postfixes, ppos, file_size) + if qpos == -1: + continue + + if REQUIRE_POSTFIX_AT_EXTRACT_END and qpos != (ppos + EXTRACT_LEN): + continue + + block = mm[ppos:ppos + EXTRACT_LEN] + + if not passes_filters(block): + continue + + h = hashlib.sha256(block).digest() + if h in seen: + continue + seen.add(h) + + saved += 1 + out_name = safe_out_name(OUT_BASE, saved) + with open(out_name, "wb") as out: + out.write(block) + + print(f"[hit {saved}] wrote {len(block)} bytes -> {out_name} | prefix@0x{ppos:X} postfix@0x{qpos:X}") + + if PRINT_HEXVIEW: + print(hexview(block, base_offset=ppos, width=HEXVIEW_WIDTH)) + print() + + if MAX_HITS and saved >= MAX_HITS: + break + finally: + mm.close() + + return 0 if saved else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/MSTAR/FindKeyBox/test_emmc.bin b/MSTAR/FindKeyBox/test_emmc.bin new file mode 100644 index 0000000..eabbb00 Binary files /dev/null and b/MSTAR/FindKeyBox/test_emmc.bin differ