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/verify_gcp_params_nonrop_discriminator.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/verify_gcp_params_nonrop_discriminator.py')
| -rw-r--r-- | fw/verify_gcp_params_nonrop_discriminator.py | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/fw/verify_gcp_params_nonrop_discriminator.py b/fw/verify_gcp_params_nonrop_discriminator.py new file mode 100644 index 0000000..a40b41d --- /dev/null +++ b/fw/verify_gcp_params_nonrop_discriminator.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Offline-only FE0A comparator discriminator for two proposed GCP params VAs. + +This DOES NOT measure a live command-buffer VA and MUST NOT be treated as +permission to retry a hardware ROP chain. FE0A comparator REJECT (and +accepted diagnostic selector 11) call an ASD→SYS_DRV fixed-fabric-write +event path; this is NOT a read-only card probe. +No device requests or keys are used. +""" +from __future__ import annotations + +import struct +from pathlib import Path + +from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB + +ELF = Path(__file__).resolve().parents[1] / 'psp_out/versions_win/elf_all/gcp_22.0.0.77_7c426e7f.elf' +OLD_BASE = 0x04024000 +SHIFTED_BASE = 0x04035000 # fault-compatible HYPOTHESIS, not measured +CODE_VA = 0x00404A74 # signed Thumb function prologue +SECOND_CODE_VA = 0x00404A78 # unique follow-up word in signed ELF LOAD +ZERO_VA = 0x00415A74 # zero bytes inside signed ELF LOAD +COUNTER = 0x40000000 + + +def load_bytes(image: bytes, va: int, size: int) -> bytes: + assert image[:6] == b'\x7fELF\x01\x01' + phoff = struct.unpack_from('<I', image, 28)[0] + entsize, count = struct.unpack_from('<HH', image, 42) + for i in range(count): + typ, off, base, _, filesz, memsz, flags, _ = struct.unpack_from('<8I', image, phoff + entsize * i) + if typ == 1 and flags & 1 and base <= va and va + size <= base + filesz: + assert va + size <= base + memsz + return image[off + va - base:off + va - base + size] + raise ValueError(f'{va:#x} is not four initialized bytes in an executable LOAD') + + +def main() -> None: + image = ELF.read_bytes() + thumb = Cs(CS_ARCH_ARM, CS_MODE_THUMB) + # FE0A pre-selector comparator: R1=params+0x10, R2=*(params+0x824), + # ADD R1,R2 then LDRB [R1,#4]! => target=params+0x14+R2. + for va, mnemonic in ((0x40483A, 'ldr.w'), (0x40484C, 'ldm.w'), + (0x40486E, 'add'), (0x404870, 'ldrb.w'), + (0x404886, 'ldrd.w'), (0x40488A, 'subs'), + (0x404890, 'bhs')): + ins = next(thumb.disasm(load_bytes(image, va, 4), va, count=1)) + assert ins.address == va and ins.mnemonic.split('.')[0] == mnemonic.split('.')[0], (hex(va), ins.mnemonic) + # FE0A REJECT is not passive: GCP invokes its signed error emitter 3x. + # That wrapper's ASD 0x208 → SYS_DRV 0x1079 path can request a fixed + # fabric write at 0x03B30600; no card-side endpoint effect is known. + for va, mnemonic in ((0x40490A, 'ldr'), (0x404912, 'blx'), + (0x404918, 'blx'), (0x404924, 'blx')): + ins = next(thumb.disasm(load_bytes(image, va, 4), va, count=1)) + assert ins.address == va and ins.mnemonic == mnemonic, (hex(va), ins.mnemonic) + prologue = next(thumb.disasm(load_bytes(image, CODE_VA, 4), CODE_VA, count=1)) + assert prologue.mnemonic == 'push' + code_word = struct.unpack('<I', load_bytes(image, CODE_VA, 4))[0] + zero_word = struct.unpack('<I', load_bytes(image, ZERO_VA, 4))[0] + second = struct.unpack('<I', load_bytes(image, SECOND_CODE_VA, 4))[0] + assert code_word == 0x460DB5B0 and second == 0x4604A121 and zero_word == 0 + assert image.count(struct.pack('<I', second)) == 1, 'second word must identify one ELF position' + assert load_bytes(image, ZERO_VA + 4, 4) == bytes(4) + assert zero_word < COUNTER < code_word + disp = (CODE_VA - OLD_BASE - 0x14) & 0xffffffff + assert (OLD_BASE + 0x14 + disp) & 0xffffffff == CODE_VA + assert (SHIFTED_BASE + 0x14 + disp) & 0xffffffff == ZERO_VA + assert (OLD_BASE + 0x14 + disp + 4) & 0xffffffff == SECOND_CODE_VA + assert (SHIFTED_BASE + 0x14 + disp + 4) & 0xffffffff == ZERO_VA + 4 + # Correct a crucial missing prerequisite: ordinary input_len=0x1000 + # fails FE0A's signed 0x4048F4..FC bounds check BEFORE the comparator. + # Selectors 11..13 jump directly to diagnostic status 0x241 instead of + # entering FE0A's potentially huge memcpy on comparator ACCEPT. Even + # then, a live probe can fault for any third params-VA hypothesis. + input_len = 0xFFFFFFFF + assert disp + 4 <= input_len and not disp + 4 <= 0x1000 + for va, mnemonic in ((0x4048F4, 'ldr'), (0x4048FA, 'cmp'), + (0x404938, 'b'), (0x4049D8, 'ldr')): + ins = next(thumb.disasm(load_bytes(image, va, 4), va, count=1)) + assert ins.address == va and ins.mnemonic.split('.')[0] == mnemonic, (hex(va), ins.mnemonic) + print(f'signed .77 ELF: both targets are initialized mapped LOAD bytes; ' + f'old={CODE_VA:#x} ({prologue.mnemonic}), shifted={ZERO_VA:#x} (zero)') + print(f'FE0A displacement={disp:#x}; counter={COUNTER:#x}, FE09 seed would be {COUNTER + 1:#x}') + print(f'adjacent unique ELF dword={second:#x} at {SECOND_CODE_VA:#x}; ' + f'follow-up displacement={(disp+4)&0xffffffff:#x}') + print('A declared input_len=0xffffffff is REQUIRED to reach the comparator; ' + 'selector 11 avoids FE0A accepted-branch memcpy.') + print('If the signed FE07/08/09 gates and counter hold: ' + 'old params base -> ACCEPT status 0x241; shifted base -> REJECT 0x100.') + # GCP .77 sub_4062B4 puts E3, caller-code 3 and the passed 16-bit + # status into ASD 0x208 msg[1]. ASD .163 reorders the three parts and + # SYS_DRV 0x1079 filters only top bytes EE/EF (signed reports cited below). + forwarded = [] + for status in (0xF000, 0, 0x100): + v = (status << 16) | (3 << 8) | 0xE3 + packed = ((v & 0xff) << 24) | (((v >> 8) & 0xff) << 16) | ((v >> 16) & 0xffff) + assert packed >> 24 == 0xE3 and packed >> 24 not in (0xEE, 0xEF) + forwarded.append(packed) + print('CAUTION: FE0A REJECT can request three SYS_DRV 0x1079 fixed-fabric writes ' + 'at 0x03B30600, words '+', '.join(hex(v) for v in forwarded)) + print('ACCEPT with selector 11 also emits three error RPCs (final status 0x241); ' + 'both paths may touch the same fixed endpoint.') + print('This is NOT a passive live read; the private gate and target effect are unmeasured.') + print('This offline script alone cannot measure other bases or the live destination mapping; ' + 'bounded non-ROP hardware observations are documented separately, not ROP authorization.') + + +if __name__ == '__main__': + main() |
