# Round 3 — extraction, crypto service, and the callback blind spot ## 1. Firmware component inventory — CORRECTS AN ASSUMPTION I assumed the service-1 (crypto) implementation lived in a driver we had never extracted (SOC_DRV / INTF_DRV / DBG_DRV / RAS_DRV). **Those do not exist on this part.** Dumping everything tinygrad parses gives six components and no more: PSP_FW_TYPE_PSP_SOS 99200 7afe3d67908efc15 PSP_FW_TYPE_PSP_SYS_DRV 161680 4a7d4b7cf480e357 PSP_FW_TYPE_PSP_KDB 5888 c4854e032ba87835 PSP_FW_TYPE_PSP_TOC 2304 ae31722e7e27f7fe PSP_FW_TYPE_PSP_SPL 672 583adb6f93104060 PSP_FW_TYPE_PSP_RL 2400 6f128b3d364c3616 tinygrad's loader silently skips the others (`if fw not in fw.sos_fw: return 0`), which is why the boot list mentions them. Saved to `psp_out/drivers/`. Consequence: the crypto service is implemented in **sOS** (or SYS_DRV), both of which we already had. No new binary to analyse -- and no new attack surface from extraction, contrary to what I expected. ## 2. Service-1 command ids — an earlier number was wrong A previous scan reported HDCP issuing service-1 RPCs with "cmd=0x1087". That came from a heuristic that walked backwards for any MOV with a value >= 0x100 and is WRONG. `0x1087` appears nowhere in sos/sys_drv/asd. The real marshalling (`sub_40176C`) is: MOVS R0, #0x10 ; STR R0,[SP] request[0] = 0x10 <- opcode MOVS R5, #2 ; STR R5,[SP,#4] request[1] = 2 STM R0!, {R1-R3} request[2..4] = buf, len, out MOVS R0, #1 ; SVC 0xF2 service 1 So service-1 opcodes are SMALL (0x10 here). The handler is one of sOS's small-id task dispatchers (sub_13448 / sub_17182) reached through the runtime `service_desc_table`, which is zero in the image and populated at boot. ## 3. The ASD callback vtable is genuinely unresolvable statically The indirect calls at `base+6152 / 6204 / 6208 / 6280 / 6312` (base = `dword_D115E0`) are the blind spot. Searched exhaustively: - stores with displacement 6100..6400 : 0 hits - references to the absolute addresses (0xd44098, 0xd440cc, 0xd440d0, 0xd44118, 0xd44138) : 0 hits The reason is structural, not a search failure: `dword_D115E0`'s static value (0xd42890) is only an initialiser. ASD's context is allocated at runtime, so `base+6152` has no static address and nothing can reference it. `sub_D115E4` DOES install a callback set from static pointers, but into a different table (`off_D11730 + 8` = 0xD447B0), and for most ASIC ids it installs **NULL**: case 1..0x2B (most): *v1 = 0; v1[1] = 0; v1[2] = 0; v1[3] = 0; case 0x1D/0x1E: sub_D1217E, sub_D11D24, sub_D15DF0 case 0x26: sub_D121E4, sub_D11D34, sub_D15E02 Behavioural evidence says the `base+6152` callbacks ARE installed at runtime (topology add with miracast=0 fails rather than returning 0, which is what a NULL callback would give). So they exist, we just cannot see them. Resolving them would need runtime disclosure of TEE memory, which we do not have. **This is a permanent blind spot for static analysis**, and cmd 0x18c hands one of these callbacks an attacker-controlled length (a3 up to 635) with an unknown destination size. ## Revised priorities 1. State-machine / lifecycle bugs. The only class that has produced a working result here (the cmd 0x0E missing-rollback session bypass), and the one I have spent least effort on. 2. The service-1 crypto boundary inside sOS -- reachable, and a host input already wedges it permanently. Requires locating the task dispatcher via the runtime service table. 3. The ASD callbacks -- parked. Not statically resolvable. ================================================================================ ## CORRECTION — the ASD callbacks ARE statically resolvable. I was wrong. I claimed above that the `base+6152/6204/6208/...` callback vtable was a permanent blind spot because "ASD's context is allocated at runtime". **That was wrong on both counts.** ### Why the earlier searches found nothing 1. `dword_D115E0` is NOT a runtime pointer. It is a LITERAL POOL ENTRY holding the constant `0xD42890` (`LDR R8, =0xD42890`). The context base is a FIXED BSS address; it reads as zero only because BSS is not stored in the file. 2. The vtable is accessed with REGISTER-OFFSET addressing, not an immediate displacement: MOVW R0, #0x1864 ; offset 6244 in a REGISTER LDR R1, [R2, R0] ; base + Rn BLX R1 My scans looked for `STR/LDR [base, #disp]` with disp in 6100..6400 and for references to the absolute addresses. Both are structurally incapable of seeing this form. Zero hits was a scan artifact, not evidence. ### The installer ASD cmd 0x02 -> sub_D02518 -> sub_D102B8: asd_memclr(0xD42890, 0x1F18) // zero the 7960-byte context *(DWORD*)(base + 6144) = asic_id sub_D0D2AC(base + 6148, asic_id) // <-- POPULATES THE VTABLE `sub_D0D2AC` (6950 bytes) is the 43-case ASIC switch I had already catalogued in an earlier round and never connected to the callbacks. ### Resolved vtable slots a1[15] = base+6208 cmd 0x18c cb2 (receives OUR length) 22 per-ASIC thunks, ALL converging on sub_D08090 a1[33] = base+6280 cmd 0x103 cb1 -> sub_D03318 / sub_D04DD6 / sub_D05E68 a1[41] = base+6312 cmd 0x103 cb2 -> sub_D05E58 ### Audit of the slot that takes attacker data `sub_D08090` (renamed `asd_cb_0x18c_impl`) is a DDC/AUX bit-banging loop: ```c v6 = a3 - 1; // a3 != 0 is checked -> no underflow while ( v8 != v6 ) { v12 = sub_D13E8C(reg, ...); // MMIO read SVC 0x7B sub_D196E4(reg, v12 & mask | (*(u8*)(a2 + v8++) << 16), ...); // MMIO write SVC 0x7C } ``` It clocks our bytes out to a display register one at a time. `a3` was validated by `asd_validate_ptr(a2, a3)` in cmd 0x18c, there is no destination buffer, and the `a3 - 1` cannot underflow. **No memory corruption.** ### Status Item 3 ("ASD callbacks -- parked, not statically resolvable") is WITHDRAWN. The vtable is resolved, the one slot fed attacker-controlled data is clean, and the remaining slots are now named and available for future auditing.