Suspend: Difference between revisions
CelesteBlue (talk | contribs) |
CelesteBlue (talk | contribs) No edit summary |
||
(4 intermediate revisions by 2 users not shown) | |||
Line 3: | Line 3: | ||
== Suspending == | == Suspending == | ||
To suspend the system, | <source lang="C"> | ||
// NOTE: this structure is zeroed before usage. All fields marked "Never written to" are thus 0. | |||
// Also, data coming from KBL Param comes from the "new" KBL Param regenerated after boot | |||
typedef struct SceKernelResumeInfo { // Size is 0x40 on FWs 0.931.010-3.60 | |||
SceSize size; // Size of this structure | |||
SceUInt32 wakeupFactor; // from KBL Param | |||
SceUInt32 unk0x8; // Never written to | |||
SceUInt32 sleepFactor; // from KBL Param | |||
SceUInt32 unk0x10; // Never written to | |||
SceUInt32 bootControlsInfo; // from KBL Param | |||
SceUInt32 kblParam_0xC8; // from KBL Param | |||
SceUInt32 unk0x1C[9]; // Never written to | |||
} SceKernelResumeInfo; | |||
</source> | |||
To suspend the system, PS Vita saves the current context into a buffer and sends it to [[Syscon]]. Then it issues a [[Syscon]] request to enter the low-power state. | |||
=== Context Buffer === | === Context Buffer === | ||
Line 11: | Line 26: | ||
! Offset !! Size !! Description | ! Offset !! Size !! Description | ||
|- | |- | ||
| 0x0 || 0x4 || Size of | | 0x0 || 0x4 || Size of this structure (<code>0x68</code>) | ||
|- | |- | ||
| 0x4 || 0x4 || Same as <code> | | 0x4 || 0x4 || Mode. Same as in <code>smc_set_power_mode</code> call | ||
|- | |- | ||
| 0x8 || 0x4 || | | 0x8 || 0x4 || Virtual address of buffer that receives a copy of <code>SceKernelResumeInfo</code> | ||
|- | |- | ||
| 0xC || 0x4 || Virtual address of resume function with the following | | 0xC || 0x4 || Virtual address of resume function called with the following arguments: <source lang="c">(u32 TTBR1, u32 CONTEXTIDR, u32 DACR)</source> | ||
|- | |- | ||
| 0x10 || 0x4 || Saved <code>SCTLR</code> | | 0x10 || 0x4 || Saved <code>SCTLR</code> | ||
Line 47: | Line 62: | ||
| 0x44 || 0x4 || Saved <code>TPIDRPRW</code> | | 0x44 || 0x4 || Saved <code>TPIDRPRW</code> | ||
|- | |- | ||
| 0x48 || 0x18 || Unknown/ | | 0x48 || 0x18 || Unknown/reserved | ||
|- | |- | ||
| 0x60 || 0x8 || System time | | 0x60 || 0x8 || System time | ||
|} | |||
=== Saving Context === | |||
The context buffer defined above is built and the physical address of the context buffer is written to Syscon memory at offset <code>0xC</code> i.e it calls [[SceSyscon#sceSysconWriteForDriver|sceSysconWriteForDriver]] with <code>sceSysconWriteForDriver(0xC, &paddr, 4);</code>. Then it issues the syscon command to [[SceSyscon#Reset device|suspend]] the device. | |||
== Resuming == | |||
=== Early boot process === | |||
When resume is launched, Syscon starts first_loader, which runs second_loader, like a cold boot. | |||
The physical address of the context buffer is read from Syscon memory at offset <code>0xC</code> i.e second_loader calls <code>sceSysconRead(0xC, &paddr, 4);</code>. This physical address is written to [[KBL Param]]. | |||
=== SKBL === | |||
Upon power up, the [[Boot Sequence]] is the same until the point where the [[SKBL]] would jump into the non-secure kernel bootloader at <code>0x51000000</code>. Instead, it [[Suspend#suspendinfo_adr check|checks the suspendinfo address is valid]], then copies resume code (from SKBL <code>.text</code>) to the scratchpad (physical address <code>0x1F000000</code>), the Suspend Info to <code>0x1F001000</code> and creates a SceKernelResumeInfo at <code>0x1F001800</code>, [[Suspend#SKBL Resume code|then enters Non-secure state at <code>0x1F000000</code>]]. | |||
===== <code>suspendinfo_adr</code> check ===== | |||
[[SKBL]] performs a sanity check on <code>suspendinfo_adr</code> before attempting to resume with it. The address must be properly aligned, and belong in a specific range of physical address space (namely Non-Secure DRAM). If the check fails, SKBL enters an infinite loop and the boot process halts. The following table details the specifics for different firmwares. | |||
{| class="wikitable" | |||
|- | |||
! FW version !! Minimal physical address !! Maximal physical address !! Required alignment !! Notes | |||
|- | |||
| 0.931.010 || 0x40300000 || 0x5FFFFFFF || 0x10 || For ES1 rev0/1 Kermit only | |||
|- | |||
| 0.931.010 || 0x40300000 || 0x7FFFFFFF || 0x10 || For other Kermit revisions | |||
|- | |||
| 0.940 || 0x40400000 || 0x7FFFFFFF || 0x8 || | |||
|- | |||
| 3.60 || 0x40200000 || 0x5FFFFFFF || 0x8 || | |||
|} | |||
=== SKBL Resume code === | |||
This code starts in Non-secure state, Supervisor mode, with the MMU and all caches disabled. | |||
This code has changed in the following firmwares: | |||
{| class="wikitable" | |||
|+ Changelog | |||
|- | |- | ||
! Firmware !! Changes since previous version !! Notes | |||
|- | |||
| 0.931.010 || N/A || First version available | |||
|- | |||
| 0.940.040 || Suspend Info's TTBR1 and CONTEXTIDR are now forwarded to the resume function || | |||
|- | |||
| 0.995.000 || Suspend Info's DACR is now forwarded to the resume function instead of being restored || Still in use as of FW 3.60. | |||
|} | |} | ||
== | ==== Entrypoint ==== | ||
The | The entrypoint is very short. It disables interrupts (<code>cpsid if</code>), clears exclusive access requests (<code>clrex</code>) then jumps to a little thunk at <code>0x1F000400</code> which simply jumps to the cleanup function at <code>0x0000000D</code> (alias of <code>0x1F00000D</code>). | ||
== | ==== Cleanup function ==== | ||
This function sets <code>SCTLR</code> to 0, clears the Branch Predictor, TLB and Instruction/Data caches, disables alignement check with SCTLR (clear bit 0x2 - this is redundant as 0 was written to SCTLR earlier), then sets up a MMU context before restoring SCTLR (<code>0x20005805</code>, re-enables MMU) and jumping to the restore function at virtual address <code>0x80000201</code> (physical address <code>0x1F000201</code>). This is important as it means TTBR1 now backs the executed code. | |||
The MMU configuration is the following: | |||
* <code>TTBCR</code> is set to <code>2</code> | |||
* <code>TTBR0</code> and <code>TTBR1</code> are set to <code>0x1F004042</code> | |||
* <code>DACR</code> is set to <code>0x55555555</code> (all domains accessible, permissions checked) | |||
* <code>CONTEXTIDR</code> is set to <code>0xF7</code> | |||
The L1 Page Table is located at <code>0x1F004000</code> and only contains 3 Section (1MiB) mappings in domain 0: | |||
* <code>0x1F000000</code> is identity mapped | |||
* <code>0x80000000</code> is mapped to physical address <code>0x1F000000</code> | |||
* <code>0xE2000000</code> is identity mapped as Strongly Ordered memory | |||
==== Restore function ==== | |||
This function restores (almost) all the registers from the Suspend Info, then copies the <code>SceKernelResumeInfo</code> to the buffer in Suspend Info, sets <code>r3-r12</code> to <code>0xDEADBEEF</code> and jumps to the resume function pointed to by the Suspend Info. | |||
NOTE: It is highly plausible that a Suspend Info with <code>TTBCR.N = 0</code> will crash when <code>TTBCR</code> is restored in this step, as <code>TTBR1</code> is backing this routine. | |||
In FW 0.931.010, no arguments are provided to the resume function (<code>r0-r2</code> are <code>0xDEADBEEF</code> and the resume function is <code>typedef void (*resumeFunc)(void);</code>).<br> | |||
In FWs 0.940-0.990, DACR is not provided to the resume function. <code>r2</code> is <code>0xDEADBEEF</code> and the resume function is <code>typedef void(*resumeFunc)(u32 TTBR1, u32 CONTEXTIDR);</code>). | |||
===== Non-restored registers ===== | |||
The following registers from the Suspend Info are not restored: | |||
{| class="wikitable" | |||
|- | |||
! Register !! Reason/Notes | |||
|- | |||
| ACTLR || Read-only in Non-secure state (SKBL configures it) | |||
|- | |||
| TTBR1 || Used to map the register restore routine in address space high-part | |||
|- | |||
| DACR || Restored on firmwares < 0.995.000 | |||
|} | |||
== Rebooting with Patches == | == Rebooting with Patches == | ||
Line 110: | Line 207: | ||
| 0x5102A050 || 0x4 || <code>0xFFFFFFFF</code> || Memory block id after allocation | | 0x5102A050 || 0x4 || <code>0xFFFFFFFF</code> || Memory block id after allocation | ||
|- | |- | ||
| 0x40300100 || 0x100 || | | 0x40300100 || 0x100 || KBL Param || [[KBL Param]] copied from virtual address <code>0x46C0</code> | ||
|} | |} | ||
Line 148: | Line 245: | ||
| 0x5102A024 || 0x4 || <code>0x1</code> || Set to 0 after <code>psp2bootconfig.skprx</code> loads for hacky lock handling | | 0x5102A024 || 0x4 || <code>0x1</code> || Set to 0 after <code>psp2bootconfig.skprx</code> loads for hacky lock handling | ||
|- | |- | ||
| 0x40300100 || 0x100 || | | 0x40300100 || 0x100 || KBL Param || [[KBL Param]] copied from virtual address <code>0x46C0</code> | ||
|} | |} | ||
Latest revision as of 04:36, 19 June 2022
When the system is suspended, it enters a low power state where the main application processor is turned off. The main DRAM state is preserved so upon resume, the kernel does not have to be reloaded.
Suspending
// NOTE: this structure is zeroed before usage. All fields marked "Never written to" are thus 0. // Also, data coming from KBL Param comes from the "new" KBL Param regenerated after boot typedef struct SceKernelResumeInfo { // Size is 0x40 on FWs 0.931.010-3.60 SceSize size; // Size of this structure SceUInt32 wakeupFactor; // from KBL Param SceUInt32 unk0x8; // Never written to SceUInt32 sleepFactor; // from KBL Param SceUInt32 unk0x10; // Never written to SceUInt32 bootControlsInfo; // from KBL Param SceUInt32 kblParam_0xC8; // from KBL Param SceUInt32 unk0x1C[9]; // Never written to } SceKernelResumeInfo;
To suspend the system, PS Vita saves the current context into a buffer and sends it to Syscon. Then it issues a Syscon request to enter the low-power state.
Context Buffer
Offset | Size | Description |
---|---|---|
0x0 | 0x4 | Size of this structure (0x68 )
|
0x4 | 0x4 | Mode. Same as in smc_set_power_mode call
|
0x8 | 0x4 | Virtual address of buffer that receives a copy of SceKernelResumeInfo
|
0xC | 0x4 | Virtual address of resume function called with the following arguments: (u32 TTBR1, u32 CONTEXTIDR, u32 DACR) |
0x10 | 0x4 | Saved SCTLR
|
0x14 | 0x4 | Saved ACTLR
|
0x18 | 0x4 | Saved CPACR
|
0x1C | 0x4 | Saved TTBR0
|
0x20 | 0x4 | Saved TTBR1
|
0x24 | 0x4 | Saved TTBCR
|
0x28 | 0x4 | Saved DACR
|
0x2C | 0x4 | Saved PRRR
|
0x30 | 0x4 | Saved NMRR
|
0x34 | 0x4 | Saved VBAR
|
0x38 | 0x4 | Saved CONTEXTIDR
|
0x3C | 0x4 | Saved TPIDRURW
|
0x40 | 0x4 | Saved TPIDRURO
|
0x44 | 0x4 | Saved TPIDRPRW
|
0x48 | 0x18 | Unknown/reserved |
0x60 | 0x8 | System time |
Saving Context
The context buffer defined above is built and the physical address of the context buffer is written to Syscon memory at offset 0xC
i.e it calls sceSysconWriteForDriver with sceSysconWriteForDriver(0xC, &paddr, 4);
. Then it issues the syscon command to suspend the device.
Resuming
Early boot process
When resume is launched, Syscon starts first_loader, which runs second_loader, like a cold boot.
The physical address of the context buffer is read from Syscon memory at offset 0xC
i.e second_loader calls sceSysconRead(0xC, &paddr, 4);
. This physical address is written to KBL Param.
SKBL
Upon power up, the Boot Sequence is the same until the point where the SKBL would jump into the non-secure kernel bootloader at 0x51000000
. Instead, it checks the suspendinfo address is valid, then copies resume code (from SKBL .text
) to the scratchpad (physical address 0x1F000000
), the Suspend Info to 0x1F001000
and creates a SceKernelResumeInfo at 0x1F001800
, then enters Non-secure state at 0x1F000000
.
suspendinfo_adr
check
SKBL performs a sanity check on suspendinfo_adr
before attempting to resume with it. The address must be properly aligned, and belong in a specific range of physical address space (namely Non-Secure DRAM). If the check fails, SKBL enters an infinite loop and the boot process halts. The following table details the specifics for different firmwares.
FW version | Minimal physical address | Maximal physical address | Required alignment | Notes |
---|---|---|---|---|
0.931.010 | 0x40300000 | 0x5FFFFFFF | 0x10 | For ES1 rev0/1 Kermit only |
0.931.010 | 0x40300000 | 0x7FFFFFFF | 0x10 | For other Kermit revisions |
0.940 | 0x40400000 | 0x7FFFFFFF | 0x8 | |
3.60 | 0x40200000 | 0x5FFFFFFF | 0x8 |
SKBL Resume code
This code starts in Non-secure state, Supervisor mode, with the MMU and all caches disabled.
This code has changed in the following firmwares:
Firmware | Changes since previous version | Notes |
---|---|---|
0.931.010 | N/A | First version available |
0.940.040 | Suspend Info's TTBR1 and CONTEXTIDR are now forwarded to the resume function | |
0.995.000 | Suspend Info's DACR is now forwarded to the resume function instead of being restored | Still in use as of FW 3.60. |
Entrypoint
The entrypoint is very short. It disables interrupts (cpsid if
), clears exclusive access requests (clrex
) then jumps to a little thunk at 0x1F000400
which simply jumps to the cleanup function at 0x0000000D
(alias of 0x1F00000D
).
Cleanup function
This function sets SCTLR
to 0, clears the Branch Predictor, TLB and Instruction/Data caches, disables alignement check with SCTLR (clear bit 0x2 - this is redundant as 0 was written to SCTLR earlier), then sets up a MMU context before restoring SCTLR (0x20005805
, re-enables MMU) and jumping to the restore function at virtual address 0x80000201
(physical address 0x1F000201
). This is important as it means TTBR1 now backs the executed code.
The MMU configuration is the following:
TTBCR
is set to2
TTBR0
andTTBR1
are set to0x1F004042
DACR
is set to0x55555555
(all domains accessible, permissions checked)CONTEXTIDR
is set to0xF7
The L1 Page Table is located at 0x1F004000
and only contains 3 Section (1MiB) mappings in domain 0:
0x1F000000
is identity mapped0x80000000
is mapped to physical address0x1F000000
0xE2000000
is identity mapped as Strongly Ordered memory
Restore function
This function restores (almost) all the registers from the Suspend Info, then copies the SceKernelResumeInfo
to the buffer in Suspend Info, sets r3-r12
to 0xDEADBEEF
and jumps to the resume function pointed to by the Suspend Info.
NOTE: It is highly plausible that a Suspend Info with TTBCR.N = 0
will crash when TTBCR
is restored in this step, as TTBR1
is backing this routine.
In FW 0.931.010, no arguments are provided to the resume function (r0-r2
are 0xDEADBEEF
and the resume function is typedef void (*resumeFunc)(void);
).
In FWs 0.940-0.990, DACR is not provided to the resume function. r2
is 0xDEADBEEF
and the resume function is typedef void(*resumeFunc)(u32 TTBR1, u32 CONTEXTIDR);
).
Non-restored registers
The following registers from the Suspend Info are not restored:
Register | Reason/Notes |
---|---|
ACTLR | Read-only in Non-secure state (SKBL configures it) |
TTBR1 | Used to map the register restore routine in address space high-part |
DACR | Restored on firmwares < 0.995.000 |
Rebooting with Patches
By abusing the way resume works, we can reboot the device into a custom firmware by patching the non-secure kernel bootloader. The general framework to do this is: first, patch the kernel bootloader in RAM to accept unsigned SELFs and load a custom kprx after psp2bootconfig.skprx
. It could be the case that by the time your exploit runs the DRAM region containing the kernel loader has been wiped/reused. If so, you must use a lower level exploit to dump the loader. It may even be possible to dump the compressed version, in that case you wouldn't even need to perform cleanup. Your custom krpx will be loaded after the bare essential kernel modules are loaded (memory management, threads, file IO, etc) and it can then patch the full module loader (SceSblAuthMgr) to accept unsigned SELFs as well as any other patches.
Cleaning up non-secure Kernel Bootloader
Ideally, if we can get the original ARZL compressed version of the non-secure kernel bootloader that is extracted from kernel_boot_loader.self
, we can extract it (see ARZL), then we can load that to memory and jump to it from our resume function. However, by the time the system is in a state we control, usually that data would be corrupted. What we are left with is the non-secure kernel bootloader in the state after bootup is completed. In order to get it to run successfully again in our resume function, we need to clean up the data it uses.
1.50 cleanup
Physical Address | Size | Data | Notes |
---|---|---|---|
0x51002024 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5100203C | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5100205C | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002074 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002090 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x510020D0 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002110 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x510021A4 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x510023FC | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002604 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002A40 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5102A014 | 0x4 | 0x0 |
Eventually gets some pointer |
0x5102A020 | 0x4 | 0x1 |
Read Block Count (start at 1) |
0x5102A024 | 0x4 | 0x1 |
Set to 0 after psp2bootconfig.skprx loads for hacky lock handling
|
0x5102A02C | 0x4 | 0xFFFFFFFF |
Memory block id after allocation |
0x5102A030 | 0x4 | 0x600BF34C |
Random number set after each boot to ensure integrity |
0x5102A034 | 0x4 | 0xFF |
Boot State ID |
0x5102A050 | 0x4 | 0xFFFFFFFF |
Memory block id after allocation |
0x40300100 | 0x100 | KBL Param | KBL Param copied from virtual address 0x46C0
|
1.69 cleanup
Physical Address | Size | Data | Notes |
---|---|---|---|
0x51002A40 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002024 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5100203C | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5100205C | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002074 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002118 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x510020D4 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002090 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x51002614 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5100240C | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x510021B4 | 0x4 | 0x0 |
Interrupt registering returns error if not 0 |
0x5102A02C | 0x4 | 0xFFFFFFFF |
Memory block id after allocation |
0x5102A050 | 0x4 | 0xFFFFFFFF |
Memory block id after allocation |
0x5102A030 | 0x4 | 0x600BF34C |
Random number set after each boot to ensure integrity |
0x5102A024 | 0x4 | 0x1 |
Set to 0 after psp2bootconfig.skprx loads for hacky lock handling
|
0x40300100 | 0x100 | KBL Param | KBL Param copied from virtual address 0x46C0
|