Secure Kernel: Difference between revisions

From Vita Development Wiki
Jump to navigation Jump to search
(Remove duplicate "Debug prints" section)
No edit summary
(29 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Entrypoint ==
== Entrypoint ==
The entrypoint of secure_kernel is +0x100.
The entrypoint of secure_kernel is +0x100.
First thing it does is disable irq.
Then it zeroes the bss segment 8 bytes at a time.


It sets up $sp to 0x808FF0, and $gp to 0x80F8A8.
First thing it does is disable irq. Then it zeroes the .bss segment 8 bytes at a time.
Then it sets dbg::TraceEnableFlag to *0xE005003C.
 
It sets up $sp to 0x808FF0, and $gp to 0x80F8A8. Then it sets dbg::TraceEnableFlag to *0xE005003C.


In the dump trace was enabled, and buf_ptr was 0x4002C160.
In the dump trace was enabled, and buf_ptr was 0x4002C160.
Line 15: Line 15:
Then it copies a jmp instruction to the reset-vector, unsure why.
Then it copies a jmp instruction to the reset-vector, unsure why.


Then it calls a function that sets up icache:
Then it calls a function that sets up icache. If icache size is 0, function just returns without doing anything. It also bails if icache line width is < 2 or >= 5 (reserved values).
If icache size is 0, function just returns without doing anything.
It also bails if icache line width is < 2 or >= 5 (reserved values).


Then it calls a function at 0x400B0, which is an uncached code that does:
Then it calls a function at 0x400B0, which is an uncached code that does:
*     Enable icache.
* Enable icache.
*     ORs 0x400 into CFG (this is "reserved" according to datasheet!).
* ORs 0x400 into CFG (this is "reserved" according to datasheet!).


Then it calls a function that just iterates through an empty table of function pointers.
Then it calls a function that just iterates through an empty table of function pointers. C++ object initialization?
C++ object initialization?


Then it calls main().
Then it calls main().


Then it calls a function that just iterates through an empty table of function pointers, again.
Then it calls a function that just iterates through an empty table of function pointers, again. C++ object dtors.
C++ object dtors.


Finally goes into death-mode:
Finally goes into death-mode:


Writes a jump instruction to inf-loop to the reset-vector.
Writes a jump instruction to inf-loop to the reset-vector. This works because jmp instruction encoding contains an abs address.
This works because jmp instruction encoding contains an abs address.


Sets $lp = inf_sleep_loop.
Sets $lp = inf_sleep_loop.


Then it sets up args and jumps to a small stub at 0x008000E0.
Then it sets up args and jumps to a small stub at 0x008000E0. This small stub clears everything in cMeP memory in the region 0x00800100-0x8080FF0. Unknown why last 0x10 bytes are not cleared.
This small stub clears everything in f00d-mem in the region 0x00800100-0x8080FF0.
Unknown why last 0x10 bytes are not cleared.
 


== Teardown ==
== Teardown ==


eep::Teardown sets full protection on 0-0x7F, 0x100-0x17F, 0x200-0x217, 0x300-0x3FF.
eep::Teardown sets full protection on keyslots 0-0x7F, 0x100-0x17F, 0x200-0x217, 0x300-0x3FF.
 
Then it zero-programs and protects the following ranges 0x400-0x47F, 0x500-0x57F, 0x600-0x607, 0x700-0x7FF.
 
 
 
== F00D messages ==
These are sent to ARM using the lower 16-bits at 0xE0000000.
When ARM has read it, it is set to 0.


ARM can write a 32-bit response to 0xE0000010.
Then it zero-programs and protects keyslots 0x400-0x47F, 0x500-0x57F, 0x600-0x607, 0x700-0x7FF.
For ARM->F00D, bit0 is used to indictate the message was written by ARM.


<pre>
== Cmep states ==
    1 = Request succeded
    4 = Debug string
0x101 = Main init started
0x102 = Sm can be loaded/resumed
0x103 = Sm resumed successfully
0x104 = Sm was shut down
0x106 = Main shutting down
0x107 = Suspend beginning
0x108 = Ready for suspending, when using the async version.
0x8016 = Error: Invalid address range
0x802F = Error: Failed to init E003, E006.
</pre>


== Food states ==
<pre>
<pre>
  1 = Secure kernel boot value?
  1 = Secure kernel boot value?
  2 = Secure kernel inited
  2 = Secure kernel initiated
  3 = Secure kernel ready to load
  3 = Secure kernel ready to load
  4 = SM loading
  4 = SM loading
Line 82: Line 52:
  7 = SM suspended
  7 = SM suspended
  8 = Soft rebooting. Set by syscall 1.
  8 = Soft rebooting. Set by syscall 1.
  9 = Shutdown requested. Triggered by ARM command 0x101.
  9 = Shutdown requested. Triggered by ARM ?command? 0x101.
10 = Shutting down.
10 = Shutting down.
</pre>
</pre>


== Debug prints ==
== Debug prints ==
secure_kernel supports tracing to a buffer.
secure_kernel supports tracing to a buffer.


Enabled by -7FF8h($gp) being non-zero.
It is enabled by -7FF8h($gp) being non-zero. Out-buf address is stored in -7FF4($gp). It writes in a loop, 16 bytes at a time, inserting a null-terminator at buf[15] each "line".
Out-buf address is stored in -7FF4($gp).
It writes in a loop, 16 bytes at a time, inserting a null-terminator at buf[15] each "line".


After out-buf is written, writes 0x20000 to 0xE0000000.
After out-buf is written, it writes 0x20000 to 0xE0000000. This will either signal ARM or disable ARM communications. Then infinite loop. This is a panic function.
This will either signal ARM or disable ARM communications.
Then inf-loop, this is a panic function.


=== Print types ===
=== Print types ===
Addresses are xored with a stack cookie that's fixed for all functions.
 
Addresses are xored with a stack cookie that is fixed for all functions.


<pre>
<pre>
Line 105: Line 73:
SWI 1 UnreachableCode:                        00000006
SWI 1 UnreachableCode:                        00000006
SWI 7 UnreachableCode:                        00000007
SWI 7 UnreachableCode:                        00000007
Addrcheck IntegerOverflow Food:                0000000B
Addrcheck IntegerOverflow Cmep:                0000000B
Addrcheck IntegerOverflow Kernel:              0000000B
Addrcheck IntegerOverflow Kernel:              0000000B
Addrcheck IntegerOverflow Module:              0000000C
Addrcheck IntegerOverflow Module:              0000000C
IRQ register func w irq enabled:               0000000E
IRQ register func with irq enabled:           0000000E
Force exit dmac w irq enabled:                 0000000F
Force exit dmac with irq enabled:             0000000F
Crypto irq enabled:                            00000010
Crypto irq enabled:                            00000010
Resuming suspendbuf w irq enabled:             00000012
Resuming suspendbuf with irq enabled:         00000012
Creating suspendbuf w irq enabled:             00000013
Creating suspendbuf with irq enabled:         00000013
Reset:                                        00000014 <xored-exception-lr> <xored-exception-pc> <exception-lr>
Reset:                                        00000014 <xored-exception-lr> <xored-exception-pc> <exception-lr>
RI:                                            00000015 <xored-exception-pc>
RI:                                            00000015 <xored-exception-pc>
Line 133: Line 101:


=== SWI ===
=== SWI ===
When swi-handler is entered stack is set to 0x00807CC0.
When swi-handler is entered stack is set to 0x00807CC0.
It stores context (all regs) on this stack.
It stores context (all regs) on this stack.
Line 143: Line 112:
Syscall numbers >= 8 are ignored by the handler and returns error 0x800F032C.
Syscall numbers >= 8 are ignored by the handler and returns error 0x800F032C.


=== IRQ ===


=== IRQ ===
When irq-handler is entered stack is set to 0x00807B40.
When irq-handler is entered stack is set to 0x00807B40.
Then it reads irq number from control bus space using the ldcb instruction.
Then it reads irq number from control bus space using the ldcb instruction.
Line 164: Line 133:


=== IRQ8 ===
=== IRQ8 ===
IRQ8 is the 8th interrupt.
IRQ8 is the 8th interrupt.
This is the one triggered when ARM sends a request to F00D using 0xE0000010.
This is the one triggered when ARM sends a request to cMeP using 0xE0000010.
And secure_kernel has a handler for this one.
And secure_kernel has a handler for this one.


Line 172: Line 142:
This interrupt is sent when ARM writes to sm cmd registers (0xE0000014 and maybe more?). It's handled inside every sm module.
This interrupt is sent when ARM writes to sm cmd registers (0xE0000014 and maybe more?). It's handled inside every sm module.


== Secure Kernel Commands ==


== Secure Kernel Commands ==
Handler starts with a switch statement that handles reset commands.
Handler starts with a switch statement that handles reset commands.


For a command 0x100401 0x10 is size of shared buffer that's used by f00d handler, 0x4 is command ID, 0x1 is validity flag(?).
For a command 0x100401 0x10 is size of shared buffer that is used by cMeP handler, 0x4 is command ID, 0x1 is validity flag(?).


None of the following 5 commands are enabled/disabled depending on f00d-state.
None of the following 5 commands are enabled/disabled depending on cmep-state. These are all unconditional.
These are all unconditional.


=== 0xB01 ===
=== 0xB01 ===
Used by [[SMC]] 0x13C.
   Reset ScePervasiveResetReg +0x190 to enable re-writing the mask for the protected memory (keyring)
   Reset ScePervasiveResetReg +0x190 to enable re-writing the mask for the protected memory (keyring)
   Set mask to 0x10 for slots 0x20E and 0x20F (set in 0xE0070008)and trigger reset with 0 and 1 to device control 0xE0070000
   Set mask to 0x10 for slots 0x20E and 0x20F (set in 0xE0070008)and trigger reset with 0 and 1 to device control 0xE0070000


=== 0xC01 ===
=== 0xC01 ===
Used by [[SMC]] 0x13C.
   Reads protected mem/keyring slot 0x50C.
   Reads protected mem/keyring slot 0x50C.
   If lower 4 bits are nonzero:
   If lower 4 bits are nonzero:
Line 193: Line 167:


=== 0xD01 ===
=== 0xD01 ===
Used by [[SMC]] 0x13C.
   Reads protected mem/keyring slot 0x50C.
   Reads protected mem/keyring slot 0x50C.
   If lower 4 bits are nonzero:
   If lower 4 bits are nonzero (external boot mode):
     Does same thing as 0xB01.
     Does same thing as 0xB01.


=== 0xE01 ===
=== 0xE01 ===
Used by [[SMC]] 0x13C.
   Does same thing as 0xB01.
   Does same thing as 0xB01.
   Then reads 3 times from ScePervasiveMisc (0xE3100000).
   Then reads 3 times from ScePervasiveMisc (0xE3100000).
Line 203: Line 183:


=== 0xF01: GetSKSO ===
=== 0xF01: GetSKSO ===
Used by [[SMC]] 0x13C.


This command was not existing on FW 1.05.
This command was not existing on FW 1.05.
Line 208: Line 190:
Temp name was GetEncryptedInfoBlk.
Temp name was GetEncryptedInfoBlk.


This command setups an encrypted SKSO then copies it to paddr 0x4001FF00.
This command setups an encrypted SKSO then copies it to physical address 0x4001FF00.


<source lang="C">
<source lang="C">
Line 249: Line 231:
* It appends a AES256CMAC hash of header+data using keyslot 0x514 as key.
* It appends a AES256CMAC hash of header+data using keyslot 0x514 as key.
* It AES-128-CBC encrypts a block of size 0xA0 with key coming from keyslot 0x515, and hardcoded IV from .data.
* It AES-128-CBC encrypts a block of size 0xA0 with key coming from keyslot 0x515, and hardcoded IV from .data.
* It memcpys the encrypted SKSO to paddr 0x4001FF00.
* It memcpys the encrypted SKSO to physical address 0x4001FF00.
* It writes (u32)1, followed by zeroes, to Bigmac keyslot 0x516. That's a sort of status.
* It writes (u32)1, followed by zeroes, to Bigmac keyslot 0x516. That's a sort of status.


 
After processing, -1 is written to physical address 0xE0000010.
 
After processing, -1 is written to 0xE0000010.
Then comes the real switch.
Then comes the real switch.
This one gives different func-ptrs.
This one gives different func-ptrs.
But before func-ptr is called there's a check in a table-lookup based on f00d-state.
But before func-ptr is called there's a check in a table-lookup based on cmep-state.
So not all cmds are allowed in all states. If not allowed error 0x8029 is sent.
So not all cmds are allowed in all states. If not allowed error 0x8029 is sent.


Then it reads lower u16 of f00d mailbox.
Then it reads lower u16 of cMeP mailbox.
If all zeroes then it returns 0x802D.
If all zeroes then it returns 0x802D.
Then it reads lower u16 and now wants it to be 0, if not it returns 0x802D.
Then it reads lower u16 and now wants it to be 0, if not it returns 0x802D.
Line 286: Line 266:


=== 0x101: ArmPanic ===
=== 0x101: ArmPanic ===
   Sets food-state to 9.
 
Used by [[SceSblSmsched]] module_start and [[SceSblSmsched#sceSblSmSchedStopForTZS]].
 
   Sets cmep-state to 9.
   This will later cause main() to return, triggering memclr + infloop.
   This will later cause main() to return, triggering memclr + infloop.


   Then it writes 0xF to control bus addr 0.
   Then it writes 0xF to control bus address 0.
   Control bus addr 0 is interrupt controller, but bits don't match architecture doc.
   Control bus address 0 is interrupt controller, but bits do not match architecture documentation.
   Corrupt code?
   Corrupt code?
   Followed by 3 NOPs. Wat.
   Followed by 3 NOPs. What?


=== 0x500201: LoadModule ===
=== 0x500201: LoadModule ===
   Reads 0x50 bytes from ArmBuffer into buf.
   Reads 0x50 bytes from ArmBuffer into buf.
   If *(buf+4) and *(buf+8) are not aligned to 4 it fails.
   If *(buf+4) and *(buf+8) are not aligned to 4 it fails.
Line 301: Line 285:
   If any of these 3 checks fail, return code is 0x8016 or panic.
   If any of these 3 checks fail, return code is 0x8016 or panic.


   Then it sets food-state to 4.
   Then it sets cmep-state to 4.


   memcpy(sp+0x68, buf+0x30, 0x20);
   memcpy(sp+0x68, buf+0x30, 0x20);
Line 321: Line 305:


=== 0x100301: RestoreModule ===
=== 0x100301: RestoreModule ===
   Reads 0x10 bytes from shared buf.
   Reads 0x10 bytes from shared buf.
   Checks alignment on stuff also.
   Checks alignment on stuff also.
Line 333: Line 318:
<hr>
<hr>


It checks the suspendbuf-state already in f00d-memory.
It checks the suspendbuf-state already in cMeP memory.
Checks magic, version, and size. On fail 0x800F0324.
Checks magic, version, and size. On fail 0x800F0324.
Then it uses the easter value to get the key into keyslot 0x22 and 0x33 somehow.
Then it uses the easter value to get the key into keyslot 0x22 and 0x33 somehow.
Called function does this.
Called function does this.


Then it calculates CMAC of the header that's already in f00d-memory into a buf on stack.
Then it calculates CMAC of the header that is already in cMeP memory into a buffer on stack.


Then it calls a corrupted function that probably is this:
Then it calls a corrupted function that probably is this:
Line 350: Line 335:
         dst=module_base, size=module_size+0x1C0, skip=0x180, shared_buf, sp0=4, sp4=&some_stack_buf)
         dst=module_base, size=module_size+0x1C0, skip=0x180, shared_buf, sp0=4, sp4=&some_stack_buf)


Note that size here is read by the f00d header before the ARM one is copied in.
Note that size here is read by the cMeP header before the ARM one is copied in.
So we can't modify it in this call unfortunately.
So we can't modify it in this call unfortunately.


Then it continues the CMAC calculation on the module just read.
Then it continues the CMAC calculation on the module just read.


Then it calls the update keyslot 0x22 and 0x23 function.
Then it calls the update keyslots 0x22 and 0x23 function.
This is to prevent next suspendbuf from having the same key?
This is to prevent next suspendbuf from having the same key?


Line 363: Line 348:
If hash checks out, it memcopies the contexts to whereever they were saved from.
If hash checks out, it memcopies the contexts to whereever they were saved from.
It loads all func-ptrs for IRQ listeners that were saved.
It loads all func-ptrs for IRQ listeners that were saved.
It resets the F00D io-state that was saved.
It resets the cMeP io-state that was saved.
It sets MeP irq mask, but it makes sure to first mask away non-allowed ones.
It sets cMeP irq mask, but it makes sure to first mask away non-allowed ones.


Then it calls a function that calls the easteregg function.
Then it calls a function that calls the easteregg function.
Line 370: Line 355:


=== 0x100401: RequestModuleSuspend ===
=== 0x100401: RequestModuleSuspend ===
   Reads 0x10 bytes from shared buf.
   Reads 0x10 bytes from shared buf.
   Verifies alignment on *(buf+4), *(buf+8), *(buf+12).
   Verifies alignment on *(buf+4), *(buf+8), *(buf+12).
Line 378: Line 364:
   If bad it returns 8016.
   If bad it returns 8016.


   Sets f00d-state to 6.
   Sets cMeP-state to 6.
   If sm is not ready for suspend, it saves the buf in bss.
   If sm is not ready for suspend, it saves the buf in .bss.
   Also sets the flag that suspend has been requested then returns.
   Also sets the flag that suspend has been requested then returns.


Line 403: Line 389:


==== CreateSuspendBuf ====
==== CreateSuspendBuf ====
Input:
Input:
   r1: shared_buf_contents
   r1: shared_buf_contents
Line 415: Line 402:


Then it writes the following to a struct in .data:
Then it writes the following to a struct in .data:
     +0 = 0x534D4300 (Magic)
     +0 = 0x534D4300 "SMC." (Magic)
     +4 = 1          (Version?)
     +4 = 1          (Version?)
     +8 = <return-of-easteregg-function>
     +8 = <return-of-easteregg-function>
Line 426: Line 413:
     +0xA0
     +0xA0


Then it writes the uer irq handlers to suspend-buf:
Then it writes the user irq handlers to suspend-buf:
     +0x100 = irq_1_func
     +0x100 = irq_1_func
     +0x104 = irq_2_func
     +0x104 = irq_2_func
Line 437: Line 424:
     +0x120 = irq_11_func
     +0x120 = irq_11_func


Then it writes (f00d io state):
Then it writes (cMeP io state):
     +0x124 = *0xE0000004
     +0x124 = *0xE0000004
     +0x128 = *0xE0000008
     +0x128 = *0xE0000008
Line 491: Line 478:
         src=suspend_buf+0x180, size=0x40, skip=module_size+0x180, shared_buf, sp0=3, sp4=&some_stack_buf)
         src=suspend_buf+0x180, size=0x40, skip=module_size+0x180, shared_buf, sp0=3, sp4=&some_stack_buf)


Encryption is done using DMAC channel 0, and CMAC is using channel 1
Encryption is done using DMAC channel 0, and CMAC is using channel 1.


Then it uses an Dmac feature to overwrite the keyslots 0x22 and 0x23 with output from itself.
Then it uses an Dmac feature to overwrite the keyslots 0x22 and 0x23 with output from itself.
Line 497: Line 484:


==== SuspendBufWritePalist ====
==== SuspendBufWritePalist ====
r1=src
r1=src
r2=size
r2=size
Line 568: Line 556:


=== 0x501: SubscribeSuspendAsyncEvent ===
=== 0x501: SubscribeSuspendAsyncEvent ===
   Sets f00d-state to 6.
 
   Sets cmep-state to 6.
   If sm is ready to be suspended, sends 0x108 and move into state 7.
   If sm is ready to be suspended, sends 0x108 and move into state 7.


=== 0x601: ForceExitModule ===
=== 0x601: ForceExitModule ===
   Sets f00d state to 6.
 
   Sets cmep-state to 6.
   Then it calls a function that:
   Then it calls a function that:
     * Calls a function that appears to stop DMAC.
     * Calls a function that appears to stop DMAC.
Line 581: Line 571:


=== 0x80901: SetTraceBuffer ===
=== 0x80901: SetTraceBuffer ===
   Shared buf:
   Shared buf:
     +0: Addr
     +0: Addr
Line 590: Line 581:


=== 0x80A01: SetRevocationList ===
=== 0x80A01: SetRevocationList ===
   Reads 8 bytes from "shared buf".
   Reads 8 bytes from "shared buf".
   Calls the function to set the revocation list.
   Calls the function to set the revocation list.
Line 597: Line 589:
== Syscalls ==
== Syscalls ==


[[Private:Sm_modules]] use these via SWI.
[[Secure_Modules|Secure Modules]] use these syscalls via SWI.


=== 1: Unload ===
=== 1: Unload ===
   Sets f00d-state to 8.
 
   Sets cmep-state to 8.
   Writes r1 to "the shared2-buf".
   Writes r1 to "the shared2-buf".
   Sends msg 1 to ARM.
   Sends msg 1 to ARM.


   Then it uses dmac with operation 0xC to memset entire module region to 0,
   Then it uses dmac with operation 0xC to memset entire module region to 0,
   Finally it jumps to secure_kernel entrypoint, triggering a softreboot.
   Finally it jumps to secure_kernel entrypoint, triggering a soft reboot.


=== 2: ReadyToSuspend ===
=== 2: ReadyToSuspend ===
   This just writes 1 to a state-field and returns 1,
   This just writes 1 to a state-field and returns 1,
   or returns 0x800F0329 if it's already non-zero.
   or returns 0x800F0329 if it is already non-zero.


   This flag tells kernel whether or not module ready to be suspended.
   This flag tells kernel whether or not module is ready to be suspended.


=== 3: SuspendSelfIfRequested ===
=== 3: SuspendSelfIfRequested ===
   Returns 0x800F0329 if SuspendReady wasn't called prior to this.
   Returns 0x800F0329 if SuspendReady wasn't called prior to this.
   It checks a flag if 0x501 cmd has subscribed to the event.
   It checks a flag if 0x501 cmd has subscribed to the event.
   Then it will send msg 0x108 to ARM and goes into f00d-state 7.
   Then it will send msg 0x108 to ARM and goes into cmep-state 7.


   Regardless of that flag it also checks whether or not ARM has requested suspend.
   Regardless of that flag it also checks whether or not ARM has requested suspend.
Line 623: Line 618:


=== 4: RegisterIrqHandler ===
=== 4: RegisterIrqHandler ===
   r1 = irq-number
   r1 = irq-number
   r2 = func-ptr (or NULL to deregister)
   r2 = func-ptr (or NULL to deregister)
Line 647: Line 643:


=== 5: TracePrintf ===
=== 5: TracePrintf ===
   This just printfs with "%s" to the emit buffer, and returns 1.
   This just printfs with "%s" to the emit buffer, and returns 1.


=== 6: CheckRvkList ===
=== 6: CheckRvkList ===
  If r1 or r2 is 0 it returns 0x800F0B16.
  Then it checks that r1 size 0x130 is in module region.
  And that r2 size 0x80 also, same error.


   It returns 0x800F0326 if no rvk list has been inited.
<source lang="C">
   After that it returns result of the revocation checking function
// Used in FW 0.931.010 secure_kernel.xxx, for command ??
  (same as used for sm normally).
typedef struct unk_0x30 { // size is 0x30 on FW 0.931.010
  SceSize size; // Size of this structure
  SceUInt32 maybe_self_type_or_success; // always 1 in 0.931.010 secure_kernel.xxx
  SceUInt64 program_sceversion; // from program_identification_header
  char elf_digest[0x20]; // SHA256 of the ELF, from PSVita_elf_digest_header
} unk_0x30;
 
// Used in FW 3.600.011 secure_kernel, for command 6
typedef struct cmd_6_args { // size is 0x80 bytes on FW 3.600.011
SceSize size; // Size of this structure
SceUInt32 maybe_self_type_or_success;
SceUInt64 program_authority_id; // from program_identification_header
SceUInt8 elf_digest[0x20]; // SHA256 of the ELF, from PSVita_elf_digest_header
SceUInt64 program_sceversion; // from program_identification_header
SceUInt64 padding_0x38; // ?
SceSharedSecret shared_secret; // from PSVita_shared_secret_header
} cmd_6_args;
</source>
 
  If r1 or r2 is 0, it returns 0x800F0B16.
  It checks that r1 size (0x130) is in module region, same error.
   It checks that r2 size (0x80) is in module region, same error.
 
  If no revoke list has been initialized, it returns 0x800F0326.
   After that, it returns the result of the revoke checking function (same as used for SM normally).
 
Check if program-authority-id is forbidden by prog_rvk.
 
Check platform. This is ignored on Diag platform or if there is a platform check bypass attribute.
 
Check if Media Type is in range.
 
Check if Media Type is allowed in attribute.
 
Bypass media type checks if Media Type is 7 (host0:) and there is enough QA flags set.
 
Two product mode checks.
 
Attribute/mode checks that are not set in release build.
 
QA flag 0xD::1 checks for two attributes.
 
QA flag 0xF::1 check for one attribute.
 
Compares SharedSecret if capability bit 32 is not set.
 
Two attribute/self_level checks.
 
Kit activation check.
 
Check for attr::magicgate or qaf::magicgate in Tool/Test platforms.
 
If it is a non-game/fSELF program and SELF version is 0, or if SELF version is less than or equal to the value in keyring 0x50E, branch to the last check.
 
All successful except Media Types 13 and 14.
 
SELF version must be non-zero. If the self version is less than or equal to the value in keyring 0x50E, it is successful.


=== 7: UnloadPanic ===
=== 7: UnloadPanic ===
   This one trace-printfs.
   This one trace-printfs.
   Then it sets f00d-state to 8.
   Then it sets cmep-state to 8.
   Then it writes 0x800F033B to the "0x40 buffer".
   Then it writes 0x800F033B to the "0x40 buffer".
   Then corrupted insn?
   Then corrupted insn?
   Then it zeroes the module region and soft-reboots.
   Then it zeroes the module region and soft-reboots.


=== 8: ReadEepromFlag ===
=== 8: IsDevelopmentMode ===
   This reads a bool from eeprom block 0x510 byte16 bit31.
 
   Reads DIPSW from Bigmac keyslot 0x510 and returns true if the Development Mode flag is on.


== Misc func doc ==
== Misc func doc ==


=== SuspendModule ===
=== SuspendModule ===
Sends arm message 0x107.
Sends arm message 0x107.


Line 680: Line 733:


=== LoadModule ===
=== LoadModule ===
If either num_paddrs and paddr_list is NULL, return error 0x8016.
If either num_paddrs and paddr_list is NULL, return error 0x8016.
Then it copies rsa pubkey + exponent to stack bufs.
Then it copies rsa pubkey + exponent to stack bufs.
Line 687: Line 741:
     *0xE0050104 = 3;
     *0xE0050104 = 3;
</pre>
</pre>
...


...
Ends up calling SceDecrypt.
Ends up calling SceDecrypt.


=== SceDecrypt ===


=== SceDecrypt ===
<pre>
<pre>
r1=out, ptr to {buffer = module end region - 0x800, size = 0x800}
r1=out, ptr to {buffer = module end region - 0x800, size = 0x800}
Line 803: Line 857:


=== main() ===
=== main() ===
Interrupts are disabled when this is called.
Interrupts are disabled when this is called.
First thing it writes 0x2000F to 0xE0020000. Probably sets up the device.
First thing it writes 0x2000F to 0xE0020000. Probably sets up the device.


It checks the f00d-state, and if it's 1 (cold boot?), it writes:
It checks the cmep-state, and if it's 1 (cold boot?), it writes:
   * Writes 0xFFFFFFFF to 0xE0000010.
   * Writes 0xFFFFFFFF to 0xE0000010.
   * Calls a big function that initializes 0xE0030000.
   * Calls a big function that initializes 0xE0030000.
   * Sends ARM-message 0x101.
   * Sends ARM-message 0x101.
   * Sets f00d-state to 2.
   * Sets cmep-state to 2.
   * Waits for ARM to send address for shared buffer and writes it to a state.
   * Waits for ARM to send address for shared buffer and writes it to a state.
   * Writes 0xFFFFFFFF to the ARM mailbox.
   * Writes 0xFFFFFFFF to the ARM mailbox.
Line 816: Line 871:
     Sends msg 0x802F to ARM
     Sends msg 0x802F to ARM
     Calls a function that deinitializes 0xE0030000.
     Calls a function that deinitializes 0xE0030000.
     Returns from main(), triggering F00D memclear + hang.
     Returns from main(), triggering cMeP memclear + hang.


Then it calls the following function regardless of state:
Then it calls the following function regardless of state:
Line 824: Line 879:


   This function detects downgrades!
   This function detects downgrades!
   if 0x1692000 != eeprom_read_fw_version(): return 0x800F0337;
   if 0x1692000 != read_fw_version(): return 0x800F0337;
   return 1;
   return 1;


Line 832: Line 887:
Then it calls a function that enables "software interrupt 3", dafuk is this.
Then it calls a function that enables "software interrupt 3", dafuk is this.


Then it sets f00d state to 3.
Then it sets cmep-state to 3.
Then it sends msg 0x102 to ARM.
Then it sends msg 0x102 to ARM.


Line 866: Line 921:


=== Range checks ===
=== Range checks ===
<pre>
<pre>
   ArmTz:  if (x >= 0x40000000 || x < 0x40300000) return 1;
   ArmTz:  if (x >= 0x40000000 || x < 0x40300000) return 1;
Line 883: Line 939:
           if (addr < 0x0080A000) return 0;
           if (addr < 0x0080A000) return 0;
           return addr < (0x0080A000+0x00016000)
           return addr < (0x0080A000+0x00016000)
   Food:
   Cmep:
           if (addr < 0x00800000) return 0;
           if (addr < 0x00800000) return 0;
           return addr < 0x00820000
           return addr < 0x00820000
</pre>
</pre>
=== swi_handler ===
{| class="wikitable"
! Firmware !! Address
|-
| 3.60 || 0x800e28
|}
=== self_header_auth ===
{| class="wikitable"
! Firmware !! Address
|-
| 3.60 || 0x803586
|}
=== self_rvk_check ===
{| class="wikitable"
! Firmware !! Address
|-
| 3.60 || 0x80594e
|}


== Changelog ==
== Changelog ==


Base is 1.692.
=== Diff FW 1.692 vs. FW 1.05 ===


=== Diff vs. 105 ===
<pre>
<pre>
Stack cookies weren't enabled.
Stack cookies were not enabled.


Debug vector was initially a "reti" instruction, was later replaced with infloop.
Debug vector was initially a "reti" instruction, was later replaced with infloop.
Line 913: Line 992:
   Did not disable interrupts.
   Did not disable interrupts.


eeprom::Init
keyslots::Init
   Changed a lot. todo: Investigate later.
   Changed a lot. todo: Investigate later.


Line 921: Line 1,000:
Cmd 0xF01 did not exist.
Cmd 0xF01 did not exist.


All f00d cmds that read from the shared region uses a buf in .bss instead of stack.
All cMeP commands that read from the shared region uses a buf in .bss instead of stack.
   This allowed a race condition where you could request a suspend using cmd 0x401.
   This allowed a race condition where you could request a suspend using command 0x401.
   Then just before the sm calls syscall 3 you send another F00D cmd to overwrite the sharedbuf.
   Then just before the sm calls syscall 3 you send another cMeP command to overwrite the sharedbuf.
   This will call sm::Suspend on a buffer that's not properly checked.
   This will call sm::Suspend on a buffer that's not properly checked.


Line 932: Line 1,011:
   Code was added to prevent it for ending up in an infinite recursion if stack cookie gets corrupted.
   Code was added to prevent it for ending up in an infinite recursion if stack cookie gets corrupted.


The foodcmd::AllowedCmdInStateTable was unchanged.
The cmep_cmd::AllowedCmdInStateTable was unchanged.
</pre>
</pre>


=== Diff vs. 160 ===
=== Diff FW 1.692 vs. FW 1.60 ===
 
<pre>
<pre>
.bss size grew from 0x478 bytes to 0x480.
.bss size grew from 0x478 bytes to 0x480.
Line 942: Line 1,022:
Rev changed from 5209->5679
Rev changed from 5209->5679


foodcmd::Parse changed:
cmep_cmd::Parse changed:
   Stackframe grew 0x20 bytes.
   Stackframe grew 0x20 bytes.
   The function that that changed appears to be 0x501.
   The function that that changed appears to be 0x501.
Line 948: Line 1,028:
GetFirmwareVersion returns 0x16920000 instead of 0x16000000 (duh!).
GetFirmwareVersion returns 0x16920000 instead of 0x16000000 (duh!).


eeprom::Init changed:
keyslots::Init changed:
   Some bitmask in was chanaged from 0x3FFFDF to 0xFFFFDF.
   Some bitmask in was changed from 0x3FFFDF to 0xFFFFDF.
   Branch was removed here that programmed Eeprom blk 0x516 to all zeroes.
   Branch was removed here that used to set Bigmac keyslot 0x516 to all zeroes.
   The function was also extended a lot.
   The function was also extended a lot.


   Idea: Does this possibly protect additional EEPROM stuff?
   Idea: Does this possibly protect additional keyslots stuff?
   However otp::Teardown looks identical.
   However otp::Teardown looks identical.


Line 1,005: Line 1,085:
Then some code to talk to 0xE004???? was removed.
Then some code to talk to 0xE004???? was removed.
</pre>
</pre>
=== Diff of various FWs ===
Base is previous FW version.
{| class="wikitable"
|-
! FW version !! What changed compared to previous FW version
|-
| 3.36 || unknown
|-
| 3.50 || Stack base changed. (see func, 3.36-3.50:0x80030a)
Added new functions. (see func, 3.50:[0x805310, 0x80541e, 0x80542a, 0x80543a, 0x805448, 0x805456, 0x805466, 0x80548c])
Function changed. (see func, 3.36:0x80560e/3.50:0x80594e)
Function changed. (see func, 3.36:0x807012?/3.50:0x806ef6)
Some strings are aligned by 4.
|}
[[Category:Cmep]]

Revision as of 18:25, 17 September 2023

Entrypoint

The entrypoint of secure_kernel is +0x100.

First thing it does is disable irq. Then it zeroes the .bss segment 8 bytes at a time.

It sets up $sp to 0x808FF0, and $gp to 0x80F8A8. Then it sets dbg::TraceEnableFlag to *0xE005003C.

In the dump trace was enabled, and buf_ptr was 0x4002C160.

Then it sets up cfg:

    * Clears bit4 (EVM), this moves exception vectors to 0x00800000.
    * Sets bit3 (IVM), this enables interrupt table at 0x00800030.

Then it copies a jmp instruction to the reset-vector, unsure why.

Then it calls a function that sets up icache. If icache size is 0, function just returns without doing anything. It also bails if icache line width is < 2 or >= 5 (reserved values).

Then it calls a function at 0x400B0, which is an uncached code that does:

  • Enable icache.
  • ORs 0x400 into CFG (this is "reserved" according to datasheet!).

Then it calls a function that just iterates through an empty table of function pointers. C++ object initialization?

Then it calls main().

Then it calls a function that just iterates through an empty table of function pointers, again. C++ object dtors.

Finally goes into death-mode:

Writes a jump instruction to inf-loop to the reset-vector. This works because jmp instruction encoding contains an abs address.

Sets $lp = inf_sleep_loop.

Then it sets up args and jumps to a small stub at 0x008000E0. This small stub clears everything in cMeP memory in the region 0x00800100-0x8080FF0. Unknown why last 0x10 bytes are not cleared.

Teardown

eep::Teardown sets full protection on keyslots 0-0x7F, 0x100-0x17F, 0x200-0x217, 0x300-0x3FF.

Then it zero-programs and protects keyslots 0x400-0x47F, 0x500-0x57F, 0x600-0x607, 0x700-0x7FF.

Cmep states

 1 = Secure kernel boot value?
 2 = Secure kernel initiated
 3 = Secure kernel ready to load
 4 = SM loading
 5 = SM loaded
 6 = SM suspending
 7 = SM suspended
 8 = Soft rebooting. Set by syscall 1.
 9 = Shutdown requested. Triggered by ARM ?command? 0x101.
10 = Shutting down.

Debug prints

secure_kernel supports tracing to a buffer.

It is enabled by -7FF8h($gp) being non-zero. Out-buf address is stored in -7FF4($gp). It writes in a loop, 16 bytes at a time, inserting a null-terminator at buf[15] each "line".

After out-buf is written, it writes 0x20000 to 0xE0000000. This will either signal ARM or disable ARM communications. Then infinite loop. This is a panic function.

Print types

Addresses are xored with a stack cookie that is fixed for all functions.

SuspendEncrypt BadAddr:                        00000003
SWI 1 BadAddr:                                 00000004
SWI 1 UnreachableCode:                         00000006
SWI 7 UnreachableCode:                         00000007
Addrcheck IntegerOverflow Cmep:                0000000B
Addrcheck IntegerOverflow Kernel:              0000000B
Addrcheck IntegerOverflow Module:              0000000C
IRQ register func with irq enabled:            0000000E
Force exit dmac with irq enabled:              0000000F
Crypto irq enabled:                            00000010
Resuming suspendbuf with irq enabled:          00000012
Creating suspendbuf with irq enabled:          00000013
Reset:                                         00000014 <xored-exception-lr> <xored-exception-pc> <exception-lr>
RI:                                            00000015 <xored-exception-pc>
ZDIV:                                          00000016 <xored-exception-pc>
Trace:                                         00000017 <func-addr>
SWI 7:                                         00000018 <xored-r1>
DMAC when updating suspendbuf key:             00000019 <ret-val>
DMAC cmac bad enum:                            0000001A <enum-value>
Bad enum to suspend AES-CBC function:          0000001B <enum-value>
Generating new suspendbuf key failed:          0000001C
Creating suspendbuf w irq8 module-registered:  0000001D
Addrcheck IntegerOverflow Tz:                  0000001E
Addrcheck IntegerOverflow Tz2:                 0000001F
Bad ptr to suspend AES-CBC function:           00000020
IRQ register func w bad irq number:            00000022
Recieved unknown IRQ:                          00000023

Exceptions

SWI

When swi-handler is entered stack is set to 0x00807CC0. It stores context (all regs) on this stack. Then stack is set to 0x00807C40 from which it loads SPRs $tp, $gp, $sp. After SWI has been executed, it restores context from first stack.

in_r4 = Syscall number, starting with 1.

There are only 8 syscalls. Syscall numbers >= 8 are ignored by the handler and returns error 0x800F032C.

IRQ

When irq-handler is entered stack is set to 0x00807B40. Then it reads irq number from control bus space using the ldcb instruction. Irq number must be < 12, otherwise panic.

Then it sets up a special "user irq" context:

 sp = 0x0080B000
 tp = userctx.tp
 gp = userctx.gp 
 <rest same as kernel irq context>

And calls the function with r1=irq_id. The table is empty in the dump.

Above holds for all irqs except 8, which is special, see below.

Just before calling the callback, interrupts are enabled again. This leads to reentracy vulnerabilities.

IRQ8

IRQ8 is the 8th interrupt. This is the one triggered when ARM sends a request to cMeP using 0xE0000010. And secure_kernel has a handler for this one.

IRQ9

This interrupt is sent when ARM writes to sm cmd registers (0xE0000014 and maybe more?). It's handled inside every sm module.

Secure Kernel Commands

Handler starts with a switch statement that handles reset commands.

For a command 0x100401 0x10 is size of shared buffer that is used by cMeP handler, 0x4 is command ID, 0x1 is validity flag(?).

None of the following 5 commands are enabled/disabled depending on cmep-state. These are all unconditional.

0xB01

Used by SMC 0x13C.

 Reset ScePervasiveResetReg +0x190 to enable re-writing the mask for the protected memory (keyring)
 Set mask to 0x10 for slots 0x20E and 0x20F (set in 0xE0070008)and trigger reset with 0 and 1 to device control 0xE0070000

0xC01

Used by SMC 0x13C.

  Reads protected mem/keyring slot 0x50C.
  If lower 4 bits are nonzero:
    Reset ScePervasiveResetReg +0x190 to enable re-writing the mask for the protected memory (keyring)
    Set mask to 0x10 for slots 0x20E and 0x20F (set in 0xE0070008)and trigger reset with 0 (not 1 this time) to device control 0xE0070000

0xD01

Used by SMC 0x13C.

  Reads protected mem/keyring slot 0x50C.
  If lower 4 bits are nonzero (external boot mode):
    Does same thing as 0xB01.

0xE01

Used by SMC 0x13C.

 Does same thing as 0xB01.
 Then reads 3 times from ScePervasiveMisc (0xE3100000).
 It reads three times, if first read != 0x20, second != 0x30, third != 0x31 then it writes 6 to 0xE0070014.

0xF01: GetSKSO

Used by SMC 0x13C.

This command was not existing on FW 1.05.

Temp name was GetEncryptedInfoBlk.

This command setups an encrypted SKSO then copies it to physical address 0x4001FF00.

typedef struct SceSKSOData_169 {
    char data_0x511[0x20]; // Comes from Bigmac keyslot 0x511
    char data_0x512[0x20]; // Comes from Bigmac keyslot 0x512
    char data_0x517[0x20]; // Comes from Bigmac keyslot 0x517
} SceSKSO_169;

typedef struct SceSKSOData_360 {
    char data_0x511[0x20]; // Comes from Bigmac keyslot 0x511
    char data_0x512[0x20]; // Comes from Bigmac keyslot 0x512
    char data_0x517[0x20]; // Comes from Bigmac keyslot 0x517
    char data_0x519[0x20]; // Comes from Bigmac keyslot 0x519
} SceSKSO_360;

typedef struct SceSKSOHeader {
    SceInt32 magic; // Magic (0xACB4ACB1 = -0x534B534F -> "SKSO")
    SceInt32 unk_one; // Always 1
    SceUInt32 random; // Pseudo random number (read from 0xE005003C)
    SceInt32 zero_or_padding; // Always 0
} SceSKSOHeader;

typedef struct SceSKSO_169 {
    SceSKSOHeader header;
    SceSKSOData_169 data;
    char cmac_hash[0x10]; // AES256CMAC hash using keyslot 0x514 as key
} SceSKSO_169;

typedef struct SceSKSO_360 {
    SceSKSOHeader header;
    SceSKSOData_360 data;
    char cmac_hash[0x10]; // AES256CMAC hash using keyslot 0x514 as key
} SceSKSO_360;

This command does:

  • It generates SKSO header.
  • It generates SKSO data by reading keyslots 0x511, 0x512, 0x517 (and 0x519 on newer FWs).
  • It appends a AES256CMAC hash of header+data using keyslot 0x514 as key.
  • It AES-128-CBC encrypts a block of size 0xA0 with key coming from keyslot 0x515, and hardcoded IV from .data.
  • It memcpys the encrypted SKSO to physical address 0x4001FF00.
  • It writes (u32)1, followed by zeroes, to Bigmac keyslot 0x516. That's a sort of status.

After processing, -1 is written to physical address 0xE0000010. Then comes the real switch. This one gives different func-ptrs. But before func-ptr is called there's a check in a table-lookup based on cmep-state. So not all cmds are allowed in all states. If not allowed error 0x8029 is sent.

Then it reads lower u16 of cMeP mailbox. If all zeroes then it returns 0x802D. Then it reads lower u16 and now wants it to be 0, if not it returns 0x802D.

After this it finally calls the funcptr for cmdhandler. For unknown cmdid it returns 0x802A.

The allowed commands are as follows:

  State: 0   Allowed: 0
  State: 1   Allowed: 0
  State: 2   Allowed: 2
  State: 3   Allowed: 78E
  State: 4   Allowed: 2
  State: 5   Allowed: 72
  State: 6   Allowed: 42
  State: 7   Allowed: 2
  State: 8   Allowed: 2
  State: 9   Allowed: 0
  State: 10  Allowed: 0

todo: Decode these.

0x101: ArmPanic

Used by SceSblSmsched module_start and SceSblSmsched#sceSblSmSchedStopForTZS.

 Sets cmep-state to 9.
 This will later cause main() to return, triggering memclr + infloop.
 Then it writes 0xF to control bus address 0.
 Control bus address 0 is interrupt controller, but bits do not match architecture documentation.
 Corrupt code?
 Followed by 3 NOPs. What?

0x500201: LoadModule

 Reads 0x50 bytes from ArmBuffer into buf.
 If *(buf+4) and *(buf+8) are not aligned to 4 it fails.
 Then it TZ-checks addr *(buf+4) size 4.
 Then it TZ-checks addr *(buf+4) size 8.
 If any of these 3 checks fail, return code is 0x8016 or panic.
 Then it sets cmep-state to 4.
 memcpy(sp+0x68, buf+0x30, 0x20);
 *(sp+0x58)  = *(buf+0x28)
 *(sp+0x5C)  = *(buf+0x2C)
 *(sp+0x178) = *(buf+0x24)
 *(sp+0x54)  = *(buf+0x20)
 Then it calls the big function to load the SM with args (sp, sp+0x50).
 If it fails then either panics or returns 0x800000FF.
 It saves the address for "0x40-buf", without checking it to be in Tz.
 This is okay because they check before writing to it.
 Then it gets the address for the "shared buf", and loads arg0-arg3 from this ptr.
 Then it sets epc to 0x0080B000, sp to 0, gp to 0.
 And uses reti to jump to it.

0x100301: RestoreModule

  Reads 0x10 bytes from shared buf.
  Checks alignment on stuff also.
  TZ-check on *(buf+4) size 4, *(buf+8) size 8, *(buf+12) size 0x18. On fail sends 0x8016.
  After that it sets state to 4.
  Then it calls the big restore function.
  If it fails it sends zeroes module region, sends reply 0x8024, sets state to 3, and sends 0x102.
  If success, it sets the "0x40-buf" to *(buf+8), sets state to 5, and sends back 0x103.

It checks the suspendbuf-state already in cMeP memory. Checks magic, version, and size. On fail 0x800F0324. Then it uses the easter value to get the key into keyslot 0x22 and 0x33 somehow. Called function does this.

Then it calculates CMAC of the header that is already in cMeP memory into a buffer on stack.

Then it calls a corrupted function that probably is this:

   SuspendBufReadPalist(
       dst=suspendContextBuf, size=0x140, skip=0x40, shared_buf, sp0=4, sp4=&some_stack_buf)

Then it continues the CMAC calculation on both contexts just read from ARM.

Then it calls a corrupted function that probably is this:

   SuspendBufReadPalist(
       dst=module_base, size=module_size+0x1C0, skip=0x180, shared_buf, sp0=4, sp4=&some_stack_buf)

Note that size here is read by the cMeP header before the ARM one is copied in. So we can't modify it in this call unfortunately.

Then it continues the CMAC calculation on the module just read.

Then it calls the update keyslots 0x22 and 0x23 function. This is to prevent next suspendbuf from having the same key?

After that it does a timing-safe memcmp loop to verify the CMAC on stack against the header. Returns 0x800F0324 on CMAC cmpfail.

If hash checks out, it memcopies the contexts to whereever they were saved from. It loads all func-ptrs for IRQ listeners that were saved. It resets the cMeP io-state that was saved. It sets cMeP irq mask, but it makes sure to first mask away non-allowed ones.

Then it calls a function that calls the easteregg function. Probably to generate a new key.

0x100401: RequestModuleSuspend

 Reads 0x10 bytes from shared buf.
 Verifies alignment on *(buf+4), *(buf+8), *(buf+12).
 Verifies the following tz-ptrs:
   *(buf+8) size 4
   *(buf+4) size 8
   *(buf+12) size 0x18
 If bad it returns 8016.
 Sets cMeP-state to 6.
 If sm is not ready for suspend, it saves the buf in .bss.
 Also sets the flag that suspend has been requested then returns.
 If sm is ready to suspend, it calls the suspend function.

Context structure:

This is used in suspend buffer at offsets +0x40 and +0xA0.

0x0:  udef $1   $2   $3
0x10: $4   $5   $6   $7
0x20: $8   $9   $10  $11
0x30: $12  $tp  $gp  $sp
0x40: $lp  $sar $rpb $rpe
0x50: $rpc $hi  $lo  $epc

First word +0 is not used.

The special "$tmp" register is not context-saved. So this could be attacked if there is SM code that is using it.

CreateSuspendBuf

Input:

 r1: shared_buf_contents
 r2: 0=is_arm_cmd, 1=is_syscall

It calculates the size of suspend buf as follows:

  module_region_size + 0x1C0

Then it calls an easteregg function? Then it memsets a buffer size 0x10 to zeroes. Then it checks and panics if irq enabled.

Then it writes the following to a struct in .data:

    +0 = 0x534D4300 "SMC." (Magic)
    +4 = 1          (Version?)
    +8 = <return-of-easteregg-function>
    +12 = total_size

Then, depending on r2, it copies 0x60 from the irq8/swi saved-user-context to:

   +0x40

Then it copies the non-8-irq saved-user-context to:

   +0xA0

Then it writes the user irq handlers to suspend-buf:

   +0x100 = irq_1_func
   +0x104 = irq_2_func
   +0x108 = irq_3_func
   +0x10C = irq_4_func
   +0x110 = irq_5_func
   +0x114 = irq_6_func
   +0x118 = irq_9_func
   +0x11C = irq_10_func
   +0x120 = irq_11_func

Then it writes (cMeP io state):

   +0x124 = *0xE0000004
   +0x128 = *0xE0000008
   +0x128 = *0xE000000C (Bug in sony code, this should be 0x12C!)
   +0x130 = *0xE0000014
   +0x134 = *0xE0000018
   +0x138 = *0xE000001C

Then it memsets:

   +0x140 size 0x40 = zeroes

Then it copies the following to a ARM shared buf ptr:

   *(*(buf+12)   ) = *(suspend_buf+0x130);
   *(*(buf+12)+ 4) = *(suspend_buf+0x134);
   *(*(buf+12)+ 8) = *(suspend_buf+0x138);
   *(*(buf+12)+12) = *(suspend_buf+0x124);
   *(*(buf+12)+16) = *(suspend_buf+0x128);
   *(*(buf+12)+16) = *(suspend_buf+0x12C);

Without verifying ptr, but ptr was verified in cmdhandler!

Then it gets the MeP irq-mask and stores it at:

    +0x13C = mep_irq_mask

Then it panics if module has irq8 registered, but this cannot happen.

Then it calls SuspendBufWritePalist to copy the plaintext header:

   SuspendBufWritePalist(
       src=suspend_buf, size=0x40, skip=0, shared_buf, sp0=1, sp4=0)

Then it starts calculating a AES-CMAC over this header:

   dmacAesCmacKeyslot23(
       dst=suspend_buf+0x180, src=suspend_buf+0, size=0x40, 1/*start*/)

Then it calls SuspendBufWritePalist again to copy the first encrypted part:

   SuspendBufWritePalist(
       src=suspend_buf+0x40, size=0x140, skip=40, shared_buf, sp0=3, sp4=&some_stack_buf)

Then it sets up args for another CMAC call but corrupted instruction?

   dmacAesCmacKeyslot23(
       dst=suspend_buf+0x180, src=suspend_buf+0, size=0x140, 2/*update*/)

Then it finally writes the encrypted module:

   SuspendBufWritePalist(
       src=module_base, size=module_size, skip=0x180, shared_buf, sp0=3, sp4=&some_stack_buf)

Then another corrupted instruction ruins things.. :(

   dmacAesCmacKeyslot23(
       dst=suspend_buf+0x180, src=module_base, size=module_size, 3/*final*/)

Then it writes the CMAC on the end:

   SuspendBufWritePalist(
       src=suspend_buf+0x180, size=0x40, skip=module_size+0x180, shared_buf, sp0=3, sp4=&some_stack_buf)

Encryption is done using DMAC channel 0, and CMAC is using channel 1.

Then it uses an Dmac feature to overwrite the keyslots 0x22 and 0x23 with output from itself. It uses AES-ECB to crypt a key in .data with key from keyslot 0x347.

SuspendBufWritePalist

r1=src r2=size r3=skip_offset r4=shared_buf_ptr sp0: 0=plaintext, 1=plaintext, 2=encrypt, 3=decrypt, 4=? sp4=?

It panics if shared_buf_ptr is NULL. Then it loops through pa-list and writes, each entry is checked to be within Tz region. If it is outside, then panic with value 3.

For pseudocode see below:

u32 size = r2;
u32 left = r3;
u32 switch_id = sp0;

for(i=0; i<num_pa; i++) {
  pa_t pa;
  read_pa(&pa, pa_list[i]);

  // <checks tz region on pa>

  u32 out = pa->addr;
  u32 sz  = pa->size;

  if (skip != 0) {
    if (pa->size < skip) {
      skip -= pa->size;
      continue;
    }

    out += skip;
    sz  -= skip;
  }

  sz = MAX(sz, left); // only write at most how much we have left
  switch(switch_id) {
  case 0:
    dmacSetup(0,dst,src,buf,0,0,0,1);
    break;
  case 1:
    dmacSetup(0,dst,src,buf,0,0,0,1);
    break;
  case 2: // start decrypt
    dmacSetup(0,dst,src,buf,0x22,*sp4,0x10A,1);
    break;
  case 3: // start encrypt
    if (sp4 == shared_buf_ptr) panic("%08x\n", 0x20);
    dmacSetup(0,dst,src,buf,0x22,*sp4,0x109,1);
    break;
  case 4: // hash? dmac function 0x13B
    // todo
  default: panic("%08x %08x\n", 0x1B, switch_id);
  }

  if (dmacWaitForFinish(0)) {
     panic("%08x %08x\n", 0x19, err_code);
  }

  left -= sz;
  src  += sz;
}

Note:

  • It only checked the palist ptr for size=8 in the cmdhandler.
  • Thus it can read outside Tz region for rest of palist.

0x501: SubscribeSuspendAsyncEvent

 Sets cmep-state to 6.
 If sm is ready to be suspended, sends 0x108 and move into state 7.

0x601: ForceExitModule

 Sets cmep-state to 6.
 Then it calls a function that:
    * Calls a function that appears to stop DMAC.
    * Zeroes the module region.
    * Flushes cache.
    * Writes some unknown DMAC registers.
    * Does a soft-reset.

0x80901: SetTraceBuffer

 Shared buf:
   +0: Addr
   +4: Size
 Checks that region is valid TZ, then sets them in the state.
 On bad addr, it sends error 0x8016, otherwise it sends 1.
 It also prints "rev %s\n" with "5679", however that only happens for some OTP configs.

0x80A01: SetRevocationList

 Reads 8 bytes from "shared buf".
 Calls the function to set the revocation list.
 If that function returns error x, the error that gets sent back is x&0xFF.
 On success, 1 is sent back.

Syscalls

Secure Modules use these syscalls via SWI.

1: Unload

  Sets cmep-state to 8.
  Writes r1 to "the shared2-buf".
  Sends msg 1 to ARM.
  Then it uses dmac with operation 0xC to memset entire module region to 0,
  Finally it jumps to secure_kernel entrypoint, triggering a soft reboot.

2: ReadyToSuspend

  This just writes 1 to a state-field and returns 1,
  or returns 0x800F0329 if it is already non-zero.
  This flag tells kernel whether or not module is ready to be suspended.

3: SuspendSelfIfRequested

  Returns 0x800F0329 if SuspendReady wasn't called prior to this.
  It checks a flag if 0x501 cmd has subscribed to the event.
  Then it will send msg 0x108 to ARM and goes into cmep-state 7.
  Regardless of that flag it also checks whether or not ARM has requested suspend.
  If so then it calls the suspend function.
  Otherwise it just returns 1.

4: RegisterIrqHandler

  r1 = irq-number
  r2 = func-ptr (or NULL to deregister)
  r3 = old-func-ptr-out (or NULL to ignore)
  Checks that r1 is < 12, and it also checks it against a mask of allowed sm IRQs:
    if (r1*2) & 0xE7E == 0: fail
  On either failure returns 0x800F0316.
  Checks that r2 is inside the module range or NULL, returns 0x800F030E on fail.
  Checks that r3 is inside the module range or NULL, returns 0x800F030E on fail.
  Returns 0x800F0316 if r2 not aligned to 2, or if (r3 & 2) != 0.
  Calls a function to get current function ptr in the irq-table entry for irq-number r1.
  Then it calls a function to register irq listener.
  This function does the following:
     Check addr, check irq number, panic on fail.
     Write r2 to IRQ func-ptr table.
     It reads the MeP interrupt mask register from control bus.
     Then depending on r2 was NULL or not, it sets n:th bit in the mask, where n is the IRQ number.
     Then it writes it back to MeP to enable the interrupt.
  Then it will write the old-func-ptr (before was overwritten) to r3, if r3 not set to NULL.

5: TracePrintf

  This just printfs with "%s" to the emit buffer, and returns 1.

6: CheckRvkList

// Used in FW 0.931.010 secure_kernel.xxx, for command ??
typedef struct unk_0x30 { // size is 0x30 on FW 0.931.010
   SceSize size; // Size of this structure
   SceUInt32 maybe_self_type_or_success; // always 1 in 0.931.010 secure_kernel.xxx
   SceUInt64 program_sceversion; // from program_identification_header
   char elf_digest[0x20]; // SHA256 of the ELF, from PSVita_elf_digest_header
} unk_0x30;

// Used in FW 3.600.011 secure_kernel, for command 6
typedef struct cmd_6_args { // size is 0x80 bytes on FW 3.600.011
	SceSize size; // Size of this structure
	SceUInt32 maybe_self_type_or_success;
	SceUInt64 program_authority_id; // from program_identification_header
	SceUInt8 elf_digest[0x20]; // SHA256 of the ELF, from PSVita_elf_digest_header
	SceUInt64 program_sceversion; // from program_identification_header
	SceUInt64 padding_0x38; // ?
	SceSharedSecret shared_secret; // from PSVita_shared_secret_header
} cmd_6_args;
  If r1 or r2 is 0, it returns 0x800F0B16.
  It checks that r1 size (0x130) is in module region, same error.
  It checks that r2 size (0x80) is in module region, same error.
  If no revoke list has been initialized, it returns 0x800F0326.
  After that, it returns the result of the revoke checking function (same as used for SM normally).

Check if program-authority-id is forbidden by prog_rvk.

Check platform. This is ignored on Diag platform or if there is a platform check bypass attribute.

Check if Media Type is in range.

Check if Media Type is allowed in attribute.

Bypass media type checks if Media Type is 7 (host0:) and there is enough QA flags set.

Two product mode checks.

Attribute/mode checks that are not set in release build.

QA flag 0xD::1 checks for two attributes.

QA flag 0xF::1 check for one attribute.

Compares SharedSecret if capability bit 32 is not set.

Two attribute/self_level checks.

Kit activation check.

Check for attr::magicgate or qaf::magicgate in Tool/Test platforms.

If it is a non-game/fSELF program and SELF version is 0, or if SELF version is less than or equal to the value in keyring 0x50E, branch to the last check.

All successful except Media Types 13 and 14.

SELF version must be non-zero. If the self version is less than or equal to the value in keyring 0x50E, it is successful.

7: UnloadPanic

  This one trace-printfs.
  Then it sets cmep-state to 8.
  Then it writes 0x800F033B to the "0x40 buffer".
  Then corrupted insn?
  Then it zeroes the module region and soft-reboots.

8: IsDevelopmentMode

  Reads DIPSW from Bigmac keyslot 0x510 and returns true if the Development Mode flag is on.

Misc func doc

SuspendModule

Sends arm message 0x107.

Force-exits DMAC.

Then calls the CreateSuspendBuf function.

Regardless of result, it then jumps to ForceExitModule.

LoadModule

If either num_paddrs and paddr_list is NULL, return error 0x8016. Then it copies rsa pubkey + exponent to stack bufs.

if ((partition_id & 0x1l000) == 0)
    *0xE0050104 = 3;

...

Ends up calling SceDecrypt.

SceDecrypt

r1=out, ptr to {buffer = module end region - 0x800, size = 0x800}
r2=set_keys callback func
r3=keys, ptr to { void *aes_key, void *rsa_pubkey_and_exp, void *maybe_pubkey_type }
r4=0
sp0=sce_type (distinguishes rvk from self)
sp4=read_buffer callback func
sp8=paddr_list
spC=meta_off
sp10=u64* max_header_len
  • Error 0x800F0000 if bad magic 'SCE\0'.
  • Error 0x800F0000 if *(sce+4) != 3. (version)
  • Error 0x800F0000 if not *(u8*)(sce+8) & 0x40. (sdk_type)
  • Error 0x800F0624 if (*(u8*)(sce+8) & 0x80) && r4 > 0. (sdk_type)
  • Error 0x800F0624 if *(u16*)(sce+10) != sce_type. (header_type)
  • Error 0x800F0624 if *(sce+12) not aligned to 16. (metadata_offset)
  • Error 0x800F0624 if (meta_off >= 0) && *(sce+12) != meta_off. (metadata_offset)
  • Error 0x800F0624 if (*max_header_len < 0x8000000000000000) && *(u64 *)(sce+16) != *max_header_len) (header_len)
end_offset  = *(sce+12); (metadata_offset)
end_offset += 0x90;
  • Error 0x800F0624 on overflow or end_offset < out.size.
  • Error 0x800F0624 if *(u64 *)(sce+16) (header_size) not aligned to 16 or > 0xffffffff.
  • Error 0x800F0624 if *(u32 *)(sce+16) < end_offset.
  • Error 0x800F0624 if out.size < *(u32 *)(sce+16).
  • Error 0x800F0624 if *(u64 *)(sce+40) != 0

Next it decrypts the metadata info at metadata_offset+0x30 for size 0x40. This contains the keys to decrypt the encrypted metadata headers. header_size - (metadata_offset + 0x30 + 0x40) is decrypted with the key in metadata info.

if (*(u8 *)(sce + 8) < 0) return 0x800F0625; // sdk_type
struct {
 u64 signatureInputLength;
 u32 sigAlgorithm;
 u32 sectionCount;
 u32 keyCount;
 u32 optHeaderSize;
 u32 unknown06;
 u32 unknown07;
} *metadata_hdr = GetMetadataHeader(sce);
if (metadata_hdr == NULL) return 0x800F0616;
if (metadata_hdr->sigAlgorithm != exp_alg) return 0x800F0616;
if (InitPKA() != 0) return 0x800F0616;
if (metadata_hdr->signatureInputLength == 0) return 0x800F0624;
if ((metadata_hdr->signatureInputLength & 0xF) != 0) return 0x800F0624;
if ((metadata_hdr->optHeaderSize & 0xF) != 0) return 0x800F0624;
if (metadata_hdr->unknown06 || metadata_hdr->unknown07) return 0x800F0624;

Finally it loops through each decrypted metadata section header and checks the validity of the fields (key usage, compression, etc).

crypto::DoAes

Firmware Address
1.69 0x8048D6

This function performs AES operations.

  • arg1: always 1 (iv size?)
  • arg2: func_spec: 0 or 1
  • arg3: key_type: first 0, then 2 then 0. (could be keysize: 0 = 128, 2 = 256)
  • arg4: dst: destination buffer
  • arg5: src: source buffer
  • arg6: size: size of source/destination
  • arg7: pkey_or_pslot: pointer to key or slot to use
  • arg8: iv: pointer to initialization vector
  • arg9: out_iv, if NULL, iv is modified in-place

It always runs AES operation on bigmac channel 0.

dmac::Operation

Firmware Address
1.69 0x8061CE

This function does the crypto.

  • arg1: dst
  • arg2: src
  • arg3: size
  • arg4: pkey_or_pslot
  • arg5: iv
  • arg6: UNKNOWN
  • arg7: iv_size_words
  • arg8: key_size_words
  • arg9: pointer to dmac_setup_t structure

Returns 0x8000F016 if src/dst/padding is not aligned to 4.

It uses bigmac to perform crypto operation. It overwrites IV in-place.

struct dmac_setup_t {
  int channel; // dmac channel: 0 or 1
  int func;    // base function specifier
};

main()

Interrupts are disabled when this is called. First thing it writes 0x2000F to 0xE0020000. Probably sets up the device.

It checks the cmep-state, and if it's 1 (cold boot?), it writes:

  * Writes 0xFFFFFFFF to 0xE0000010.
  * Calls a big function that initializes 0xE0030000.
  * Sends ARM-message 0x101.
  * Sets cmep-state to 2.
  * Waits for ARM to send address for shared buffer and writes it to a state.
  * Writes 0xFFFFFFFF to the ARM mailbox.
  * If the big function above failed it:
    Sends msg 0x802F to ARM
    Calls a function that deinitializes 0xE0030000.
    Returns from main(), triggering cMeP memclear + hang.

Then it calls the following function regardless of state:

 if *E0020004 & 0xFFFFFFEF != 0: return 0x800F032F
 if *E0010004 != 0x80000005:     return 0x800F032F
 if *E0062020 & 0xFFFFFFF4 != 0: return 0x800F032F
 This function detects downgrades!
 if 0x1692000 != read_fw_version(): return 0x800F0337;
 return 1;

If this function fails, it does same thing as when the other fails.

Then it calls a function that initializes MeP IRQ controler. Then it calls a function that enables "software interrupt 3", dafuk is this.

Then it sets cmep-state to 3. Then it sends msg 0x102 to ARM.

Then it calls a function to save the kernel context to an address. This context is later loaded when processing SWI and IRQs.

Enable interrupts.

After that comes the main sleep-loop.

When main-loop exits (when state is 9) it sends ARM message 0x106. It also does the common error-path code described above.

Main always returns 0 but it's ignored so who cares.


CheckAddrRange(addr, size, dbg_code, check_funcptr)

Firmware Address
1.69 0x801E36

On integer overflow of (addr+size-1) it panics with dbg_code. Returns 1 on success, 0 on failure.

This has a fatal bug, because it calls the check looks like this:

 if (check_funcptr(addr) || check_funcptr(addr+size-1))

Where in fact it should be:

 if (check_funcptr(addr) && check_funcptr(addr+size-1))

Range checks

  ArmTz:   if (x >= 0x40000000 || x < 0x40300000) return 1;
           return 0;

  ArmUser: cut = *0xE0062260;
           if (addr < 0x40300000) return 0;
           end = 0x40000000 + cut;
           if (addr < cut) return 1; // Overflow: why allow it?
           if (addr < end) return 1;
           return 0;

  Kernel:
           if (addr < 0x00800000) return 0;
           return addr < 0x0080A000;
  Module:
           if (addr < 0x0080A000) return 0;
           return addr < (0x0080A000+0x00016000)
  Cmep:
           if (addr < 0x00800000) return 0;
           return addr < 0x00820000

swi_handler

Firmware Address
3.60 0x800e28

self_header_auth

Firmware Address
3.60 0x803586

self_rvk_check

Firmware Address
3.60 0x80594e

Changelog

Diff FW 1.692 vs. FW 1.05

Stack cookies were not enabled.

Debug vector was initially a "reti" instruction, was later replaced with infloop.

sm::Suspend didn't wait for ongoing dmac operations to finish.
They fixed this bug.

sm::Load:
  An additional check added:
      ret = sub_803E8C(r6);
      if ret != 0:
          return ret;

  Now clears the 0x800 buffer using dmac-memset on some error cases.

dbg::Panic:
  Used a static buf in .bss instead of stack (for the snprintf()).
  Did not disable interrupts.

keyslots::Init
  Changed a lot. todo: Investigate later.

suspend::Aes128CbcEncDec:
  Code to "panic with 00000020 if shared_buf == NULL" was not there.

Cmd 0xF01 did not exist.

All cMeP commands that read from the shared region uses a buf in .bss instead of stack.
  This allowed a race condition where you could request a suspend using command 0x401.
  Then just before the sm calls syscall 3 you send another cMeP command to overwrite the sharedbuf.
  This will call sm::Suspend on a buffer that's not properly checked.

A range-check on irq-id was missing from irq::SetIrqFuncTableAndEnableIrq.
  Harmless?

dbg::EmitStr:
  Code was added to prevent it for ending up in an infinite recursion if stack cookie gets corrupted.

The cmep_cmd::AllowedCmdInStateTable was unchanged.

Diff FW 1.692 vs. FW 1.60

.bss size grew from 0x478 bytes to 0x480.

A small function was added that clears bit 1 and 10 in $cfg.
Rev changed from 5209->5679

cmep_cmd::Parse changed:
  Stackframe grew 0x20 bytes.
  The function that that changed appears to be 0x501.

GetFirmwareVersion returns 0x16920000 instead of 0x16000000 (duh!).

keyslots::Init changed:
  Some bitmask in was changed from 0x3FFFDF to 0xFFFFDF.
  Branch was removed here that used to set Bigmac keyslot 0x516 to all zeroes.
  The function was also extended a lot.

  Idea: Does this possibly protect additional keyslots stuff?
  However otp::Teardown looks identical.

sm::Load changed:
  Stackframe increased from 0x1E0 to 0x220.
  However function didn't change at all.
  So they just increased size of a buffer by 0x40.

A function called by a function called by sm::Load changed:
  Stackframe increased from 0x70->0x78.
  This adds a new call to a function at the bottom.

sub_80468E->sub_8046E4 called indirectly by sm::Load changed:
  This one now writes 0x80 instead of 0x40 to a local variable.
  This is probably for the expanded buffer in sm::Load.
  Also been extended with some calls to memcpy and memset.

New function added sub_804B7A, called by rvk::CheckSm.
This is a timing-safe memcmp with hardcoded len=16.

rvk::CheckSm changed:
  Stackframe increased from 0x30 to 0x38.

Two new functions were added that are called by rvk::CheckSm:
  sub_805062
  sub_80508E

sub_805BEA -> sub_805DB2:
  Stackframe reduced 0x18->0x10.
  The function before called same function 3 times in a row, now only 2.

sub_805C4E -> sub_805DB2:
  Call to unknown function was removed. r3 to this function was 0x111.

sub_805DF8 added.
sub_805C4E removed.

Many small functions removed:
  sub_805EC4
  sub_805ECA
  sub_805EE4
  sub_805EFE
  ...

sub_80631C removed.
sub_806EFE removed.
sub_9068D2 removed.
sub_8069E2 removed.
sub_806A16 removed.

Then some code to talk to 0xE004???? was removed.

Diff of various FWs

Base is previous FW version.

FW version What changed compared to previous FW version
3.36 unknown
3.50 Stack base changed. (see func, 3.36-3.50:0x80030a)

Added new functions. (see func, 3.50:[0x805310, 0x80541e, 0x80542a, 0x80543a, 0x805448, 0x805456, 0x805466, 0x80548c])

Function changed. (see func, 3.36:0x80560e/3.50:0x80594e)

Function changed. (see func, 3.36:0x807012?/3.50:0x806ef6)

Some strings are aligned by 4.