From Vita Development Wiki
Revision as of 19:23, 17 January 2019 by Yifan Lu (talk | contribs) (Secret debug mode)
Jump to navigation Jump to search
Offset Size Description
0x0 0x4 0x64B2C8E5 magic
0x4 0x4 Offset to code
0x8 0x4 Size of plaintext version string, 0 on 0.931, 0x10 on other
0xC 0x4 Size of unknown block, only seen as 0
0x10 0x4 Code size
0x14 0x2 AES key revision, possible values 0 to 5
0x16 0x2 Public key revision, possible values 0 to 15
0x18 0x8 Unknown/zero
0x20 0x20 sha256 hash of decrypted body
0x40 0x10 Version in ASCII, not present on 0.931
0x50 (0x40 on 0.931) 0x90 Zero
0xE0 (0xD0 on 0.931) Until Data Encrypted Header

Encrypted Header

At offset 0xE0 there is a 0x1E0 sized buffer that is speculated to be an encrypted header. For any given firmware version, secure_kernel.enc/second_loader.enc and secure_kernel.enp/second_loader.enp share the first 0xC0 bytes. For non-retail PUPs, each SLSK share the first 0xC0 bytes as observed in 0.931, 0.995, 1.000.41. Similarly in retail PUPs, each SLSK also share the first 0xC0 bytes as observed in 1.05 and 3.60. However, the bytes differ from retail and non-retail SLSK.

There is likely a 0xC0 sized "common" header that is shared by every firmware and by both secure_kernel and second_loader but different between retail and non-retail builds. Then there is likely a 0x20 byte section that is unique per SLSK (maybe contains version, size, load offset, etc). Then a 0x100 byte RSA-2048 signature of the header.


The last 0x340 bytes of each SLSK is not personalized. For both secure_kernel and second_loader, both the enc and enp variants share the last 0x340 bytes (although they differ from each other and across firmwares). This is likely the signature and might also contain certificates.

Reading through brom code, it appears last 0x340 bytes are not used in any way.

Bootrom enc loading process

Secret debug mode

Before the ENC is loaded, there is a check for some secret mode. Note these two ports are used in regular syscon SPI-like communications. However, usually these two pins are used as part of the SPI-like protocol for signaling. But the bootrom does not use the SPI registers at all. It uses some registers that is never seen outside of bootrom. Even though it is logically separate from the SPI ports, it could be physically connected to the same pins although this is unconfirmed. Note that when the secret handshake passes and we are in secret mode, the MBR is read from the gamecard instead (with gamecard auth not enabled, so a regular SD card would work). Additionally, the personalization removal is done using keyslot 0x207 instead of 0x206 (see below) although it is not currently known if 0x207 is console-unique. All the signature checks and HMAC is still performed, so this secret mode cannot be used for running unsigned code. However, Glitching would still work when in the secret debug mode.

int is_debug_mode(void) {
    int res = 0;
    gpio_set_port_mode(0, 3, GPIO_MODE_OUT);
    if (gpio_port_read(0, 4)) {
        // this sets a bit in some f00d-only hardware
        // note this same reg is used to enable f00d reset from arm
        *(uint32_t *)0xE0020000 |= 0x10;
        // theory: mux on syscon SPI ports to connect to f00d directly

        // compute a challenge using true random numbers
        uint32_t challenge[4];
        challenge[0] = trng_read32();
        challenge[1] = trng_read32();
        challenge[2] = challenge[0];
        challenge[3] = challenge[1];

        // send challenge
        *(uint32_t *)0xE0000020 = challenge[0];
        *(uint32_t *)0xE0000024 = challenge[1];
        gpio_port_set(0, 3);

        // poll
        while (!gpio_port_read(0, 4));

        // get response
        uint32_t response[2];
        response[0] = *(uint32_t *)0xE0000028;
        response[1] = *(uint32_t *)0xE000002C;

        // clear regs
        *(uint32_t *)0xE0000028 = -1;
        *(uint32_t *)0xE000002C = -1;
        *(uint32_t *)0xE0000060 = -1; // maybe cached of 0xE0000020?
        *(uint32_t *)0xE0000064 = -1; // maybe cached of 0xE0000024?

        // end handshake
        gpio_port_clear(0, 3);

        // compute expected result
        uint32_t expected[4];
        if (bigmac_aes256_ecb_encrypt(expected, challenge, sizeof(challenge), g_debug_challenge_key) < 0) {
            goto clear_mem;

        // check result
        if (memcmp_timingsafe(expected, response, 8) == 0) {
            res = 1;
        memset(g_debug_challenge_key, 0, sizeof(g_debug_challenge_key));
        memset(challenge, 0, sizeof(challenge));
        memset(response, 0, sizeof(response));
        memset(expected, 0, sizeof(expected));
    } else {
        memset(g_debug_challenge_key, 0, sizeof(g_debug_challenge_key));
    return res;

Remove personalization

First, personalization layer is removed. It uses AES-128-CBC with a derived key and decrypts data at ENC+0xE0 (or ENC+0xD0 if there's no plaintext version) for size of code_size+0x1E0.

There are two possible paths to derive the key used to remove personalization. Normally, the key is derived using keyslot 0x206. There's however an alternative path, triggered in secret debug mode, when instead the keyslot 0x207 is used with a different seed.

Once personalization is removed, the source keys are locked down. Keyslots 0x9, 0x206, 0x207 are locked down completely (leaving only 0xA0 protection). However, keyslot 0x8 allows encryption, this lets update manager SM add personalization layer during update without having to derive the keys itself.

Header RSA check

A key is derived from keyslot 0x344 and put into keyslot 0x20. This key is then immediately used to calculate HMAC-SHA256 over enc header, excluding the RSA sig (typically 0x00 to 0x1C0).

2 bytes are read from keyring slot 0x603 is read. This is the bitmask of allowed RSA public keys (0xFFFF on 1.692). If the mask is zero, a hardcoded RSA modulus is used. Otherwise, it checks enc rsa revision against the mask and if it's allowed, it gets the modulus from keyring RSA storage starting at keyslot 0x700.

The signature is typically located at 0x1C0 and is 0x100 bytes. After calculating powmod, it checks the padding and compares previously calculated HMAC-SHA256 against the contents.

Finally, it protects keyslots 0x700 to 0x77F to disable f00d reading out the modulus. However, only reading is protected, it should still be possible to encrypt/decrypt using these keyslots.

Metadata decryption and code verification

Using keyslot 0x208+aes_key_revision and metadata buffer (0xE0 offset for 0x20 bytes) the code decryption key is derived and put into keyslot 10. Then, 5 more keys are derived in the same way, using seed data [0x100; 0x19F]. These 5 keys are put into keyslots 11, 12, 13, 14, 15.

Keyslots 0x208, 0x209, 0x20A, 0x20B, 0x20C, 0x20D (all possible AES key revision keys) are protected.

Data at [0x1A0; 0x1C0) is decrypted using keyslot 10. This is HMAC-SHA256 of the code segment. HMAC-SHA256 is calculated over the code segment using keyslot 0x20, then keyslot 0x20 is protected. Finally, the calculated hmac is compared to the decrypted one.

Protecting the keys

Some keys are protected, depending on bit flags buffer located right after plaintext version string (so, at offset 0x50). However, on the latest 3.68 enc it is all zeroes so no keys should be protected by this function (?)

Decrypting code

Code is decrypted using key 10 and a hard coded IV. Then, the key is protected.


The remainder (0x1C000 - code_sz) after the decrypted code is cleared with dmac. Dmac regs are also cleared.