# AMD PSP (Navi31 / RX 7900 XTX) — Reverse-Engineering & Security Handover Self-contained handover package for the AMD Platform Security Processor firmware review. Everything needed to reload the reverse-engineering work into IDA and to reproduce the hardware findings. ## Start here * **`FINDINGS.md`** — the consolidated writeup: what was reversed, the full host-facing attack-surface characterisation, confirmed bugs and their true reach, rollback/version-diff results, the DRAM trust model, and retracted claims. Read this first. The detailed per-topic docs referenced from `FINDINGS.md` live in the parent `psp_out/` directory (46 markdown files). This package holds the summary plus the reusable artifacts. ## Contents ``` handover/ README.md this file FINDINGS.md consolidated findings writeup import_symbols.py re-apply all reversed symbols/structs to a fresh IDB elf/ the six reversed module ELFs (the exact builds the sos2.elf sys_drv2.elf symbols were extracted from — load these in IDA) asd2.elf hdcp2.elf dtm2.elf ras2.elf versions/ all archived firmware builds, per module, for sos/ sys_drv/ cross-version diffing & rollback (38 ELFs) asd/ hdcp/ VERSIONS.md symbols/ sos.json sys_drv.json asd.json hdcp.json dtm.json ras.json exported names, prototypes, comments, segments, types probes/ hardware probes & PoCs (see "Hardware" below) vport2.py vport_emit.py vdiff2.py cross-version symbol-port diff tooling mkelf.py wrap a raw PSP image in a minimal ELF for IDA ``` The `.i64` databases and many archived firmware versions are in `psp_out/` and `psp_out/elf/`; this package bundles the six reversed ELFs plus the symbol JSONs so the RE is fully reloadable standalone. ### ELF sha256 (the builds the symbols correspond to) ``` sos2.elf 81347067864b41114add1af94ca42c883877cc527d3e1b5904a3d6db64e47ec6 sys_drv2.elf 3ea10d443a571363859b9e7cbfbf84da6f2d50e3a175c7dc537de127fb80e8fc asd2.elf e0d0a344ea6378d696e816b6f577399f57c0d3346df8ff35d9f116b2d89b9bf9 hdcp2.elf b2a7b5e007bd9b570396c34119230e1b0ad863fa4eb31310be1e4d8fb9d14d82 dtm2.elf af22ef6243befdee4b85b0e427d9e269c13db39bc07e978eb5e57c6d3a139843 ras2.elf 0daf0c36ce0b0e45175ee13b263d0d43505b0b887a0327839c28d1ca7d2198ad ``` Live-firmware versions: sos/sys_drv **0.49.0.54**, asd 33.0.1.21, hdcp 23.0.0.82, dtm/ras as shipped. The symbols were reversed against these; they re-apply to other builds too but addresses will differ (use `vport_emit.py` to re-map). ## Reloading the symbols into IDA The reversed work is captured as JSON, not `.i64`, so it re-applies to a fresh, clean database of any build. 1. Open `elf/.elf` in IDA (auto-detects ARM/Thumb from the ELF), let auto-analysis finish. To reverse a *different* build, wrap its raw image first: `python3 mkelf.py .elf` bases: sos `0x0`, sys_drv `0xE00100`, asd `0xD00100`, hdcp/dtm/ras `0x400000` (hdcp `0x400100`). 3. `File > Script file...` -> `import_symbols.py`. It auto-detects the module from the root filename and applies the matching `symbols/.json`: struct/enum types, segment names+perms, function names + prototypes + comments, data names, and line comments. Idempotent. Applies ~3,470 function names, ~6,360 comments, and the custom structs across the six modules. Headless: `idat64 -A -Simport_symbols.py .elf` ## Hardware reproduction All hardware work ran through tinygrad's userspace AMD driver on an RX 7900 XTX over a USB4 / ASM2464PD PCIe bridge (`DEV=AMD`). Every probe is self-documenting (docstring explains the mechanism and the expected signal). **Always run `probes/psp_recover.py` first** — it power-cycles the PCIe link, which the card needs after a machine restart or a PSP hang, and before any rollback/DMA experiment. Key reproductions: | probe | shows | |---|---| | `test_rollback_51.py --fw51` | boots firmware 0.49.0.51 from a host-supplied signed container | | `test_tmr_range_guard.py [--fw51]` | the TMR range guard differential (.54 rejects, .51 accepts) | | `probe_tmr_fence.py` | the TMR memory-controller fence blocks host reads/writes | | `probe_tmr_writes_real.py` / `probe_tmr_footprint.py` | the SETUP_TMR DMA writes `0xFF`, mapping its footprint | | `probe_tmr_base_sweep.py --fw51 --base=...` | 4 GB base sweep: every reachable-safe target inert | | `probe_c2p_mailbox.py` | C2P mailbox is a runtime NAK stub (not reachable) | | `probe_ta_session_uaf.py` | TA session lifecycle is robust (no UAF/double-free) | Rollback needs a signed older container: fetch an older `amdgpu/psp_13_0_0_sos.bin` from linux-firmware git history (commit `36934f24c008` = 0.49.0.51), drop it where `test_rollback_51.py` expects it (`/tmp/lfw/36934f24c008.bin`). ## Version-diff tooling `vport2.py [--list]` classifies every function as IDENT / RELOC / MOVED / MOVED~ / DIVERGED against an older build, using alignment-independent mnemonic matching plus a 0.90 fuzzy threshold. Two false-positive modes are fixed (narrow index window; inline literal pools). Caveat: it *misses* genuinely-new boilerplate (RPC stubs) that closely resemble siblings — use an opcode/constant census for templated functions. `vport_emit.py` writes a `{name: {va, size, cls}}` map for porting addresses to a target build; `vdiff2.py ` prints an instruction diff of one diverged function. ## Environment notes * Symbols exported from IDA 9.x (`ida_typeinf` types API; `ida_struct` is gone). * Do **not** rely on absolute addresses across builds — SYS_DRV grew +4 KB between `.51` and `.54`; use `vport_emit.py` to re-map. * Do not fire destructive/error-injection commands at a live card without intent; `probe_tmr_base_sweep.py` has a safety rail refusing bases whose footprint reaches the `0xFFFD_FC000000` register block (brick risk).