diff options
| author | user <user@clank> | 2026-10-09 16:11:31 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-10-09 16:11:31 +0200 |
| commit | bc43149d914325e15dc0490dee9d982e21287623 (patch) | |
| tree | a632cf70de742d5a60d01c9c0d806bf5b9257538 /fw/scan_rpc_opcode_immediates.py | |
| parent | GCP->ASD 0x403: ASD-internal chain PROVEN end-to-end in emulator (real handlers) (diff) | |
| download | amd-psp-re-main.tar.gz amd-psp-re-main.tar.xz amd-psp-re-main.zip | |
Diffstat (limited to 'fw/scan_rpc_opcode_immediates.py')
| -rw-r--r-- | fw/scan_rpc_opcode_immediates.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/fw/scan_rpc_opcode_immediates.py b/fw/scan_rpc_opcode_immediates.py new file mode 100644 index 0000000..1a1ce12 --- /dev/null +++ b/fw/scan_rpc_opcode_immediates.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Candidate direct service-RPC opcode immediates in extracted signed ELF code. + +This halfword-resynchronizing Thumb-2 candidate scan is NOT a control-flow +proof: inline data and instruction interiors can yield false positives, and +computed opcodes or literal-pool loads can be missed. Immediate hits are +NOT necessarily opcodes: HDCP .52 has `MOVW #0x20B` for a 523-byte message +length comparison at 0x402798, not an ASD RPC. Inspect reported sites in +IDA and trace requests before assigning producer/caller provenance. +""" +from __future__ import annotations + +import argparse +import struct +from pathlib import Path + +from capstone import CS_ARCH_ARM, CS_MODE_THUMB, Cs +from capstone.arm import ARM_OP_IMM + +DEFAULT_OPCODE = 0x3105 + + +def executable_spans(path: Path): + image = path.read_bytes() + if len(image) < 52 or image[:5] != b'\x7fELF\x01' or image[5] != 1: + return [] + phoff = struct.unpack_from('<I', image, 28)[0] + entsize, count = struct.unpack_from('<HH', image, 42) + spans = [] + for i in range(count): + at = phoff + i * entsize + if at + 32 > len(image): + break + kind, off, va, _, filesz, _, flags, _ = struct.unpack_from('<8I', image, at) + if kind == 1 and flags & 1 and off + filesz <= len(image): + spans.append((va, image[off:off+filesz])) + return spans + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument('paths', type=Path, nargs='+') + ap.add_argument('--opcode', type=lambda v: int(v, 0), default=DEFAULT_OPCODE, + help='unsigned 32-bit opcode (default 0x3105)') + ap.add_argument('--literals', action='store_true', + help='also list aligned executable-span words equal to opcode') + args = ap.parse_args() + opcode = args.opcode + if not (0x100 <= opcode <= 0xffffffff): + ap.error('opcode must fit 32 bits and exceed a Thumb movs immediate') + files = sorted({f for p in args.paths for f in ([p] if p.is_file() else p.rglob('*.elf'))}) + md = Cs(CS_ARCH_ARM, CS_MODE_THUMB) + md.detail = True + for file in files: + for va, data in executable_spans(file): + # These opcodes cannot fit a 16-bit Thumb mov-imm. Try 32-bit + # prefixes at *each* halfword, so invalid instructions + # cannot truncate the scan. Candidate alignment still needs IDA. + for off in range((-va) & 1, len(data) - 3, 2): + if data[off+1] < 0xE8: + continue + ins = next(md.disasm(data[off:off+4], va+off, count=1), None) + if ins and ins.size == 4 and ins.mnemonic in ( + 'mov', 'movs', 'movw', 'mvn', 'addw', 'subw' + ) and any(o.type == ARM_OP_IMM and (o.imm & 0xffffffff) == opcode + for o in ins.operands): + print(f'{file}\t0x{ins.address:x}\t{ins.mnemonic} {ins.op_str}') + if args.literals: + for off in range((-va) & 3, len(data) - 3, 4): + if struct.unpack_from('<I', data, off)[0] == opcode: + print(f'{file}\t0x{va+off:x}\taligned literal 0x{opcode:x} (xref unverified)') + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) |
