#!/usr/bin/env python3 """First-pass structural triage of a PSP TA body: entry points and command surface. Every TA in this family shares a layout, established on HDCP 23.0.4.49 in idatui and re-checked here against the four TAs this project already knows: info header at body+0 (maps to 0x400100) +0x04 image base +0x10 entry trampoline +0x14 'TA'/'DR' class magic +0x1c session table +0x20 &__stack_chk_guard the trampoline dispatches on request[0] through a pointer table: 1 CreateEntryPoint 2 DestroyEntryPoint 3 OpenSessionEntryPoint 4 CloseSessionEntryPoint 5 InvokeCommandEntryPoint (4 args) `InvokeCommandEntryPoint` is the host-facing command surface. Its dispatcher is either a `cmp`-chain or a `tbb`/`tbh` jump table; both are enumerated here. usage: ta_triage.py [...] (add --commands for the command dump) """ import argparse import os import re import struct import sys import capstone as cs BASE = 0x400100 MD = cs.Cs(cs.CS_ARCH_ARM, cs.CS_MODE_THUMB) MD_ARM = cs.Cs(cs.CS_ARCH_ARM, cs.CS_MODE_ARM) def sweep_arm(b, start_va, n=0x40): """The entry trampoline is ARM, not Thumb (4-byte encodings), unlike the rest of the image. Decoding it as Thumb yields convincing garbage.""" off = start_va - BASE return list(MD_ARM.disasm(b[off:off + n * 4], start_va)) def sweep(b, start_va, end_va=None): """Linear sweep that resumes past undecodable words (data in .text).""" out = [] off = start_va - BASE end = (end_va - BASE) if end_va else len(b) - 2 while off < end: got = None for i in MD.disasm(b[off:off + 4], BASE + off): got = i break if got: out.append(got) off += got.size else: off += 2 return out def header(b): g = lambda o: struct.unpack_from(' len(b): break e = struct.unpack_from('= 5 and args.commands: ids, tbs = commands(b, eps[4]) print(' invoke dispatcher @%#x' % eps[4]) print(' cmp constants: %s' % [hex(x) for x in ids if x < 0x200]) for t in tbs: print(' jump table @%#x' % t) return 0 if __name__ == '__main__': raise SystemExit(main())