In 2021, I was part of team of four European researchers focused on finding exploits in retro games. We set our goal on finding Arbitrary Code Execution (ACE) in Pokémon Diamond and Pearl. We operated under online aliases: Map, FlederKiari, Martmists and myself, RETIREglitch.

If you’re reading this, I assume you’re already familiar with cyber security or ACE in some way. But as a quick recap: Arbitrary Code Execution is a term in cyber security describing the ability to take total control over a system through an exploit. In most scenarios this makes use of invalid user input that is not sanitized properly. But in the case of a game, you are constricted to in-game actions.

Preceding Research

The Void

Most games have tricks, bugs or glitches that have been discovered by players. In Pokémon Diamond and Pearl, several glitches allow the player to enter an out of bounds area of the game named the ‘void’, named after it’s black and empty appearance. While out of bounds, the game reads unrelated memory as maps to enter. Invalid maps are error-handled, and always become Jubilife City. Several sub-glitches are possible through the void. You may overflow your coordinates to reach ‘parallel universes’, wrong-warp, get walk through walls, reach unintended areas of the game. The exact behavior of the void is completely understood, and I myself wrote several tools to debug and manipulate the void and maps that appear.

The RETIRE trick

In 2017 a cyber security researcher known as Cryo, (also known as Ganix or KernelEquinox) found a trick they coined the RETIRE trick. Through reverse-engineering they discovered that a specific menu function used in 2 mini-game areas of the game lacked validation checks. This menu would replace the top function on the player’s menu with the word ‘RETIRE’ when entering the Safari Zone or Pal Park. Under normal circumstances triggering this option would open a prompt asking whether the player wants to leave the minigame area. These two areas are the ‘Safari Zone’ and ‘Pal Park’.

If the ‘Safari Zone flag’ is set when the button is pressed, the game always runs a script to leave the Safari Zone. In all other cases, it runs a map script with index 4. Map scripts, as their name suggests, are scripts associated with the map the player currently resides in. In Pal Park, this would prompt the player to leave, similarly to the Safari Zone case. However, if used in other maps, it would run whatever script is linked to index 4, without any checks performed.

This menu is enabled when the player enters the Safari Zone through payment, or by simply entering the Pal Park map. Ganix realized that inside the void they could enter Pal Park’s map, which would automatically enable the menu. Leaving the map does however not automatically remove the menu, meaning the RETIRE trick was now available in any map.

This quickly lead to the first in-game method of obtaining the Legendary Pokémon Arceus, which was an event-only distribution Pokémon. There was an in-game event to trigger a battle against the Pokémon, through an item that was never released to the public. The script to trigger this battle happened to be linked to script index 4, which meant that using the RETIRE trick in the unused map would trigger the battle.

Later, a similar glitch coined alt-RETIRE was found by a researcher known as Area/Yem. She found that after receiving the menu, catching 6 Pokémon would also run a map script. This is because the Pal Park is an area to receive 6 Pokémon from another game, and catching all 6 forces the player to leave the area. But the prompt is slightly different, as there is no option to back reconsider leaving (after all, you finished the minigame). This different script has index 3, rather than 4.

At first this trick was limited to being used in maps that have grass or water tiles to get the encounter. Later I would find a method of manipulating the tiles that appear in the void, including grass and water. Since we can enter any map in the void, this now allowed alt-RETIRE to be used in any map as well.

Script Execution Anomalies

Some researchers attempted to abuse the RETIRE trick in maps containing less than 4 map script indexes. In many cases this lead to a crash or nothing happening. In rare cases however, a script command unrelated to the map would be triggered, leading some to speculate this could lead to ACE.

Research Findings

Explaining the anomalies

Our research focused on figuring out how these anomalies functioned at a low level, and if there was any potential for the bug leading to Arbitrary Code Execution. FlederKiari performed the initial research, focusing on reverse-engineering how map scripts are executed.

This execution of map scripts would be visualized as follows:

  1. Trigger a map script.
    This can occur upon entering a map, talking to an Non-playable Character (NPC), walking over a trigger, or pressing the RETIRE option. Store the related map script index.
  2. Copy all script data related to the current map from the cartridge (ROM) to main memory.
    This script data consists of a table with offsets to scripts, and the map scripts themselves.
  3. Read the offset from the table using the map script index.
  4. Use the offset to find the address of the associated script.
  5. Interpret the script
    Read each byte at this address, and find the corresponding script command. Read parameters if present, and continue until a command is invalid or returns.

FlederKiari noted how this behaviour lead to the anomalies. No validation is performed on the map script index. If a script index is higher than the amount of map scripts in the current map, the offset in the table will be read out of bounds, and thus the address of the associated script reads from unrelated memory. If the address is inside a controllable memory section, this could lead to arbitrary script execution. This lets the user run any script command in the game’s scripting language.

In the above image the script data for a map containing 4 map scripts is visualized. Each offset is highlighted in the same color as their associated script. The offset table and scripts are separated by a terminator value 0xFD13. If the RETIRE trick would be performed in this map, the purple offset 0x53 would be read from the table. It would then add this offset to the address of the purple offset, 0x22969A4, and add size(uint32_t), or 0x4 to the final value. This would form address 0x22969FB, the address of the first command in the script that is highlighted purple.

In the above image, the script data for the map contains 2 scripts. In this case, reading the 4th script offset reads past the table containing offsets.

It ends up reading script commands of the first map script as an offset to script 4. In this case, the value 0x6005DC. This would form the address 0x2896F84, which is a protected memory region, this would result in a crash due to access violations.

Dumping potential entrance points

Martmists and I focused on writing several tools to automate the dumping process of the available invalid offsets. Since the data past the offset table are script commands, the offset is inherently linked to the map the execution takes place in. There is no way to control the offsets that get generated. If no map contains commands that generate an offset to controllable and valid memory regions, there is no further exploitation possible.

To dump the offsets we made use of the emulator DeSMuMe, a Nintendo DS emulator with scripting capabilities. Later during development we moved all our scripts to Bizhawk using the MelonDS core. MelonDS has several accuracy improvements, specifically to memory domain access on several consoles. Bizhawk also provided a better API for scripting and GUI elements.

The game has a total of 558 maps, of which 385 contain less than 4 scripts. The first revision of the tool would alter the current map index to one of those map indices and execute the RETIRE trick in them. It would detect the menu closing, and track the dynamic allocation of scripts, finally reading the generated offset during runtime.

At the end of this, the dump provided 61 unique offsets. Many of the maps have similar script commands, and resulted in the generation of identical offsets. Based on the GBATEK – GBA/NDS Technical Info (problemkaputt.de) we implemented filters for protected memory regions. This resulted in 22 remaining unique offsets that land within main memory. Of these, only 1 offset was proven to land in a controllable section of memory. When the RETIRE trick is executed in map 332 or 333, the script execution will start in a section of memory where variables can temporarily be allocated. Luckily, these temporary variables are not cleared when their structs are freed.

Of many types of data that may be allocated here, 2 are currently proven to allow for script execution with enough control to spell a jump instruction. This is through Hall of Fame data, allocated when viewing your records in a PC, and your items during some trainer battles. The goal from here is to redirect script execution from this memory section to a more controllable memory section.

A suitable, fully controllable memory region

Initially our goals were set on a built in calculator application, as it allows the user to insert and calculate outputs of up to 10 bytes in size. This would be enough to spell the LoadAdressValue command, which allows us to write 1 byte of data to any address in memory. I wrote a script that translated our simple instructions to inputs for the calculator, and a bot that performed these at inhuman speeds.

Later I decided to redirect code execution to the Dot Artist Application. This is a painting application which allows players to draw dots on a canvas. Internally, this canvas is a bit field. The user can insert up to 120 completely controllable bytes of input through painting. It also proved to be easier to debug than the calculator application: The output of the Dot Artist can be saved, and easily modified if an error is made.

After settling on the Dot Artist as the best input field I created a website, script converter, which generates the painting outputs for any script. A user can create and customize scripts, and share them with the community.

Turning Script Execution into Code Execution

The transition from script execution to full code execution proved to be relatively straightforward. The scripting language provides several commands designed to allow memory manipulation, including writing and copying memory values.

On the Nintendo DS functions can be copied to main memory for optimization purposes. It reduces the amount of calls made to copy instructions from ROM. Given the newfound ability to manipulate memory, it became possible to directly modify the assembly instructions of functions residing in main memory. This would turn the script execution into code execution seamlessly.

Proof of Concept

By overwriting the code associated with the RETIRE trick to enable Arbitrary Script Execution to function in any map, then subsequently altering the code for encountering wild Pokémon, we were able to establish a proof-of-concept. This POC allows the player to encounter an event-exclusive Shiny Mew, which is not typically attainable through regular gameplay.

Followup work

Since the initial finding of Arbitrary Script and Code Execution I’ve contributed to several new ACE methods. One such exploit is a remote code execution exploit making use of Gifts that can be wirelessly distributed. I also contributed to AGDQ 2024, showing a save file that hacks and adds a storyline/custom event to the game.