| Mode | Name | Size | |
|---|---|---|---|
| -rw-r--r-- | FINDINGS.md | 10670 | logstatsplainblame |
| -rw-r--r-- | README.md | 6071 | logstatsplainblame |
| d--------- | elf | 311 | logstatsplain |
| -rw-r--r-- | import_symbols.py | 4391 | logstatsplainblame |
| -rw-r--r-- | mkelf.py | 1491 | logstatsplainblame |
| d--------- | probes | 639 | logstatsplain |
| d--------- | symbols | 221 | logstatsplain |
| -rw-r--r-- | vdiff2.py | 2923 | logstatsplainblame |
| -rw-r--r-- | vport2.py | 6629 | logstatsplainblame |
| -rw-r--r-- | vport_emit.py | 2986 | logstatsplainblame |
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.
- Open
elf/<module>.elfin 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 <raw.bin> <base_hex> <module>.elfbases: sos0x0, sys_drv0xE00100, asd0xD00100, hdcp/dtm/ras0x400000(hdcp0x400100). File > Script file...->import_symbols.py. It auto-detects the module from the root filename and applies the matchingsymbols/<module>.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 <module>.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 <new.elf> <funcs.json> <old.elf> [--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 <new> <funcs> <old> <func> prints an instruction diff of
one diverged function.
Environment notes
- Symbols exported from IDA 9.x (
ida_typeinftypes API;ida_structis gone). - Do not rely on absolute addresses across builds — SYS_DRV grew +4 KB between
.51and.54; usevport_emit.pyto re-map. - Do not fire destructive/error-injection commands at a live card without intent;
probe_tmr_base_sweep.pyhas a safety rail refusing bases whose footprint reaches the0xFFFD_FC000000register block (brick risk).
