mirror of
https://github.com/EchoStretch/kstuff-lite.git
synced 2026-10-18 17:59:51 -05:00
Audit PPR offsets and add firmware scanners
This commit is contained in:
1 parent
0db6795877
commit
ed416c49bb
63 files changed
+1153
-87
No files matched your search
@@ -24,6 +24,7 @@ refs/*
|
||||
/ps5-kstuff/debug-reader.elf
|
||||
/ps5-kstuff/.build-config
|
||||
logs/
|
||||
__pycache__/
|
||||
*.bin
|
||||
*.elf
|
||||
*.o
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user