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