Line 3: |
Line 3: |
| [[Image:boot-stage1-error.jpeg|frame|When the Stage 1 bootloader (in ROM) fails, it displays a 32-bit hexadecimal number on the top screen.]] | | [[Image:boot-stage1-error.jpeg|frame|When the Stage 1 bootloader (in ROM) fails, it displays a 32-bit hexadecimal number on the top screen.]] |
| | | |
− | The first stage of the DSi's bootloader lives in ROM, presumably on the CPU die. It loads further encrypted (and probably signed) stages from NAND flash, starting with a (partially unencrypted) offset table in the sector at 0x200. | + | The first stage of the DSi's bootloader lives in ROM, presumably on the CPU die. It loads further encrypted+signed stages from [[NAND]] flash, starting with a plaintext offset table in the sector at offset 0x200. |
| | | |
| Not much is known about this bootloader yet, but it presumably knows how to: | | Not much is known about this bootloader yet, but it presumably knows how to: |
Line 35: |
Line 35: |
| Unlike the stage1 bootloader, which must be small enough to fit in ROM (probably several kilobytes), the stage2 bootloader has about a megabyte of NAND flash reserved for it. The stage2 bootloader understands partitions and filesystems, and it is capable of loading the DSi menu. It also must understand the encryption used on filesystem blocks in the NAND, and it must understand how to load and validate title metadata. | | Unlike the stage1 bootloader, which must be small enough to fit in ROM (probably several kilobytes), the stage2 bootloader has about a megabyte of NAND flash reserved for it. The stage2 bootloader understands partitions and filesystems, and it is capable of loading the DSi menu. It also must understand the encryption used on filesystem blocks in the NAND, and it must understand how to load and validate title metadata. |
| | | |
− | The Stage 2 loader was not modified by the [[System Menu 1.4]] update. This is still earlier in the boot process than the "Health and Safety" warning. | + | The Stage 2 loader was not modified by the [[System Menu 1.4]] update. This is still earlier in the boot process than the "Health and Safety" warning(that warning is displayed by the sysmenu). |
| | | |
− | The first stage bootloader reads sector 0x200 in order to find a table of offsets to the Stage 2 bootloader: | + | The first stage bootloader reads the sector at offset 0x200 in order to find a table of offsets to the Stage 2 bootloader: |
| | | |
| 00000220 00 08 00 00 10 64 02 00 00 80 7b 03 00 66 02 00 |.....d....{..f..| | | 00000220 00 08 00 00 10 64 02 00 00 80 7b 03 00 66 02 00 |.....d....{..f..| |
Line 43: |
Line 43: |
| 00000240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| | | 00000240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| |
| | | |
− | This appears to be describing two chunks of the stage2 loader, one 0x26410 bytes in length at address 0x800, and one 0x27588 bytes at address 0x26e00. | + | This is describing two chunks of the stage2 loader: the ARM9-binary 0x26410 bytes in length at address 0x800, and the ARM7-binary 0x27588 bytes at address 0x26e00. |
| + | |
| + | Structure of this header: |
| + | {| border="1" cellpadding="3" cellspacing="0" |
| + | ! Offset |
| + | ! Size |
| + | ! Description |
| + | |- |
| + | | 0x0 |
| + | | 0x20 |
| + | | Reserved |
| + | |- |
| + | | 0x20 |
| + | | 0x10*2 |
| + | | Two binary-block headers: first one is for the ARM9 binary, second one for the ARM7 binary. |
| + | |- |
| + | | 0x40 |
| + | | 0xBF |
| + | | Reserved |
| + | |- |
| + | | 0xFF |
| + | | 0x1 |
| + | | Unknown, value 0xFF. |
| + | |- |
| + | | 0x100 |
| + | | 0x80 |
| + | | RSA-1024 signature |
| + | |- |
| + | | 0x180 |
| + | | 0x80 |
| + | | Unknown |
| + | |} |
| + | |
| + | Structure of the binary-block headers: |
| + | {| border="1" cellpadding="3" cellspacing="0" |
| + | ! Offset |
| + | ! Size |
| + | ! Description |
| + | |- |
| + | | 0x0 |
| + | | 0x4 |
| + | | Offset for this binary in NAND. |
| + | |- |
| + | | 0x4 |
| + | | 0x4 |
| + | | Actual binary size. |
| + | |- |
| + | | 0x8 |
| + | | 0x4 |
| + | | Binary load address in memory. This is also the binary entrypoint. |
| + | |- |
| + | | 0xC |
| + | | 0x4 |
| + | | Binary size aligned to 0x200-bytes. |
| + | |} |
| + | |
| + | Structure of the 0x74-byte "hash-data" stored in the RSA message: |
| + | {| border="1" cellpadding="3" cellspacing="0" |
| + | ! Offset |
| + | ! Size |
| + | ! Description |
| + | |- |
| + | | 0x0 |
| + | | 0x10 |
| + | | [[AES_Engine]] keyY used for the ARM9/ARM7 binaries crypto. |
| + | |- |
| + | | 0x10 |
| + | | 0x14 |
| + | | SHA1 hash. Going by 3DS TWL_FIRM this seems to calculated over the first 0x28-bytes of [[NAND]], then the first 0x100-bytes of the header, then the last 0x80-bytes of the header(following the signature). However, attempting to calculate the hash this way doesn't result in the right hash(even with the bootloader image contained in 3DS TWL_FIRM). It's unknown how the first part is handled on DSi. |
| + | |- |
| + | | 0x24 |
| + | | 0x14 |
| + | | SHA1 hash over the plaintext ARM9 binary, with the actual binary size. |
| + | |- |
| + | | 0x38 |
| + | | 0x14 |
| + | | SHA1 hash over the plaintext ARM7 binary, with the actual binary size. |
| + | |- |
| + | | 0x4C |
| + | | 0x14 |
| + | | Unknown, not used by 3DS TWL_FIRM. |
| + | |- |
| + | | 0x60 |
| + | | 0x14 |
| + | | Unknown, not used by 3DS TWL_FIRM. |
| + | |} |
| + | |
| + | The 3DS TWL_FIRM verifies TWL RSA padding with the following, which is also valid for this DSi bootloader padding: |
| + | * The first byte must be 0x0. |
| + | * The second byte must be 0x1 or 0x2. |
| + | * Executes a while(<value of byte at current pos in RSA message>). When the second_byte in the message is 0x1, the byte at curpos must be 0xFF(otherwise the non-zero value of the byte at curpos doesn't matter). This loop must find a zero byte before offset 0x7F in the message otherwise an error is returned. |
| + | * Returns an address for msg_curpos+1. |
| + | With the code in 3DS TWL_FIRM, the actual "totalhashdatasize" in the RSA message must be <=0x74. The 3DS TWL_FIRM code copies the RSA "hashdata" to the output buffer, using the actual size of the RSA "hashdata". |
| | | |
| Note that this sector (and two similar ones at 0x400 and 0x600) appear to be the only unencrypted blocks on the NAND flash. | | Note that this sector (and two similar ones at 0x400 and 0x600) appear to be the only unencrypted blocks on the NAND flash. |
| | | |
− | It is unclear why there are two pieces which are nearly but not quite the same size. Passive traces of the boot sequence confirm that the 0x26e00 chunk is slightly larger, and it's loaded first. The 0x800 chunk is read immediately after the 0x26e00 chunk.
| + | After loading+verifying the the above header, the ARM7 binary is loaded+verified, then the ARM9 binary is loaded+verified. |
| + | |
| + | Whereas the filesystem data in NAND is encrypted using a unique key for every DSi, the stage2 bootloader is identical on every DSi tested so far. The stage2 bootloader binaries are not encrypted with any console-unique keys. |
| + | |
| + | Stage1 uses the [[AES_Engine]] to decrypt each ARM9/ARM7 binary, where keyY is from the above signature. The [[AES_Engine]] keyslot used here is the same one used for the shared areas for [[Tad]], therefore the keyX is the same as the one used for that. The following is used for the CTR, where "binblk->binblocksize" is the actual binary size: |
| | | |
− | Whereas the filesystem data in NAND is encrypted using a unique key for every DSi, the stage2 bootloader is identical on every DSi tested so far. This probably means that it is encrypted using a fixed key included in stage1.
| + | unsigned int ctr[4]; |
| + | memset(ctr, 0, 16); |
| + | |
| + | ctr[0] = binblk->binblocksize; |
| + | ctr[1] = (unsigned int)(-binblksize); |
| + | ctr[2] = ~binblk->binblocksize; |
| | | |
| + | === Stage2 operations === |
| After Stage 2 is loaded: | | After Stage 2 is loaded: |
| # The NAND flash is partially re-initialized | | # The NAND flash is partially re-initialized |