Share This Page

This thread is for an in-depth technical review of the ARM9Loader hack.

These first posts will be edited to reflect updated information from community members. Credit will be given for corrections in the changelog, unless they were given to me via PM under condition of anonymity.

Introduction
The description is initially based on the presentation by plutoo at 32C3, on December 27, 2015, in combination with information from the 3DS HomeBrew site, http://3dbrew.org/wiki/3DS_System_Flaws, some of my own ideas, and lots of my own ignorance thrown in. (In other words, errors are likely!)

The end goal is for a deep understanding of how and why the security broke down. Interestingly, if Nintendo had fixed some well-documented low-impact security issues (e.g., not clearing RAM on reboot, March 2014; etc.), then ARM9Loader might not have been practically exploitable.

BOOTROM
The jobs of the BOOTROM include to set some super-secret encryption keys, load firmware into memory, validate it's cryptographic signature, and BRANCH (JMP) to the firmware's entry point.

The 3DS includes both a primary firmware (FIRM0) and a second firmware (FIRM1). The second firmware (FIRM1) is used when the first firmware (FIRM0) fails the cryptographic signature check for any reason. This makes sense, as it provides a fail-safe for situations that cannot be entirely eliminated (e.g., power-loss during update FIRM0, NAND sector corruption, etc.).

Conceptually, the BootRom does the following:

Lots of top-secret stuff first

Load FIRM0 firmware from NAND (firmware partition) into memory

Check the cryptographic signature of the loaded FIRM0 in memory

If the cryptographic signature is verified, jump to FIRM0's entrypoint

Else....

Load FIRM1 firmware from NAND into memory

Check the cryptographic signature of the loaded FIRM1 in memory

If the cryptographic signature is verified, jump to FIRM1's entrypoint

Else... PANIC (no towel)

Minor BootROM flaw
If the cryptographic signature of a firmware fails validation, the bootrom does not clear the memory that was written to when loading that (invalid) firmware. This allows FIRM0 to be used to load attacker-controlled contents ... up to the size of the largest Nintendo-signed firmware (because header must be signed before loading that size firmware).

So, if FIRM1 is smaller in size than FIRM0, then the attacker can still control the contents of at least some memory during FIRM1's loading.

ARM9Loader Theorya.k.a. How it is supposed to work

ARM9Loader is an additional layer of security which was added only to the N3DS. The ARM9Loader is part of the firmware binary, and thus will only execute if the firmware itself was properly signed. The purposes of the ARM9Loader include using additional console-unique data to:

further decrypt the KERNEL9 binary (using OTP & sector 150)

perform additional key initialization prior to handing control to Process9

There are three versions of Arm9Loader (as of firmware 11.0.0-33), named v1, v1.1, and v2. Each is described in turn...

ARM9Loader v1 exploit

Arm9Loader version 1 is found on N3DS firmware versions < 9.5.9-X.

Version 1 uses a portion of sector 150 as an encrypted key (Key #1). This is only mentioned as contrast with version 2, because neither Kernel9 nor ARM9Loader v1 clear keyslot 0x11.

Because the ARM9Loader and Kernel9 fail to clear keyslot 0x11, all of keyslot 0x11's dependent keys can be regenerated as soon as ARM9 execution is attained. As you can deduce, once a keyslot is in use by software to store data, the keyslot's initialization vectors (e.g., KeyX, KeyY) cannot easily be changed without risk of breaking software that relied upon that keyslot.

Result
Attackers gained access to keys that depend on keyslot 0x11, for firmware 9.5.0-X and lower.

Limitations:
Neither the OTP nor the SHA256 hash of the OTP was exposed. Therefore, a v.Next firmware could simply generate a different initialization value for a (currently unused) keyslot, and depend on that new value. This is exactly what occurred, in fact, for 9.6.0-X, which used another keys from the special NAND sector, still decrypted by the hash of the OTP. At first glance, one might think that sector 150 would provide the basis for many keys.

ARMLoader v1.1
v1.1 comes only with N3DS firmware version 9.5.9-X. The only fix is that it clears keyslot 0x11.

Last edited
by Selver,
Sep 19, 2016
- Reason: First line to match contents, for featured posts summary

Version 2 is found on N3DS firmware versions 9.6.0-X through at least 11.0.0-X (most current at time of posting).

As noted above, the bootrom won't branch to the firmware entry point unless the firmware was properly cryptographically signed by Nintendo. Arm9Loader is part of the firmware, but the remainder of the Arm9 firmware is encrypted. Arm9Loader reads an additional key from the NAND sector 150 to decrypt the encrypted Arm9 firmware.

With version 2, Arm9Loader has a bug where it does not perform the validation of the resulting decrypted key against a known test vector. As a result, if sector 150 of the NAND contains invalid data, even with 100% valid signed FIRM partition contents, Arm9Loader will decrypting the firmware into garbage due to the incorrect key and (even though the decrypted data is cryptographically random) still branch to the entrypoint address.

Presumption: The FIRM1/FIRM0 loading is part of the bootrom, and thus cannot be updated without a hardware update.

This results in what is technically called "Cryptographically Random Data" being executed, instead of the actual firmware. Now, by itself, this wouldn't seem to be useful, because the probability that the decrypted instructions would result in an exploit are 1/(an astronomically large number).

However, the 3DS has two tiny weaknesses, which greatly magnify the above bug.
#1. The BOOTROM does not clear the memory used by FIRM0 when it fails to be validated
#2. The RAM is not cleared between reboots (known for over a year)

How does #1 help an attacker?
Consider having FIRM0 containing valid header data, but otherwise filled with NOP sleds and a payload at the end of what will be loaded. The attacker can select the precise instructions in the payload, as they are loaded directly by the BOOTROM. Of course, this is normally not useful, as the digital signature will then be invalid, and thus the firmware won't execute.

Now add in a totally valid FIRM1, but one which is smaller in size than FIRM0. FIRM1 will also load into memory, but all the instructions from FIRM0 that were beyond FIRM1's valid size will remain in memory (they were loaded as part of the failed digital signature validation for FIRM0). Again, this also would normally not be useful, because FIRM1 (being a digitally signed file) will contain official firmware....

However, because Arm9Loader version 2 fails to verify the key (from sector 150) that it uses to decrypt the firmware is valid, that allows the key to be modified by an attacker. The key from sector 150 is encrypted using the SHA256 hash of the console-unique OTP. The attacker will be unable to predict the decrypted key, because the attacker does not know either the OTP nor the SHA256 hash of the OTP. Even so, the attacker can force a random 0x11 key (and relying subkeys) to be generated. This will result in the wrapped firmware being decrypted to cryptographically random data, and then (!) ARM9Loader will BRANCH right into the middle of that cryptographically random data.

Ok, but that still doesn't seem really useful, right? I mean, branching into cryptographically random instructions is useless, right? Especially when it requires a unique brute-forced key to be determined for each console, right?

... to be continued ...

ARM9Loader v2 -- 2/3

[Note that this section was written very early January 2015 ... before downgrading to obtain the OTP was possible ... and thus considers if brute-force per-console attacks were practical]

As noted above, the size of FIRM0 >> the size of FIRM1, which caused the BOOTROM to load attacker-selected instructions into memory. That's the target space for a randomly-decrypted instruction stream to BRANCH into. Alternatively, with a HARDMOD, the attacker could modify the special sector of NAND at or near the time that they write all memory, presumptively with attack code at the end of memory, plus either NOP Sleds or BRANCH sleds to increase the chance a random BRANCH instruction will get to the attack code. Because memory is not initialized to zero on reboot, this memory remains as set by the attacker. (On failure, attacker could modify the special NAND sector and reboot, or try all steps again.)

The probability of an exception (invalid memory dereference, invalid instruction) for any randomly-generated instruction is relatively close to 1/8. Thus, each boot will execute (on average) eight random instructions before throwing an exception. (Sum from N=1 to infinity of 7/(8**N) == 8, per Wolfram Alpha).

The probability of one of those eight random instructions being a BRANCH instruction is about one in eight (bits 27-25 == 101b), of which ~1/2 will have the right CONDition bits set, presuming randomness. Thus, a rough estimate is a 50% chance that a BRANCH instruction will execute for any random decryption key.

Will that BRANCH instruction point to valid memory? Surprisingly, the answer is, quite likely yes. The branch instruction is +/- 32MB (24 bits). Can an attacker force FIRM0 to be at least 32MB greater in size than FIRM1? Presumably yes, as the digital signature need not be valid for FIRM0. This is actually a limiting factor, as the header must have a valid digital signature, which limits available FIRM sizes.

So, there is approximately a 50% chance (per boot with random decryption key) of a valid BRANCH instruction being executed. Some portion will jump to another location that's filled with cryptographically random instructions (within FIRM1's size). How big is the firmware image in memory? 50% * (1 - (sizeof firmware)/256MB) == chance of hitting memory left over from prior boot, and running the exploit code.

Wow... this is actually much more serious than I would have initially thought.
With a hardmod, it appears this would only take about eight writes (16 if restoring the NAND sector and resetting memory with BRANCH sled and attack code each time), before a value is found that will reliably jump to exploit code. Once found, that value can reliably be re-used on that N3DS.

Update #2: Actually, what is important is not the size of FIRM0, but how much larger it is than FIRM1. This is because most of FIRM0 is overwritten by FIRM1, leaving a much more narrow target for the JMP. However, that JMP can be to any location, because the very next instruction left over from FIRM0 can, itself, be a JMP instruction. Rather academic at this point, but...

You seem very certain, i hope your right, it's been a long time coming

Click to expand...

I am because I saw it, I mean at least I think I did. There was a YouTube video showing off the capabilities of ntrhax from the SALT team or whatever they wish to call themselves and they had emunand on the latest version on a New 3DS. Now if a amateur group like them could figure out how to achieve it Im sure that the veterans like GW will surely able to do exactly the same, maybe even a community member who knows but its comming all right Thats what she said.

I am because I saw it, I mean at least I think I did. There was a YouTube video showing off the capabilities of ntrhax from the SALT team or whatever they wish to call themselves and they had emunand on the latest version on a New 3DS. Now if a amateur group like them could figure out how to achieve it Im sure that the veterans like GW will surely able to do exactly the same, maybe even a community member who knows but its comming all right Thats what she said.

Thanks for posting this. Quite a lot of details related. @AlbertoSONIC@motezazer@173210@Steveice10@TuxSH@d0k3
Sorry for bothering all of you. However I have some obstacles about it to share.
1,This deserves a HARD-MOD. Since only with Hard-mod can you overwrite the raw NAND sections, doing the modification. - At least currently
2.Decrytion attempts, and yes OTP. I don't know how they dumped the OTP for N3DS. Also the exploit <=2.2 is for O3DS. You must control what it produces!
3.Raw NAND access. Your code would be loaded before FIRM (if you firmlaunch it) so no fread9/fwrite9/... Even dumping something needs different APIs then.
4.Proper keys. You know you need it decrypted correctly to do a firmlaunch, or yes, an older decrypted already firm.And, the actual result for an enhanced arm9loader hax is CFW on 9.3+ SysNAND. Not the EmuNAND directly, orz.

— Posts automatically merged - Please don't double post! —

Well i have something to append.
1.FIRM section is 0xEF000 size, and is in .firm format, so not a bootrom, sorry for that. For my O3DS no size difference between FIRM0 and FIRM1.
And the max ever possible space for it is 4MB.
2.You can not run code on NAND due to its complexity of altering data on it. So those must be loaded to memory first.
3.However the slides may have a side-effect on it. There is another exploit, that after HARD-RESET, memory isn't cleared.
So you might set it up before the calling, temporarily. I doubt if that is from ntrcardhax.
Well but on the video it is cold booted. So you can leave this alone now.. Orz.

The probability of an exception (invalid memory dereference, invalid instruction) for any randomly-generated instruction is relatively close to 1/8. Thus, each boot will execute (on average) eight random instructions before throwing an exception. (Sum from N=1 to infinity of 7/(8**N) == 8, per Wolfram Alpha).

Click to expand...

The real exception vector is located in bootrom and to jump the another vector at the top of ARM9 RAM.
If arm9loader doesn't initialize the vector in RAM, we can use it to execute code.

So, there is approximately a 50% chance (per boot with random decryption key) of a valid BRANCH instruction being executed. Some portion will jump to another location that's filled with cryptographically random instructions (within FIRM1's size). How big is the firmware image in memory? 50% * (1 - (sizeof firmware)/256MB) == chance of hitting memory left over from prior boot, and running the exploit code.

Click to expand...

The loaded FIRM will be copied in ARM9 internal memory, so you should assume the space is 1MB instead of 256MB.
The size of the ARM9 partition in 9.5 NATIVE_FIRM is 566784 bytes (553.5KB).
ITCM is close to ARM9 memory, but I don't take it into account because it's used by bootrom.
So, 50 * (1 - 566784 / 1048576) = 23 %
Hmm, not so bad.

If you take exception into account, the calculation will be more complex. I won't do that because I'm not good at math.

3.Raw NAND access. Your code would be loaded before FIRM (if you firmlaunch it) so no fread9/fwrite9/... Even dumping something needs different APIs then.
2.You can not run code on NAND due to its complexity of altering data on it. So those must be loaded to memory first.

Click to expand...

Reading/writing NAND is quite a easy because NAND is initialized to read FIRM, but you don't have filesystem.

4.Proper keys. You know you need it decrypted correctly to do a firmlaunch, or yes, an older decrypted already firm.And, the actual result for an enhanced arm9loader hax is CFW on 9.3+ SysNAND. Not the EmuNAND directly, orz.

Click to expand...

You can load anything from NAND.

— Posts automatically merged - Please don't double post! —

Wait, if you can control the exception vector, any code except calling itself or resetting ARM9 should be OK. The probability is very high.