Full Report
The Nintendo Entertainment System (NES) was built in an era of CRT TVs, where rendering it entirely different than LEDs. Most graphical changes happen during a blanking period; so, there is an interrupt to ensure this is the case. The VBlank interrupt is a Non-Maskable-Interrupt (NMI). The game console also has Interrupt Requests or ICQs for short. Depending on the game mode that the gameplay is in, the IRQ will behave differently. Additionally, the NES had logical blocks of code and assets in banks, where only one bank can be loaded at a time. The NMIs swap out the PRG bank during graphics changes. Eventually, by the end of the NMI, the proper banks are swapped back in. What if we could trick code to run with the improper banks loaded? This is how the vulnerability that was found works! DPCM audio samples have the ability to corrupt controller inputs. This is because a register is shifted one too many times. Since the DMA read is asynchronous and this is a hardware issue, we must find a way to workaround this. To fix this issue, the most common fix was to simply poll the controller over and over again until the same buttons were seen twice in a row. So, what's the bug? By changing buttons at a rate of 8K inputs per frame, we can trick the polling code for controller inputs to be stuck forever! This paired with an interrupt leads to a situation where code from a bank never intended to be executed in this context will be ran! By some miracle, the code runs fine. Eventually, a RTS instruction will jump the code to 0x0000 on the stack. The NMI continues to happen every frame - it records button press inputs to $17,$18,$F5,$F6 and $F8. Through careful planning, the controller inputs can be used to write somewhat arbitrary asm to execute. $17 is the total held buttons on controller 1 and $18 is the new buttons pressed using a bit for each button. $F5,$F6 and $F8 have similar limitations to $17/$18. This creates a limitation of which bytes can be used for the second byte. Additionally, left and right as well as up and down cannot be held at the same time, further limiting the instructions. With these limitations in mind, our goal is to warp to the end credits. There are 6 criteria that need to be met with 3 of them already there once we start relating to banks. The first is the stack be larger than 0x30, second is NMI mode at address $100 must be 0x20 and we need to jump to $B85A. Previous versions of the TAS had to work around the limitations above. However, the author found a special case - bytes 0x0-0x2 uses these for scratch addresses at the end of an NMI. They happen to be for controller inputs INCLUDING the conflicting inputs. By using this property, we have more control over these two bytes, which happens to be enough :) The TAS is 3 frames long of game play. Here is what happens: Write JSR $9000 at the scratch address using two controllers. Using the only inputs PUSH a value of 0xFA to register SP. The next NMI occurs and writes our controller inputs to the stack. This time, our inputs result in JSR $0000 being executed. JSR $9000 is executes from the previous write after our jump occurs. Since the SP is sane this works. The video for this explains a slightly simplified version, which is what the example is based on. However, the concepts are the same. A funny change they made was using a different version of the game because of the addresses are slightly different. Overall, the article and video are amazing resources! Beating SMB3 is less than a second is hilarious and I very much enjoyed learning about this. From the vulnerability itself to a making the exploit work, it's truly magic :)
Analysis Summary
# Vulnerability: NES DPCM Conflict & SMB3 Polling Loop Race Condition
## CVE Details
- **CVE ID:** N/A (Legacy hardware flaw; predates modern CVE assignment for consumer electronics)
- **CVSS Score:** 7.2 (High) - *Estimated based on Physical/Local access leading to Full Code Execution.*
- **CWE:** CWE-362 (Race Condition), CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer)
## Affected Systems
- **Products:** Nintendo Entertainment System (NES) Hardware; Famicom.
- **Versions:** Original Ricoh 2A03/2A07 CPU revisions.
- **Configurations:** Systems running games that utilize DPCM (Delta Pulse Code Modulation) audio samples and implement standard "Double-Read" controller polling workarounds. Specifically demonstrated on *Super Mario Bros. 3* (PRG0 and PRG1 versions).
## Vulnerability Description
The vulnerability stems from a hardware bug in the NES APU (Audio Processing Unit). When the APU fetches a DPCM sample byte from memory, it interferes with the CPU's ability to read the controller registers ($4016/$4017). This "DPCM Bug" causes a bit-shift error, resulting in corrupted input data.
To compensate, developers implemented a software workaround: polling the controller in a loop until two consecutive reads return identical values. However, by oscillating controller inputs at an extremely high frequency (simulated via TAS or specialized hardware), an attacker can prevent the polling loop from ever finding a match. This creates a "stuck" state that delays the completion of the Non-Maskable Interrupt (NMI). If the NMI is delayed long enough to collide with a subsequent Interrupt Request (IRQ), the system executes code with incorrect Bank Switching (PRG banks) loaded, leading to an unexpected Jump-to-Address and eventually Arbitrary Code Execution (ACE).
## Exploitation
- **Status:** PoC available (Console-verified TAS).
- **Complexity:** High (Requires micro-second precision of inputs).
- **Attack Vector:** Physical / Local (Controller Port).
## Impact
- **Confidentiality:** N/A
- **Integrity:** Total (Full control over system memory and execution flow).
- **Availability:** Total (Ability to crash the system or redirect execution to end-credits).
## Remediation
### Patches
- **Hardware:** No official hardware patch exists for original NES units.
- **Software:** Modern homebrew developers can use the "Safe DPCM" polling method (reading the registers while the DPCM is inactive or using specialized timing).
### Workarounds
- Disabling DPCM audio prevents the hardware conflict from occurring.
- Using a hardware-based controller that limits the rate of input changes.
## Detection
- **Indicators of Compromise:** Unusual program jumps to address $0000-$0001; unexpected execution of PRG Bank code in an IRQ context; rapid flickering of controller input state in RAM addresses $17, $18, $F5, $F6.
- **Detection Methods:** Trace loggers (e.g., BizHawk) can monitor the Program Counter (PC) for transitions to unconventional memory segments.
## References
- NESDev Wiki - Controller Reading DPCM Conflict: [https://wiki.nesdev.org/w/index.php/Controller_reading#DPCM_conflict]
- TASVideos Submission #7245: [https://tasvideos.org/7245S]
- Console Verification Video: [https://www.youtube.com/watch?v=R2Zzw4ahQVQ]