Audit PPR offsets and add firmware scanners

This commit is contained in:
Roman Tarasov committed 2026-09-14 02:17:39 +03:00
1 parent 0db6795877
commit ed416c49bb
63 files changed
+1153 -87

No files matched your search

+1
View File
@@ -24,6 +24,7 @@ refs/*
/ps5-kstuff/debug-reader.elf
/ps5-kstuff/.build-config
logs/
__pycache__/
*.bin
*.elf
*.o
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x574553)
DEF(store_rax_rdi, -0xA7602E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (10.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x134820)
DEF(ppr_pfs_get_cmac_index, -0x1346c0)
DEF(ppr_pfs_get_xts_return, -0x907687)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x574553)
DEF(store_rax_rdi, -0xA7602E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (10.01.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x134820)
DEF(ppr_pfs_get_cmac_index, -0x1346c0)
DEF(ppr_pfs_get_xts_return, -0x907687)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5743D3)
DEF(store_rax_rdi, -0xA7602E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (10.20.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x134560)
DEF(ppr_pfs_get_cmac_index, -0x134400)
DEF(ppr_pfs_get_xts_return, -0x907687)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5743D3)
DEF(store_rax_rdi, -0xA7602E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (10.40.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x134560)
DEF(ppr_pfs_get_cmac_index, -0x134400)
DEF(ppr_pfs_get_xts_return, -0x907687)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5731D3)
DEF(store_rax_rdi, -0xA74E2E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (10.60.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x134580)
DEF(ppr_pfs_get_cmac_index, -0x134420)
DEF(ppr_pfs_get_xts_return, -0x906487)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x57F5B3)
DEF(store_rax_rdi, -0xA8AEAE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (11.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12fbd0)
DEF(ppr_pfs_get_cmac_index, -0x12fa70)
DEF(ppr_pfs_get_xts_return, -0x918d07)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x57F5B3)
DEF(store_rax_rdi, -0xA8AEAE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 11.20.elf.
DEF(ppr_pfs_get_xts_index, -0x12fbd0)
DEF(ppr_pfs_get_cmac_index, -0x12fa70)
DEF(ppr_pfs_get_xts_return, -0x918d07)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x57EBA3)
DEF(store_rax_rdi, -0xA8A52E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (11.40.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12efe0)
DEF(ppr_pfs_get_cmac_index, -0x12ee80)
DEF(ppr_pfs_get_xts_return, -0x9182f7)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fee)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.00.elf.
DEF(ppr_pfs_get_xts_index, -0xf5100)
DEF(ppr_pfs_get_cmac_index, -0xf4fa0)
DEF(ppr_pfs_get_xts_return, -0x78f76f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fee)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.01.elf.
DEF(ppr_pfs_get_xts_index, -0xf5090)
DEF(ppr_pfs_get_cmac_index, -0xf4f30)
DEF(ppr_pfs_get_xts_return, -0x78f76f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fee)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.02.elf.
DEF(ppr_pfs_get_xts_index, -0xf5100)
DEF(ppr_pfs_get_cmac_index, -0xf4fa0)
DEF(ppr_pfs_get_xts_return, -0x78f76f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fee)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.05.elf.
DEF(ppr_pfs_get_xts_index, -0xf4890)
DEF(ppr_pfs_get_cmac_index, -0xf4730)
DEF(ppr_pfs_get_xts_return, -0x78f39f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fae)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.10.elf.
DEF(ppr_pfs_get_xts_index, -0xf4870)
DEF(ppr_pfs_get_cmac_index, -0xf4710)
DEF(ppr_pfs_get_xts_return, -0x78f35f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fae)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.11.elf.
DEF(ppr_pfs_get_xts_index, -0xf4710)
DEF(ppr_pfs_get_cmac_index, -0xf45b0)
DEF(ppr_pfs_get_xts_return, -0x78f35f)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fae)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.12.elf.
DEF(ppr_pfs_get_xts_index, -0xf45c0)
DEF(ppr_pfs_get_cmac_index, -0xf4460)
DEF(ppr_pfs_get_xts_return, -0x78f2ff)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fae)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.13.elf.
DEF(ppr_pfs_get_xts_index, -0xf45f0)
DEF(ppr_pfs_get_cmac_index, -0xf4490)
DEF(ppr_pfs_get_xts_return, -0x78f2ff)
+1 -1
View File
@@ -80,7 +80,7 @@ DEF(store_rax_rdi, -0x945fae)
// hook field therefore names a verified raw INT3 byte in retail RX text.
DEF(syscall_cfi_table_jmp_int3, -0xb2fff0)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 1.14.elf.
DEF(ppr_pfs_get_xts_index, -0xf4040)
DEF(ppr_pfs_get_cmac_index, -0xf3ee0)
DEF(ppr_pfs_get_xts_return, -0x78f2ff)
+1 -1
View File
@@ -74,7 +74,7 @@ DEF(cr0_write_ret, -0x4f6037)
DEF(store_rax_rdi, -0x96e1ee)
DEF(syscall_cfi_table_jmp_int3, -0x903018)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 2.00.elf.
DEF(ppr_pfs_get_xts_index, -0x107640)
DEF(ppr_pfs_get_cmac_index, -0x1074e0)
DEF(ppr_pfs_get_xts_return, -0x8112df)
+1 -1
View File
@@ -74,7 +74,7 @@ DEF(cr0_write_ret, -0x4f5e87)
DEF(store_rax_rdi, -0x96e1ee)
DEF(syscall_cfi_table_jmp_int3, -0x902f28)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 2.20.elf.
DEF(ppr_pfs_get_xts_index, -0x107180)
DEF(ppr_pfs_get_cmac_index, -0x107020)
DEF(ppr_pfs_get_xts_return, -0x81129f)
+1 -1
View File
@@ -74,7 +74,7 @@ DEF(cr0_write_ret, -0x4f5dd7)
DEF(store_rax_rdi, -0x96e1ee)
DEF(syscall_cfi_table_jmp_int3, -0x903440)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 2.25.elf.
DEF(ppr_pfs_get_xts_index, -0x1070d0)
DEF(ppr_pfs_get_cmac_index, -0x106f70)
DEF(ppr_pfs_get_xts_return, -0x81129f)
+1 -1
View File
@@ -74,7 +74,7 @@ DEF(cr0_write_ret, -0x4f5dd7)
DEF(store_rax_rdi, -0x96e1ee)
DEF(syscall_cfi_table_jmp_int3, -0x902f30)
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 2.26.elf.
DEF(ppr_pfs_get_xts_index, -0x1070a0)
DEF(ppr_pfs_get_cmac_index, -0x106f40)
DEF(ppr_pfs_get_xts_return, -0x81129f)
+1 -1
View File
@@ -77,7 +77,7 @@ DEF(cr0_write_ret, -0x4F5B07) // IDA 0x67A4F9
DEF(store_rax_rdi, -0x96E52E) // IDA 0x201AD2
// PPR/fPKG continuations and wrapper-local ABI sites.
// PPR/fPKG offsets statically revalidated against retail 2.30.elf.
DEF(ppr_pfs_get_xts_index, -0x106c90)
DEF(ppr_pfs_get_cmac_index, -0x106b30)
DEF(ppr_pfs_get_xts_return, -0x8115bf)
+1 -1
View File
@@ -77,7 +77,7 @@ DEF(cr0_write_ret, -0x4f5a87)
DEF(store_rax_rdi, -0x96e42e)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 2.50.elf.
DEF(ppr_pfs_get_xts_index, -0x1069f0)
DEF(ppr_pfs_get_cmac_index, -0x106890)
DEF(ppr_pfs_get_xts_return, -0x81149f)
+1 -1
View File
@@ -77,7 +77,7 @@ DEF(cr0_write_ret, -0x4f5a87)
DEF(store_rax_rdi, -0x96e42e)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 2.70.elf.
DEF(ppr_pfs_get_xts_index, -0x1069f0)
DEF(ppr_pfs_get_cmac_index, -0x106890)
DEF(ppr_pfs_get_xts_return, -0x81149f)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x4C7AD3)
DEF(store_rax_rdi, -0x9568EE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (3.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x115f00)
DEF(ppr_pfs_get_cmac_index, -0x115da0)
DEF(ppr_pfs_get_xts_return, -0x85641f)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x4C7A93)
DEF(store_rax_rdi, -0x9568EE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (3.10.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x115ec0)
DEF(ppr_pfs_get_cmac_index, -0x115d60)
DEF(ppr_pfs_get_xts_return, -0x8563df)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x4C7743)
DEF(store_rax_rdi, -0x9565AE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (3.20.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x115a00)
DEF(ppr_pfs_get_cmac_index, -0x1158a0)
DEF(ppr_pfs_get_xts_return, -0x85608f)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x4C7743)
DEF(store_rax_rdi, -0x9565AE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (3.21.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x115a00)
DEF(ppr_pfs_get_cmac_index, -0x1158a0)
DEF(ppr_pfs_get_xts_return, -0x85608f)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x52DCF3)
DEF(store_rax_rdi, -0x9D716E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (4.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x116f90)
DEF(ppr_pfs_get_cmac_index, -0x116e30)
DEF(ppr_pfs_get_xts_return, -0x86fa22)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x52DCA3)
DEF(store_rax_rdi, -0x9D716E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (4.02.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x116f40)
DEF(ppr_pfs_get_cmac_index, -0x116de0)
DEF(ppr_pfs_get_xts_return, -0x86fa22)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x52dc13)
DEF(store_rax_rdi, -0x9d716e)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 4.03.bin.
DEF(ppr_pfs_get_xts_index, -0x116eb0)
DEF(ppr_pfs_get_cmac_index, -0x116d50)
DEF(ppr_pfs_get_xts_return, -0x86fa22)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x52DB03)
DEF(store_rax_rdi, -0x9D716E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (4.50.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x116cf0)
DEF(ppr_pfs_get_cmac_index, -0x116b90)
DEF(ppr_pfs_get_xts_return, -0x86fa52)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x52D763)
DEF(store_rax_rdi, -0x9D716E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (4.51.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x1168c0)
DEF(ppr_pfs_get_cmac_index, -0x116760)
DEF(ppr_pfs_get_xts_return, -0x86fa52)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x542983)
DEF(store_rax_rdi, -0xA0C2EE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 5.00.elf.
DEF(ppr_pfs_get_xts_index, -0x1257a0)
DEF(ppr_pfs_get_cmac_index, -0x125640)
DEF(ppr_pfs_get_xts_return, -0x8a1aa2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x542983)
DEF(store_rax_rdi, -0xA0C2EE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (5.02.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x1257a0)
DEF(ppr_pfs_get_cmac_index, -0x125640)
DEF(ppr_pfs_get_xts_return, -0x8a1aa2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5427B3)
DEF(store_rax_rdi, -0xA0C2EE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (5.10.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x125400)
DEF(ppr_pfs_get_cmac_index, -0x1252a0)
DEF(ppr_pfs_get_xts_return, -0x8a1aa2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5419C3)
DEF(store_rax_rdi, -0xA0C32E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (5.50.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x124610)
DEF(ppr_pfs_get_cmac_index, -0x1244b0)
DEF(ppr_pfs_get_xts_return, -0x8a19f2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x54A053)
DEF(store_rax_rdi, -0xA22B6E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 6.00.01.elf.
DEF(ppr_pfs_get_xts_index, -0x11f200)
DEF(ppr_pfs_get_cmac_index, -0x11f0a0)
DEF(ppr_pfs_get_xts_return, -0x8b4a52)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x54A073)
DEF(store_rax_rdi, -0xA22B6E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (6.02.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x11f220)
DEF(ppr_pfs_get_cmac_index, -0x11f0c0)
DEF(ppr_pfs_get_xts_return, -0x8b4a52)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549EB3)
DEF(store_rax_rdi, -0xA22B6E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (6.50.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x11eab0)
DEF(ppr_pfs_get_cmac_index, -0x11e950)
DEF(ppr_pfs_get_xts_return, -0x8b4a32)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549643)
DEF(store_rax_rdi, -0xA12F2E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (7.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12c170)
DEF(ppr_pfs_get_cmac_index, -0x12c010)
DEF(ppr_pfs_get_xts_return, -0x8a7982)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549643)
DEF(store_rax_rdi, -0xA12F2E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 7.01.01.elf.
DEF(ppr_pfs_get_xts_index, -0x12c170)
DEF(ppr_pfs_get_cmac_index, -0x12c010)
DEF(ppr_pfs_get_xts_return, -0x8a7982)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549343)
DEF(store_rax_rdi, -0xA12CEE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (7.20.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12b170)
DEF(ppr_pfs_get_cmac_index, -0x12b010)
DEF(ppr_pfs_get_xts_return, -0x8a76a2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549343)
DEF(store_rax_rdi, -0xA12CEE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (7.40.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12b170)
DEF(ppr_pfs_get_cmac_index, -0x12b010)
DEF(ppr_pfs_get_xts_return, -0x8a76a2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549203)
DEF(store_rax_rdi, -0xA12CEE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (7.60.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x12ab60)
DEF(ppr_pfs_get_cmac_index, -0x12aa00)
DEF(ppr_pfs_get_xts_return, -0x8a7692)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x549203)
DEF(store_rax_rdi, -0xa12cee)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 7.61.elf.
DEF(ppr_pfs_get_xts_index, -0x12ab60)
DEF(ppr_pfs_get_cmac_index, -0x12aa00)
DEF(ppr_pfs_get_xts_return, -0x8a7692)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5580C3)
DEF(store_rax_rdi, -0xA3016E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (8.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x132fc0)
DEF(ppr_pfs_get_cmac_index, -0x132e60)
DEF(ppr_pfs_get_xts_return, -0x8c8622)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x5570F3)
DEF(store_rax_rdi, -0xA3016E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 8.20.02.elf.
DEF(ppr_pfs_get_xts_index, -0x131b30)
DEF(ppr_pfs_get_cmac_index, -0x1319d0)
DEF(ppr_pfs_get_xts_return, -0x8c8622)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x555D83)
DEF(store_rax_rdi, -0xA2EE6E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (8.40.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x1318c0)
DEF(ppr_pfs_get_cmac_index, -0x131760)
DEF(ppr_pfs_get_xts_return, -0x8c7322)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x555CF3)
DEF(store_rax_rdi, -0xA2EE6E)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (8.60.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x131640)
DEF(ppr_pfs_get_cmac_index, -0x1314e0)
DEF(ppr_pfs_get_xts_return, -0x8c7322)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x56AC13)
DEF(store_rax_rdi, -0xA5A3AE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (9.00.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x135300)
DEF(ppr_pfs_get_cmac_index, -0x1351a0)
DEF(ppr_pfs_get_xts_return, -0x8f0352)
+3 -3
View File
@@ -76,9 +76,9 @@ DEF(cr0_write_ret, -0x56AAC3)
DEF(store_rax_rdi, -0xA5A3AE)
// PPR/fPKG offsets verified against the matching kernel image.
DEF(ppr_pfs_get_xts_index, -0x135300)
DEF(ppr_pfs_get_cmac_index, -0x1351a0)
// PPR/fPKG offsets statically revalidated against retail 9.05.elf.
DEF(ppr_pfs_get_xts_index, -0x1350b0)
DEF(ppr_pfs_get_cmac_index, -0x134f50)
DEF(ppr_pfs_get_xts_return, -0x8f0352)
DEF(ppr_pfs_get_cmac_return, -0x8f0304)
DEF(ppr_pfs_cleanup_keys, -0x8f8420)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x56B283)
DEF(store_rax_rdi, -0xA5A3AE)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets are devkit-derived (9.20.elf); retail is not validated.
DEF(ppr_pfs_get_xts_index, -0x1352c0)
DEF(ppr_pfs_get_cmac_index, -0x135160)
DEF(ppr_pfs_get_xts_return, -0x8f0362)
+1 -1
View File
@@ -77,7 +77,7 @@ DEF(cr0_write_ret, -0x56ada3)
DEF(store_rax_rdi, -0xa5a3ae)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 9.40.elf.
DEF(ppr_pfs_get_xts_index, -0x134d50)
DEF(ppr_pfs_get_cmac_index, -0x134bf0)
DEF(ppr_pfs_get_xts_return, -0x8f01d2)
+1 -1
View File
@@ -76,7 +76,7 @@ DEF(cr0_write_ret, -0x56abe3)
DEF(store_rax_rdi, -0xa5a3ae)
// PPR/fPKG offsets verified against the matching kernel image.
// PPR/fPKG offsets statically revalidated against retail 9.60.elf.
DEF(ppr_pfs_get_xts_index, -0x134b70)
DEF(ppr_pfs_get_cmac_index, -0x134a10)
DEF(ppr_pfs_get_xts_return, -0x8f01d2)
+5 -4
View File
@@ -89,10 +89,11 @@ KDATA_OFFSET(syscall_cfi_table_jmp_int3)
ABSOLUTE_OFFSET(p_sysent)
// Optional PPR/fPKG porting inventory. These entries are populated for the
// supported retail firmwares from 1.00 through 11.40. Keeping them in the
// common table removes firmware-local address arithmetic from the UELF while
// allowing newer, not-yet-ported tables to remain valid. ShellCore-relative
// values stay with the existing ps5-kstuff/shellcore_patches tables.
// firmware tables from 1.00 through 11.40. Each populated header says whether
// its values were revalidated against retail or are still devkit-derived.
// Keeping them in the common table removes firmware-local address arithmetic
// from the UELF while allowing newer, not-yet-ported tables to remain valid.
// ShellCore-relative values stay in ps5-kstuff/shellcore_patches.
OPTIONAL_KDATA_OFFSET(ppr_pfs_get_xts_index)
OPTIONAL_KDATA_OFFSET(ppr_pfs_get_cmac_index)
OPTIONAL_KDATA_OFFSET(ppr_pfs_get_xts_return)
+256 -18
View File
@@ -550,6 +550,8 @@ static uint64_t remote_syscall(int pid, int nr,
#define SYS_mlock 203
#define SYS_mdbg_call 573
#define SYS_dynlib_dlsym 591
#define SYS_dynlib_get_list 592
#define SYS_dynlib_get_info_ex 608
#define SYS_dynlib_load_prx 594
#define SYS_get_self_auth_info 607
@@ -596,8 +598,9 @@ struct shellcore_patch
struct shellcore_fpkg_offsets
{
/* Retained as per-firmware provenance for the former direct-call/cave
* implementation. The scoped GOT wrappers intentionally do not use it. */
/* Early ShellCore builds leave the package API GOT slots lazy until the
* first game launch. Those builds use verified direct call sites and a
* small executable tail trampoline instead of rewriting unresolved GOT. */
uint64_t ppr_call;
uint64_t ppr_cave;
uint64_t ppr_cave_size;
@@ -606,6 +609,9 @@ struct shellcore_fpkg_offsets
uint64_t pread_plt;
uint64_t mount_ppr_pkg_plt;
uint64_t getpid_plt;
uint64_t game_mount_calls[4];
uint64_t game_umount_calls[4];
uint64_t ppr_umount_call;
};
/*
@@ -886,6 +892,15 @@ static int replace_shellcore_blob_u64(unsigned char* blob, size_t blob_size,
return count == expected_count ? 0 : -1;
}
static int shellcore_bytes_equal(const unsigned char* lhs,
const unsigned char* rhs, size_t size)
{
for(size_t i = 0; i < size; i++)
if(lhs[i] != rhs[i])
return 0;
return 1;
}
static int find_shellcore_blob_entry(const unsigned char* blob,
size_t blob_size, uint64_t marker,
size_t* entry_offset)
@@ -934,6 +949,170 @@ static int resolve_shellcore_plt(uint64_t shellcore_base,
return 0;
}
static int resolve_shellcore_syscall_trampoline(
int pid, uint64_t dmap, uint64_t cr3,
struct shellcore_locked_pages* locked, uint64_t* target)
{
enum { MAX_MODULES = 128 };
uint32_t handles[MAX_MODULES];
size_t module_count = MAX_MODULES;
static const char getpid_name[] = "getpid";
static const unsigned char expected[] = {
0x48, 0xc7, 0xc0, 0x14, 0x00, 0x00, 0x00, /* mov eax, SYS_getpid */
0x49, 0x89, 0xca, /* mov r10, rcx */
0x0f, 0x05 /* syscall */
};
if(remote_syscall(pid, SYS_dynlib_get_list, (uint64_t)handles,
MAX_MODULES, (uint64_t)&module_count, 0, 0, 0)
|| !module_count || module_count > MAX_MODULES)
return -1;
for(size_t i = 0; i < module_count; i++)
{
uint64_t getpid_target = 0;
if(remote_syscall(pid, SYS_dynlib_dlsym, handles[i],
(uint64_t)getpid_name,
(uint64_t)&getpid_target, 0, 0, 0)
|| getpid_target < 0x10000
|| getpid_target >= 0x0000800000000000ull)
continue;
unsigned char code[sizeof(expected)];
if(lock_shellcore_range(pid, getpid_target, sizeof(code), locked)
|| phys_copyout(code, getpid_target, sizeof(code), dmap, cr3)
|| !shellcore_bytes_equal(code, expected, sizeof(expected)))
continue;
*target = getpid_target + 7;
return 0;
}
return -1;
}
struct shellcore_direct_call
{
uint64_t offset;
unsigned int api;
};
static int install_shellcore_direct_fpkg_calls(
int pid, uint64_t shellcore_base, uint64_t dmap, uint64_t cr3,
const struct shellcore_fpkg_offsets* sc,
const uint64_t api_plt[4], const uint64_t api_wrapper[4],
struct shellcore_locked_pages* locked)
{
enum { API_COUNT = 4, TRAMPOLINE_SIZE = 16, MAX_CALLS = 10 };
struct shellcore_direct_call calls[MAX_CALLS];
unsigned char original[MAX_CALLS][5];
unsigned char patched[MAX_CALLS][5];
unsigned char trampolines[API_COUNT * TRAMPOLINE_SIZE];
unsigned char current[sizeof(trampolines)];
size_t count = 0;
#define ADD_DIRECT_CALL(which_offset, which_api) do { \
if((which_offset) && count < MAX_CALLS) { \
calls[count].offset = (which_offset); \
calls[count].api = (which_api); \
count++; \
} \
} while(0)
for(size_t i = 0; i < 4; i++)
ADD_DIRECT_CALL(sc->game_mount_calls[i], 0);
for(size_t i = 0; i < 4; i++)
ADD_DIRECT_CALL(sc->game_umount_calls[i], 1);
ADD_DIRECT_CALL(sc->ppr_call, 2);
ADD_DIRECT_CALL(sc->ppr_umount_call, 3);
#undef ADD_DIRECT_CALL
if(!sc->ppr_cave || sc->ppr_cave_size < sizeof(trampolines)
|| !sc->ppr_call || !sc->ppr_umount_call || count < 2)
return -1;
if(lock_shellcore_range(pid, shellcore_base + sc->ppr_cave,
sizeof(trampolines), locked)
|| phys_copyout(current, shellcore_base + sc->ppr_cave,
sizeof(trampolines), dmap, cr3))
return -1;
for(size_t i = 0; i < sizeof(trampolines); i++)
if(current[i])
return -1;
for(size_t i = 0; i < sizeof(trampolines); i++)
trampolines[i] = 0xcc;
for(size_t api = 0; api < API_COUNT; api++)
{
unsigned char* trampoline = trampolines + api * TRAMPOLINE_SIZE;
trampoline[0] = 0x48; /* movabs rax, api_wrapper[api] */
trampoline[1] = 0xb8;
memcpy(trampoline + 2, &api_wrapper[api], sizeof(api_wrapper[api]));
trampoline[10] = 0xff; /* jmp rax */
trampoline[11] = 0xe0;
}
/* Validate every call and its original destination before changing code. */
for(size_t i = 0; i < count; i++)
{
if(lock_shellcore_range(pid, shellcore_base + calls[i].offset,
sizeof(original[i]), locked)
|| phys_copyout(original[i], shellcore_base + calls[i].offset,
sizeof(original[i]), dmap, cr3)
|| original[i][0] != 0xe8)
return -1;
int32_t displacement;
memcpy(&displacement, original[i] + 1, sizeof(displacement));
if(calls[i].offset + sizeof(original[i]) + displacement
!= api_plt[calls[i].api])
return -1;
patched[i][0] = 0xe8;
int64_t wide = (int64_t)sc->ppr_cave
+ (int64_t)calls[i].api * TRAMPOLINE_SIZE
- (int64_t)(calls[i].offset + sizeof(patched[i]));
int32_t relative = (int32_t)wide;
if((int64_t)relative != wide)
return -1;
memcpy(patched[i] + 1, &relative, sizeof(relative));
}
if(phys_copyin(shellcore_base + sc->ppr_cave, trampolines,
sizeof(trampolines), dmap, cr3)
|| phys_copyout(current, shellcore_base + sc->ppr_cave,
sizeof(trampolines), dmap, cr3)
|| !shellcore_bytes_equal(current, trampolines, sizeof(trampolines)))
return -1;
size_t installed = 0;
for(; installed < count; installed++)
{
unsigned char verify[sizeof(patched[installed])];
if(phys_copyin(shellcore_base + calls[installed].offset,
patched[installed], sizeof(patched[installed]),
dmap, cr3)
|| phys_copyout(verify, shellcore_base + calls[installed].offset,
sizeof(verify), dmap, cr3)
|| !shellcore_bytes_equal(verify, patched[installed], sizeof(verify)))
break;
}
if(installed == count)
return 0;
/* A partially reachable wrapper is unsafe. Restore every touched call;
* return -2 if a reference may still remain so the RX mapping is kept. */
int rollback_failed = 0;
size_t touched = installed < count ? installed + 1 : installed;
for(size_t i = 0; i < touched; i++)
{
unsigned char verify[sizeof(original[i])];
if(phys_copyin(shellcore_base + calls[i].offset,
original[i], sizeof(original[i]), dmap, cr3)
|| phys_copyout(verify, shellcore_base + calls[i].offset,
sizeof(verify), dmap, cr3)
|| !shellcore_bytes_equal(verify, original[i], sizeof(verify)))
rollback_failed = 1;
}
return rollback_failed ? -2 : -1;
}
static int install_shellcore_ppr_hook(
int pid, uint64_t shellcore_base, uint64_t dmap, uint64_t cr3,
const struct shellcore_fpkg_offsets* sc,
@@ -942,15 +1121,16 @@ static int install_shellcore_ppr_hook(
const size_t blob_size = (size_t)(ppr_mount_940_blob_end
- ppr_mount_940_blob_start);
static unsigned char prepared_blob[SHELLCORE_FPKG_WRAPPER_MAP_SIZE];
uint64_t helper_target[4];
uint64_t helper_target[3];
uint64_t api_got[4], api_target[4];
uint64_t api_wrapper[4];
size_t game_mount_entry = 0;
size_t game_umount_entry = 0;
size_t ppr_umount_entry = 0;
int direct_calls = sc->ppr_umount_call != 0;
if(!sc->close_plt || !sc->open_plt || !sc->pread_plt
|| !sc->mount_ppr_pkg_plt || !sc->getpid_plt)
|| !sc->mount_ppr_pkg_plt || (!direct_calls && !sc->getpid_plt))
return shellcore_ppr_fail("fpkg scope: missing ShellCore offsets");
if(!blob_size || blob_size > sizeof(prepared_blob))
return shellcore_ppr_fail("fpkg scope: invalid wrapper blob size");
@@ -963,27 +1143,67 @@ static int install_shellcore_ppr_hook(
sc->mount_ppr_pkg_plt, /* sceFsMountPprPkg */
sc->mount_ppr_pkg_plt + 0x20, /* sceFsUmountPprPkg */
};
const uint64_t helper_plt[4] = {
sc->close_plt, sc->open_plt, sc->pread_plt, sc->getpid_plt
const uint64_t helper_plt[3] = {
sc->close_plt, sc->open_plt, sc->pread_plt
};
if(sc->mount_ppr_pkg_plt < 0x40)
return shellcore_ppr_fail("fpkg scope: invalid API PLT layout");
for(size_t i = 0; i < 3; i++)
{
unsigned char plt[6];
if(lock_shellcore_range(pid, shellcore_base + helper_plt[i],
sizeof(plt), locked)
|| phys_copyout(plt, shellcore_base + helper_plt[i], sizeof(plt),
dmap, cr3)
|| plt[0] != 0xff || plt[1] != 0x25)
return shellcore_ppr_fail("fpkg scope: helper PLT mismatch");
/* These GOT entries may also be lazy on early firmware. They are not
* redirected, so calling their PLT stubs is resolver-safe. */
helper_target[i] = shellcore_base + helper_plt[i];
}
for(size_t i = 0; i < 4; i++)
{
if(resolve_shellcore_plt(shellcore_base, helper_plt[i], dmap, cr3,
pid, locked,
0, &helper_target[i]))
return shellcore_ppr_fail("fpkg scope: helper PLT mismatch");
if(resolve_shellcore_plt(shellcore_base, api_plt[i], dmap, cr3,
pid, locked,
&api_got[i], &api_target[i]))
if(direct_calls)
{
unsigned char plt[6];
if(lock_shellcore_range(pid, shellcore_base + api_plt[i],
sizeof(plt), locked)
|| phys_copyout(plt, shellcore_base + api_plt[i], sizeof(plt),
dmap, cr3)
|| plt[0] != 0xff || plt[1] != 0x25)
return shellcore_ppr_fail(
"fpkg scope: direct API PLT mismatch");
api_got[i] = 0;
api_target[i] = shellcore_base + api_plt[i];
}
else if(resolve_shellcore_plt(shellcore_base, api_plt[i], dmap, cr3,
pid, locked,
&api_got[i], &api_target[i]))
return shellcore_ppr_fail("fpkg scope: package API PLT mismatch");
}
uint64_t getpid_address = helper_target[3];
uint64_t syscall_target = getpid_address + 7;
if(syscall_target < getpid_address)
return shellcore_ppr_fail("fpkg scope: syscall address overflow");
/* Direct syscall instructions outside libkernel are rejected by early
* firmware. Early ShellCore also leaves getpid@GOT lazy, so resolve the
* export from its loaded modules and validate the complete syscall stub.
* Later builds can keep using their already-resolved import target. */
uint64_t syscall_target;
if(direct_calls)
{
if(resolve_shellcore_syscall_trampoline(pid, dmap, cr3, locked,
&syscall_target))
return shellcore_ppr_fail(
"fpkg scope: libkernel syscall trampoline unavailable");
}
else
{
uint64_t getpid_target;
if(resolve_shellcore_plt(shellcore_base, sc->getpid_plt, dmap, cr3,
pid, locked, 0, &getpid_target)
|| getpid_target + 7 < getpid_target)
return shellcore_ppr_fail(
"fpkg scope: getpid trampoline unavailable");
syscall_target = getpid_target + 7;
}
memcpy(prepared_blob, ppr_mount_940_blob_start, blob_size);
if(replace_shellcore_blob_u64(prepared_blob, blob_size,
@@ -1053,6 +1273,24 @@ static int install_shellcore_ppr_hook(
api_wrapper[1] = wrapper_base + game_umount_entry;
api_wrapper[2] = wrapper_base;
api_wrapper[3] = wrapper_base + ppr_umount_entry;
if(direct_calls)
{
int result = install_shellcore_direct_fpkg_calls(
pid, shellcore_base, dmap, cr3, sc, api_plt, api_wrapper, locked);
if(result)
{
/* -2 means rollback could not prove that no call still reaches
* the wrapper, so retain its executable mapping. */
if(result != -2)
remote_syscall(pid, SYS_munmap, wrapper_base,
SHELLCORE_FPKG_WRAPPER_MAP_SIZE,
0, 0, 0, 0);
return shellcore_ppr_fail(
result == -2 ? "fpkg scope: direct-call rollback failed"
: "fpkg scope: direct-call install failed");
}
return 0;
}
size_t installed = 0;
for(; installed < 4; installed++)
{
@@ -1649,7 +1887,7 @@ int main(void* ds, int a, int b, uintptr_t c, uintptr_t d)
"Retail";
char msg[128];
snprintf(msg, sizeof(msg), "Welcome To Kstuff Lite 1.12-fpkg-dr-test5\nPlayStation 5 FW: %x.%02x (%s)\nBy sleirsgoevy",
snprintf(msg, sizeof(msg), "Welcome To Kstuff Lite 1.12-fpkg-dr-test6\nPlayStation 5 FW: %x.%02x (%s)\nBy sleirsgoevy",
fwver >> 8, fwver & 0xFF, console_type);
notify(msg);
+18 -5
View File
@@ -26,6 +26,7 @@ BITS 64
%define FPKG_SCOPE_PPR_MOUNT 3
%define FPKG_SCOPE_PPR_UNMOUNT 4
%define PPR_HOOK_SCOPE_BIT 0x100
%define PPR_HOOK_PLAINTEXT_BIT 0x200
%define PPR_FIH_SIZE 0x1000
%define PPR_SUPERBLOCK_SIZE 0x5A0
@@ -152,6 +153,7 @@ ppr_mount_940_hook:
mov edx, 5 ; plaintext/no-auth marker accepted
call ppr_trace
or r15d, PPR_HOOK_PLAINTEXT_BIT
; BEGIN(version=8)
mov esi, PPR_CONTROL_BEGIN
@@ -175,9 +177,9 @@ ppr_mount_940_hook:
xor r8d, r8d
xor r9d, r9d
call ppr_control
jc .call_original
jc .fail_plaintext
test rax, rax
jnz .call_original
jnz .fail_plaintext
.begin_ready:
or r15d, 1
@@ -241,7 +243,14 @@ ppr_mount_940_hook:
.abort_protocol:
xor r8d, r8d ; protocol abort, no mount result
call ppr_clear
and r15d, PPR_HOOK_SCOPE_BIT
and r15d, (PPR_HOOK_SCOPE_BIT | PPR_HOOK_PLAINTEXT_BIT)
.fail_plaintext:
; Once the explicit on-disk marker has been accepted, never fall back to
; native verifyImage. That would turn a recoverable protocol error into
; an IOC wait with plaintext bytes interpreted as authenticated data.
mov ebx, 0x80020016 ; SCE kernel EINVAL
jmp .leave_scope
.call_original:
mov rdi, r12
@@ -316,13 +325,17 @@ ppr_mount_940_hook:
; Inputs follow the registers captured by kekcall:
; RSI=operation, RDX=arg0, R8=arg2, R9=arg3. R10/arg1 is not captured.
ppr_control:
; SysV requires a 16-byte aligned RSP at a nested call site. All wrapper
; callers enter here with RSP mod 16 = 8 after their CALL instruction.
sub rsp, 8
xor ecx, ecx ; getpid+7 moves RCX to syscall R10
mov rdi, PPR_CONTROL_MAGIC
mov rax, PPR_CONTROL_SYSCALL
; Keep the patched trampoline address in the instruction stream so the
; wrapper stays freestanding and does not need a writable data mapping.
; Early kernels reject syscall instructions outside libkernel. Keep the
; validated getpid+7 trampoline address in this freestanding blob.
mov r11, PPR_SYSCALL_TARGET_PLACEHOLDER
call r11
lea rsp, [rsp + 8] ; preserve syscall carry flag
ret
; EDX is the last successfully completed hook stage. The trace is advisory:
+3
View File
@@ -10,6 +10,9 @@ static const struct shellcore_fpkg_offsets shellcore_fpkg_offsets_250 = {
.pread_plt = 0x12a3a10,
.mount_ppr_pkg_plt = 0x12a5610,
.getpid_plt = 0x12a2640,
.game_mount_calls = {0x4cff43, 0x4d2929, 0x4d2bce},
.game_umount_calls = {0x4d2ff3, 0x4d3150},
.ppr_umount_call = 0x4d3da0,
};
static struct shellcore_patch shellcore_patches_250_retail[] = {
+5 -3
View File
@@ -88,9 +88,11 @@ struct ppr_profile
* The cleanup call deltas are kept in the same profile so a firmware cannot
* expose only half of the interception ABI.
*
* Every E8 delta below was verified byte-for-byte against the matching retail
* x86_kernel.elf. Firmware without a matching image in the local corpus is
* deliberately omitted. No runtime kernel-text probing is performed.
* The matching offset header records whether each firmware was statically
* revalidated against retail or is still derived from a devkit image. The E8
* deltas below are checked together with those offsets by
* tools/validate_ppr_offsets.py when a retail image is available. No runtime
* kernel-text probing is performed.
*/
static const struct ppr_profile* get_ppr_profile(void)
{
+507
View File
@@ -0,0 +1,507 @@
#!/usr/bin/env python3
"""Find PPR/fPKG offsets in a kernel using a same-firmware reference image.
The reference image is used only to build instruction signatures at the eight
known PPR sites. The target kdata anchor is derived independently from the
ordinary CR0 helper offsets, and every discovered PPR address is checked as a
connected ABI (get-index calls/returns, cleanup calls and verifyImage mailbox
call). Existing PPR values are therefore never copied to the target.
Typical use when a missing retail kernel becomes available:
python tools/find_ppr_offsets.py C:\kernels\retail\10.00.elf \
--reference-dir C:\kernels\devkit
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import sys
from typing import Iterator, NamedTuple
try:
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
from capstone.x86 import X86_OP_IMM
except ImportError as exc: # pragma: no cover - environment diagnostic
raise SystemExit("capstone is required: python -m pip install capstone") \
from exc
from validate_ppr_offsets import (
MASK64,
PF_X,
PPR_NAMES,
PROFILES,
Segment,
bytes_at,
infer_kdata_anchor,
load_image,
parse_header,
rel32_call_target,
segment_kdata_anchor,
version_key,
)
MD = Cs(CS_ARCH_X86, CS_MODE_64)
MD.detail = True
class Signature(NamedTuple):
data: bytes
mask: bytes
class LocatedSite(NamedTuple):
address: int
method: str
def signed_hex(value: int) -> str:
if value & (1 << 63):
value -= 1 << 64
sign = "-" if value < 0 else ""
return f"{sign}0x{abs(value):x}"
def executable_segments(segments: list[Segment]) -> Iterator[Segment]:
return (segment for segment in segments if segment.flags & PF_X)
def find_exact(segments: list[Segment], pattern: bytes) -> list[int]:
found = []
for segment in executable_segments(segments):
start = 0
while True:
start = segment.data.find(pattern, start)
if start < 0:
break
found.append((segment.vaddr + start) & MASK64)
start += 1
return found
def instruction_signature(segments: list[Segment], address: int,
instruction_count: int = 12) -> Signature:
raw = bytes_at(segments, address, 128)
instructions = list(MD.disasm(raw, address, count=instruction_count))
if len(instructions) < 6:
raise ValueError(f"too few instructions at reference site {address:#x}")
size = sum(instruction.size for instruction in instructions)
data = bytearray(raw[:size])
mask = bytearray(b"\x01" * size)
cursor = 0
for instruction in instructions:
for offset, field_size in (
(instruction.imm_offset, instruction.imm_size),
(instruction.disp_offset, instruction.disp_size),
):
if field_size:
mask[cursor + offset:cursor + offset + field_size] = \
b"\x00" * field_size
cursor += instruction.size
return Signature(bytes(data), bytes(mask))
def longest_fixed_run(mask: bytes) -> tuple[int, int]:
best_start = best_size = 0
start = 0
while start < len(mask):
while start < len(mask) and not mask[start]:
start += 1
end = start
while end < len(mask) and mask[end]:
end += 1
if end - start > best_size:
best_start, best_size = start, end - start
start = end + 1
return best_start, best_size
def masked_matches(data: bytes, start: int, signature: Signature) -> bool:
if start < 0 or start + len(signature.data) > len(data):
return False
view = data[start:start + len(signature.data)]
return all(not keep or actual == expected
for actual, expected, keep
in zip(view, signature.data, signature.mask))
def find_masked(segments: list[Segment], signature: Signature) -> list[int]:
run_start, run_size = longest_fixed_run(signature.mask)
if run_size < 3:
raise ValueError("instruction signature has no useful fixed byte run")
needle = signature.data[run_start:run_start + run_size]
found = set()
for segment in executable_segments(segments):
position = 0
while True:
position = segment.data.find(needle, position)
if position < 0:
break
candidate = position - run_start
if masked_matches(segment.data, candidate, signature):
found.add((segment.vaddr + candidate) & MASK64)
position += 1
return sorted(found)
def locate_site(reference_segments: list[Segment], reference_address: int,
target_segments: list[Segment]) -> tuple[LocatedSite | None,
list[int]]:
# Exact matching gives the strongest result and is normally sufficient for
# devkit/retail images built from the same firmware source revision.
exact = bytes_at(reference_segments, reference_address, 64)
if len(exact) == 64:
matches = find_exact(target_segments, exact)
if len(matches) == 1:
return LocatedSite(matches[0], "exact-64"), matches
# Ignore encoded branch targets, RIP-relative displacements and immediates
# when layout or link-time addresses differ between target types.
signature = instruction_signature(reference_segments, reference_address)
matches = find_masked(target_segments, signature)
if len(matches) == 1:
return LocatedSite(matches[0], "masked-instructions"), matches
return None, matches
def direct_call_entry(segments: list[Segment], return_address: int) -> int | None:
entry = rel32_call_target(segments, (return_address - 5) & MASK64)
if entry is None or bytes_at(segments, entry, 4) \
!= bytes.fromhex("55 48 89 e5"):
return None
return entry
def cleanup_body_delta(segments: list[Segment], address: int) -> int:
instructions = list(MD.disasm(bytes_at(segments, address, 32), address,
count=8))
for instruction in instructions:
if instruction.mnemonic != "jmp" or len(instruction.operands) != 1:
continue
operand = instruction.operands[0]
if operand.type != X86_OP_IMM:
continue
target = operand.imm & MASK64
delta = (target - address) & MASK64
if delta < 0x100:
return delta
return 0
def locate_cleanup(reference_segments: list[Segment], reference_address: int,
target_segments: list[Segment]) \
-> tuple[LocatedSite | None, list[int]]:
site, candidates = locate_site(
reference_segments, reference_address, target_segments
)
if site is not None:
return site, candidates
# Newer kernels expose cleanup_a53io_pkg_keys through a short alignment
# thunk. Its rel32 jump changes easily, while the actual body is highly
# distinctive. Match the body and translate back to the public entry.
body_delta = cleanup_body_delta(reference_segments, reference_address)
if not body_delta:
return None, candidates
body, body_candidates = locate_site(
reference_segments,
(reference_address + body_delta) & MASK64,
target_segments,
)
if body is None:
return None, [((candidate - body_delta) & MASK64)
for candidate in body_candidates]
return LocatedSite((body.address - body_delta) & MASK64,
f"{body.method}-via-body"), [body.address]
def select_reference(reference_dir: Path, major: int, minor: int) -> Path:
stem = f"{major}.{minor:02d}"
exact = reference_dir / f"{stem}.elf"
if exact.is_file():
return exact
candidates = sorted(reference_dir.glob(f"{stem}.*.elf"))
if len(candidates) == 1:
return candidates[0]
if not candidates:
raise ValueError(f"no same-firmware reference for {stem}")
names = ", ".join(path.name for path in candidates)
raise ValueError(f"ambiguous references for {stem}: {names}")
def connected_abi_errors(segments: list[Segment], offsets: dict[str, int],
anchor: int, major: int, minor: int) -> list[str]:
address = lambda name: (anchor + offsets[name]) & MASK64
profile_key = 101 if major == 1 and minor >= 5 else major
if profile_key not in PROFILES:
return [f"no PPR ABI profile for firmware family {major}"]
profile = PROFILES[profile_key]
errors = []
for kind in ("xts", "cmac"):
entry = address(f"ppr_pfs_get_{kind}_index")
return_address = address(f"ppr_pfs_get_{kind}_return")
target = rel32_call_target(segments, (return_address - 5) & MASK64)
if target != entry:
got = "not a call" if target is None else f"target {target:#x}"
errors.append(f"get_{kind}_return: {got}, expected {entry:#x}")
cleanup = address("ppr_pfs_cleanup_keys")
for kind, delta in (("cmac", profile.cleanup_cmac_delta),
("xts", profile.cleanup_xts_delta)):
if rel32_call_target(segments, (cleanup + delta) & MASK64) is None:
errors.append(f"cleanup {kind}: expected an E8 rel32 call")
verify_lr = address("sceSblServiceMailbox_lr_verifyImage")
mailbox = address("sceSblServiceMailbox")
target = rel32_call_target(segments, (verify_lr - 5) & MASK64)
if target != mailbox:
got = "not a call" if target is None else f"target {target:#x}"
errors.append(f"verifyImage LR: {got}, expected {mailbox:#x}")
clear = address("ppr_pfs_clear_key_missing")
if bytes_at(segments, clear, len(profile.clear_prefix)) \
!= profile.clear_prefix:
errors.append("clear-key miss: unexpected result assignment")
success = address("ppr_pfs_verify_image_no_key_success")
if bytes_at(segments, success, len(profile.success_prefix)) \
!= profile.success_prefix:
errors.append("verifyImage no-key continuation: unexpected instruction")
return errors
def scan(target: Path, reference: Path, header_dir: Path) -> dict[str, object]:
key, major, minor = version_key(target)
header = header_dir / f"{key}.h"
if not header.is_file():
raise ValueError(f"offset header not found: {header}")
target_known = parse_header(header)
reference_key, _reference_major, _reference_minor = version_key(reference)
reference_header = header_dir / f"{reference_key}.h"
if not reference_header.is_file():
raise ValueError(f"reference offset header not found: {reference_header}")
reference_known = parse_header(reference_header)
if any(not reference_known.get(name, 0) for name in PPR_NAMES):
raise ValueError("reference PPR offsets are absent from the header")
target_segments = load_image(target)
reference_segments = load_image(reference)
target_anchor, target_anchor_score = infer_kdata_anchor(
target_segments, target_known
)
try:
reference_anchor = segment_kdata_anchor(reference_segments)
reference_anchor_score: int | str = "PT_LOAD"
except ValueError:
reference_anchor, reference_anchor_score = infer_kdata_anchor(
reference_segments, reference_known
)
reference_errors = connected_abi_errors(
reference_segments, reference_known, reference_anchor,
_reference_major, _reference_minor,
)
if reference_errors:
raise ValueError("reference PPR ABI is inconsistent: "
+ "; ".join(reference_errors))
found: dict[str, LocatedSite] = {}
ambiguous: dict[str, list[str]] = {}
# Find the two return sites first. Their preceding E8 instructions reveal
# the target image's get-index entries even when those functions moved or
# their prologues are intentionally identical.
for name in ("ppr_pfs_get_xts_return", "ppr_pfs_get_cmac_return"):
reference_address = (reference_anchor + reference_known[name]) & MASK64
site, candidates = locate_site(
reference_segments, reference_address, target_segments
)
if site is None:
ambiguous[name] = [f"{candidate:#x}" for candidate in candidates]
else:
found[name] = site
for kind in ("xts", "cmac"):
name = f"ppr_pfs_get_{kind}_index"
return_name = f"ppr_pfs_get_{kind}_return"
entry = direct_call_entry(
target_segments, found[return_name].address
) if return_name in found else None
if entry is not None:
found[name] = LocatedSite(entry, "derived-call-target")
continue
reference_address = (reference_anchor + reference_known[name]) & MASK64
site, candidates = locate_site(
reference_segments, reference_address, target_segments
)
if site is None:
ambiguous[name] = [f"{candidate:#x}" for candidate in candidates]
else:
found[name] = site
name = "ppr_pfs_cleanup_keys"
reference_address = (reference_anchor + reference_known[name]) & MASK64
site, candidates = locate_cleanup(
reference_segments, reference_address, target_segments
)
if site is None:
ambiguous[name] = [f"{candidate:#x}" for candidate in candidates]
else:
found[name] = site
for name in (
"ppr_pfs_clear_key_missing",
"sceSblServiceMailbox_lr_verifyImage",
"ppr_pfs_verify_image_no_key_success",
):
reference_address = (reference_anchor + reference_known[name]) & MASK64
site, candidates = locate_site(
reference_segments, reference_address, target_segments
)
if site is None:
ambiguous[name] = [f"{candidate:#x}" for candidate in candidates]
else:
found[name] = site
discovered = dict(target_known)
for name, site in found.items():
delta = (site.address - target_anchor) & MASK64
discovered[name] = delta - (1 << 64) if delta & (1 << 63) else delta
errors = []
if ambiguous:
for name, candidates in ambiguous.items():
state = "no candidates" if not candidates else \
f"ambiguous candidates: {', '.join(candidates)}"
errors.append(f"{name}: {state}")
else:
errors.extend(connected_abi_errors(
target_segments, discovered, target_anchor, major, minor
))
sites = {}
for name in PPR_NAMES:
if name not in found:
sites[name] = {"status": "unresolved",
"candidates": ambiguous.get(name, [])}
continue
delta = (found[name].address - target_anchor) & MASK64
sites[name] = {
"status": "found",
"address": f"{found[name].address:#x}",
"offset": signed_hex(delta),
"method": found[name].method,
}
return {
"target": str(target),
"reference": str(reference),
"header": str(header),
"reference_header": str(reference_header),
"target_kdata_anchor": f"{target_anchor:#x}",
"target_anchor_score": target_anchor_score,
"reference_kdata_anchor": f"{reference_anchor:#x}",
"reference_anchor_score": reference_anchor_score,
"status": "complete" if not errors else "needs-review",
"sites": sites,
"errors": errors,
}
def iter_targets(paths: list[Path]) -> Iterator[Path]:
for path in paths:
if path.is_dir():
yield from sorted(
child for child in path.rglob("*")
if child.is_file() and child.suffix.lower() in (".elf", ".bin")
)
else:
yield path
def print_human(report: dict[str, object]) -> None:
print(f"\n{report['target']}")
print(f" reference = {report['reference']}")
print(f" kdata_base = {report['target_kdata_anchor']} "
f"[anchor score {report['target_anchor_score']}]")
sites = report["sites"]
assert isinstance(sites, dict)
for name, raw_site in sites.items():
site = raw_site
assert isinstance(site, dict)
if site["status"] == "found":
print(f" {name}: {site['offset']} "
f"({site['address']}, {site['method']})")
else:
candidates = site.get("candidates", [])
print(f" {name}: unresolved ({len(candidates)} candidate(s))")
errors = report["errors"]
assert isinstance(errors, list)
if errors:
print(" review:")
for error in errors:
print(f" {error}")
return
print(" header definitions:")
for name, raw_site in sites.items():
site = raw_site
print(f" DEF({name}, {site['offset']})")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("targets", nargs="+", type=Path,
help="target retail ELF/BIN or directory")
parser.add_argument("--reference", type=Path,
help="explicit same-firmware reference (one target only)")
parser.add_argument("--reference-dir", type=Path,
help="directory containing same-firmware reference ELFs")
parser.add_argument("--headers", type=Path,
default=Path(__file__).resolve().parents[1]
/ "prosper0gdb" / "offsets")
parser.add_argument("--json", action="store_true", help="emit JSON")
args = parser.parse_args()
targets = list(iter_targets(args.targets))
if args.reference and len(targets) != 1:
parser.error("--reference requires exactly one target")
if not args.reference and not args.reference_dir:
parser.error("provide --reference or --reference-dir")
reports = []
failures = 0
for target in targets:
try:
_key, major, minor = version_key(target)
reference = args.reference or select_reference(
args.reference_dir, major, minor
)
reports.append(scan(target, reference, args.headers))
failures += reports[-1]["status"] != "complete"
except (OSError, ValueError) as exc:
failures += 1
reports.append({
"target": str(target),
"status": "error",
"error": str(exc),
})
if args.json:
json.dump(reports, sys.stdout, indent=2)
print()
else:
for report in reports:
if report["status"] == "error":
print(f"\n{report['target']}\n error: {report['error']}")
else:
print_human(report)
complete = sum(report["status"] == "complete" for report in reports)
print(f"\nScanned {len(reports)} target(s): {complete} complete, "
f"{len(reports) - complete} need review.")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())
+301
View File
@@ -0,0 +1,301 @@
#!/usr/bin/env python3
"""Statically validate retail PPR offsets against local PS5 kernel images.
The checker derives the kdata anchor from existing, independently recognizable
CR0 helper instructions. It then checks the relationships that make the PPR
trap ABI safe: both get-index calls and return addresses, cleanup call sites,
the clear-key miss instruction, the verifyImage mailbox return address, and the
success continuation. This is a static audit; console testing is still needed
before calling a firmware fully supported.
"""
from __future__ import annotations
import argparse
from collections import Counter
from pathlib import Path
import re
import struct
import sys
from typing import NamedTuple
PT_LOAD = 1
PF_X = 1
MASK64 = (1 << 64) - 1
PPR_NAMES = (
"ppr_pfs_get_xts_index",
"ppr_pfs_get_cmac_index",
"ppr_pfs_get_xts_return",
"ppr_pfs_get_cmac_return",
"ppr_pfs_cleanup_keys",
"ppr_pfs_clear_key_missing",
"sceSblServiceMailbox_lr_verifyImage",
"ppr_pfs_verify_image_no_key_success",
)
ANCHOR_SIGNATURES = {
"cr0_load": bytes.fromhex("48 8b 47 58"),
"cr0_clear_store": bytes.fromhex("48 83 e0 f7"),
"cr0_write_ret": bytes.fromhex("0f 22 c0"),
"store_rax_rdi": bytes.fromhex("48 89 07"),
}
class Profile(NamedTuple):
cleanup_cmac_delta: int
cleanup_xts_delta: int
clear_prefix: bytes
success_prefix: bytes
PROFILES = {
1: Profile(0x126, 0x15A, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("49 c1 e7 20")),
101: Profile(0x145, 0x179, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("49 c1 e7 20")),
2: Profile(0x144, 0x178, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("49 c1 e7 20")),
3: Profile(0x137, 0x166, bytes.fromhex("41 be fe ff ff ff"),
bytes.fromhex("49 c1 e7 20")),
4: Profile(0x13D, 0x16C, bytes.fromhex("41 bc fe ff ff ff"),
bytes.fromhex("49 c1 e7 20")),
5: Profile(0x138, 0x167, bytes.fromhex("41 bc fe ff ff ff"),
bytes.fromhex("49 c1 e5 20")),
6: Profile(0x138, 0x167, bytes.fromhex("41 bc fe ff ff ff"),
bytes.fromhex("49 c1 e5 20")),
7: Profile(0x11C, 0x14B, bytes.fromhex("41 bf fe ff ff ff"),
bytes.fromhex("49 c1 e5 20")),
8: Profile(0x117, 0x147, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("49 c1 e5 20")),
9: Profile(0x119, 0x149, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("49 c1 e5 20")),
10: Profile(0x119, 0x149, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("c4 63 fb f0")),
11: Profile(0x119, 0x149, bytes.fromhex("bb fe ff ff ff"),
bytes.fromhex("c4 63 fb f0")),
}
class Segment(NamedTuple):
vaddr: int
data: bytes
flags: int
def load_image(path: Path) -> list[Segment]:
data = path.read_bytes()
if data[:4] != b"\x7fELF":
return [Segment(0, data, PF_X)]
if len(data) < 64 or data[4] != 2 or data[5] != 1:
raise ValueError("expected a little-endian ELF64 or raw kernel image")
phoff = struct.unpack_from("<Q", data, 32)[0]
phentsize = struct.unpack_from("<H", data, 54)[0]
phnum = struct.unpack_from("<H", data, 56)[0]
if phentsize < 56 or phoff + phentsize * phnum > len(data):
raise ValueError("invalid program-header table")
segments = []
for index in range(phnum):
pos = phoff + index * phentsize
p_type, flags, offset, vaddr, _paddr, filesz, _memsz, _align = \
struct.unpack_from("<IIQQQQQQ", data, pos)
if p_type != PT_LOAD or not filesz:
continue
available = min(filesz, max(0, len(data) - offset))
segments.append(Segment(vaddr, data[offset:offset + available], flags))
if not segments:
raise ValueError("ELF has no readable PT_LOAD segment")
return segments
def bytes_at(segments: list[Segment], address: int, size: int) -> bytes:
for segment in segments:
delta = (address - segment.vaddr) & MASK64
if delta < len(segment.data):
return segment.data[delta:delta + size]
return b""
def find_all(segments: list[Segment], pattern: bytes) -> list[int]:
found = []
for segment in segments:
if not segment.flags & PF_X:
continue
start = 0
while True:
start = segment.data.find(pattern, start)
if start < 0:
break
found.append((segment.vaddr + start) & MASK64)
start += 1
return found
def segment_kdata_anchor(segments: list[Segment]) -> int:
"""Return the first non-executable PT_LOAD following executable text."""
executable = sorted(
(segment for segment in segments if segment.flags & PF_X),
key=lambda segment: segment.vaddr,
)
if not executable:
raise ValueError("image has no executable segment")
text_end = max(segment.vaddr + len(segment.data)
for segment in executable)
following = sorted(
segment.vaddr for segment in segments
if not segment.flags & PF_X and segment.vaddr >= text_end
)
if not following:
raise ValueError("image has no distinct post-text kdata segment")
return following[0]
def parse_header(path: Path) -> dict[str, int]:
text = path.read_text(encoding="utf-8")
return {
name: int(value, 0)
for name, value in re.findall(
r"DEF\((\w+),\s*(-?0x[0-9a-fA-F]+|0)\)", text
)
}
def infer_kdata_anchor(segments: list[Segment], offsets: dict[str, int]) \
-> tuple[int, int]:
candidates = Counter()
for name, signature in ANCHOR_SIGNATURES.items():
delta = offsets.get(name, 0)
if not delta:
continue
for address in find_all(segments, signature):
anchor = (address - delta) & MASK64
if anchor & 0xFFFF == 0:
candidates[anchor] += 1
if not candidates:
raise ValueError("could not infer an aligned kdata anchor")
anchor, score = candidates.most_common(1)[0]
if score < 2:
raise ValueError(f"ambiguous kdata anchor (best score {score})")
return anchor, score
def rel32_call_target(segments: list[Segment], address: int) -> int | None:
instruction = bytes_at(segments, address, 5)
if len(instruction) != 5 or instruction[0] != 0xE8:
return None
displacement = struct.unpack_from("<i", instruction, 1)[0]
return (address + 5 + displacement) & MASK64
def version_key(path: Path) -> tuple[str, int, int]:
match = re.match(r"^(\d+)\.(\d+)", path.stem)
if not match:
raise ValueError("filename does not begin with major.minor")
major, minor = map(int, match.groups())
return f"{major}_{minor:02d}", major, minor
def validate(path: Path, header_dir: Path) -> list[str]:
key, major, minor = version_key(path)
header = header_dir / f"{key}.h"
if not header.is_file():
return ["no matching offset header"]
offsets = parse_header(header)
if any(not offsets.get(name, 0) for name in PPR_NAMES):
return ["PPR is not enabled in the matching table"]
segments = load_image(path)
anchor, _score = infer_kdata_anchor(segments, offsets)
address = lambda name: (anchor + offsets[name]) & MASK64
profile_key = 101 if major == 1 and minor >= 5 else major
profile = PROFILES[profile_key]
errors = []
for name in PPR_NAMES:
if not bytes_at(segments, address(name), 1):
errors.append(f"{name}: address is outside the image")
for kind in ("xts", "cmac"):
entry = address(f"ppr_pfs_get_{kind}_index")
return_address = address(f"ppr_pfs_get_{kind}_return")
if bytes_at(segments, entry, 4) != bytes.fromhex("55 48 89 e5"):
errors.append(f"get_{kind}_index: not a function prologue")
target = rel32_call_target(segments, (return_address - 5) & MASK64)
if target != entry:
got = "not a call" if target is None else f"target {target:#x}"
errors.append(f"get_{kind}_return: {got}, expected {entry:#x}")
cleanup = address("ppr_pfs_cleanup_keys")
if bytes_at(segments, cleanup, 4) != bytes.fromhex("55 48 89 e5"):
errors.append("ppr_pfs_cleanup_keys: not a function prologue")
for kind, delta in (("cmac", profile.cleanup_cmac_delta),
("xts", profile.cleanup_xts_delta)):
if rel32_call_target(segments, (cleanup + delta) & MASK64) is None:
errors.append(f"cleanup {kind}: expected an E8 rel32 call")
clear = address("ppr_pfs_clear_key_missing")
if bytes_at(segments, clear, len(profile.clear_prefix)) \
!= profile.clear_prefix:
errors.append("ppr_pfs_clear_key_missing: unexpected result assignment")
verify_lr = address("sceSblServiceMailbox_lr_verifyImage")
mailbox = address("sceSblServiceMailbox")
target = rel32_call_target(segments, (verify_lr - 5) & MASK64)
if target != mailbox:
got = "not a call" if target is None else f"target {target:#x}"
errors.append(f"verifyImage LR: {got}, expected {mailbox:#x}")
success = address("ppr_pfs_verify_image_no_key_success")
if bytes_at(segments, success, len(profile.success_prefix)) \
!= profile.success_prefix:
errors.append("verifyImage no-key continuation: unexpected instruction")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("kernels", type=Path,
help="kernel corpus root containing retail/")
parser.add_argument("--headers", type=Path,
default=Path(__file__).resolve().parents[1]
/ "prosper0gdb" / "offsets")
args = parser.parse_args()
retail = args.kernels / "retail"
if not retail.is_dir():
parser.error(f"retail directory not found: {retail}")
failures = 0
checked = 0
for path in sorted(retail.iterdir()):
if path.suffix.lower() not in (".elf", ".bin"):
continue
try:
key, _major, _minor = version_key(path)
except ValueError:
continue
if not (args.headers / f"{key}.h").is_file():
continue
try:
errors = validate(path, args.headers)
except (OSError, ValueError, struct.error) as exc:
errors = [str(exc)]
if errors == ["PPR is not enabled in the matching table"]:
print(f"SKIP {path.name}: {errors[0]}")
continue
checked += 1
if errors:
failures += 1
print(f"FAIL {path.name}")
for error in errors:
print(f" {error}")
else:
print(f"PASS {path.name}")
print(f"Checked {checked} retail image(s): "
f"{checked - failures} passed, {failures} failed.")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())