#!/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())