Glitch City Laboratories Archives

Glitch City Laboratories closed on 1 September 2020 (announcement). This is an archived copy of a thread from Glitch City Laboratories Forums.

You can join Glitch City Research Institute to ask questions or discuss current developments.

You may also download the archive of this forum in .tar.gz, .sql.gz, or .sqlite.gz formats.

Generation III Glitch Discussion

Gen III Remote Code Execution - Page 1

Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-18 19:58:35
TL;DR: with a Wii or GameCube where you can run homebrew (obviously easier with a Wii), and a GBA->GC link cable, you can abuse the functionality officially used by Pokémon Colosseum/XD/a bunch of Berry Glitch fixdiscs to get code exec in the context of Gen III.
(of course, you could also use the GBA BIOS multiboot, but here you don't have to press any buttons on the GBA BIOS to get multiboot going! And because this is in the Gen III ROM, this can legitimately be called Gen III RCE :P)

What is this?
Have you ever connected up Pokémon Colosseum/XD to your Gen III game? Remember how it told you to turn the GBA on with the GBA->GC link cable plugged in, then it took an awfully long time on the copyright screen and then showed some Colosseum/XD thing?
The awfully long time on the copyright screen was Colosseum/XD transferring a multiboot image (basically a GBA ROM image with modifications, namely it runs from RAM lol) to your GBA. The Colosseum/XD thing on the GBA was basically your GBA executing that multiboot image.
After two days of reversing (this multiboot protocol is pretty different to the one used by the BIOS, but it does have a couple of similarities), I got a respectable PoC working. So, where else to release this to the world?

Here's an image of, basically, the very PoC I'm releasing today. The payload only supports an English Pokémon Ruby, version 1.0, 1.1 or 1.2 (because that's the only of my Gen III carts I could find; I didn't know what version it was; and the GC/Wii-side application doesn't work inside Dolphin); The payload now supports every Generation III game, except Pokémon LeafGreen v1.1 (Japan) due to it being undumped; it calls the game's save-loading function, replaces the first character of the player name with 'z', then returns to the game (by calling the game's main loop as a function, because just returning would reload the save and overwrite the change made).

[img]http://i.imgur.com/Nb4hjPel.jpg[/img]

Where's the code?
Right here: https://github.com/Wack0/gba-gen3multiboot

If you want to modify the GBA-side payload, you can naturally find it in the [tt]gba[/tt] directory. Note that the payload is in the C language. A refreshing change of pace from position-independent ASM shellcode, I'm sure you'll agree.

To compile this, you'll need devkitPro (devkitARM for the GBA side and devkitPPC for the GC/Wii side). Remember, this is very much a proof of concept which I'm sure will be improved on by others.

How do I run it after compiling?

I assume you have a Wii (I'm not even going to mention GameCube, I think all the good ways to get homebrew running there need a Wii anyway lol), with the Homebrew Channel installed and running. Just execute the compiled [tt]gen3multiboot_wii.dol[/tt] there. When testing, I always used [tt]wiiload[/tt], it being quicker than copying a new binary to the SD card every time.

Make sure you have a GameCube controller plugged into controller port 1 and your GC->GBA link cable plugged into port 2.

Hey, it didn't work. What gives?

Code execution on the GBA side is not 100% reliable for some reason. A couple of different things could go wrong. If you see an error about KeyC derivation, you should just be able to turn your GBA off, press any button on your GameCube controller plugged into port 1, then turn your GBA back on again.

If, however, your GBA didn't seem to notice the code being transferred to it, and your Wii has frozen on a line with a CRC, you're going to need to power off both GBA and Wii, turn your Wii back on, get back into the Homebrew Channel and run [tt]gen3multiboot_wii.dol[/tt] again.

It may take a few tries but it will eventually work.

What is this "different" protocol?

If you want technical details, best see the source code.
At a higher level, it goes like this:



This took me quite some time of reversing work to figure out (there was lots of info about the GBA BIOS multiboot protocol on the internet; only ARM ASM code about this one, from the Pokémon reverse engineering project). I hope it helps someone.

Re: Gen III Remote Code Execution

Posted by: Yeniaul
Date: 2017-02-18 20:10:33
I'm still sad I couldn't help out with this, even though it happened on my Discord server right in front of me.
My self-esteem just tanked.
Dammit.

Re: Gen III Remote Code Execution

Posted by: Unused Trainer
Date: 2017-02-19 04:42:01
Is this a RAM code?

Re: Gen III Remote Code Execution

Posted by: Háčky
Date: 2017-02-19 06:58:51
Another technique that might be useful is to take advantage of the Mystery Events feature that overrides an NPC script (used to install Normans Eon Ticket script). By storing a script that uses the callasm command, you could execute code on demand by talking to a particular NPC.

By writing such a script into the save file using multiboot, it might be possible to do this even in games where Mystery Events is unavailable (FireRed/LeafGreen and non-Japanese Emerald).

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-19 12:35:44
Testing payloads under emulation

What you need:


First up you need to configure your Dolphin and VBA-M to get them linked together properly, there's enough guides round the internet for this, but here's some quick instructions anyway: configure Dolphin to have controller 2 as GBA, set up the GBA BIOS in VBA-M, make sure "Skip BIOS" is ticked and "Pause when inactive" is unticked on VBA-M Edit menu, in VBA-M options->link menu configure link type to be gamecube, make sure "Link on boot" is ticked, choose "Start Network Link".

Now, make a copy of that Interactive Multi-Game Demo Disc Version 16 ISO. Why is this ISO important? It has a Berry Glitch fix included! (Also, the PPC side here has lots of nice debug messages that show up in Dolphin logs and really helped me reverse the multiboot algo.)

Anyway, open up the copy of the ISO in your hex editor. First, go to offset [tt]0x45ded5fc[/tt] and patch two bytes here to [tt]48 00[/tt] – this bypasses the game code check so Japanese games will work. You'll need to do the same thing at offset [tt]0x45ecdf1c[/tt]. Next, go to offset [tt]0x45f2ae30[/tt] – that's where the multiboot image is stored that gets transferred by the Berry Glitch fix. So replace the bytes there with your own payload (up to 31,872 bytes - that's the size of the original multiboot payload used here), save.

Open your modified ISO up in dolphin, when you get to the main menu of the demo disc press down twice to get to the berry fix entry, and press A (X key is mapped to A button by default). After some health and safety etc screens, the multiboot part will start and show a screen of saying how this will fix the Berry Glitch and give out shiny Zigzagoons. Just press A again, and then open up your ROM in VBA-M. If everything goes well there should be some "data transferring" stuff in Dolphin, there will be longer copyright screen in VBA-M, and then your payload should run (and error should then show in Dolphin) :)

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-19 16:38:24
Meanwhile, I'm busy working on getting the example payload compatible with all of Gen 3.

I'd like to note something pretty important here.

Game Freak were sneaky fuckers.

In FR/LG (and probably Emerald as well) they left the save file loading code in place..

..then they added new code that reloads the save at the title screen after setting up ASLR.

Of course, my current in-dev payload now bypasses this.. by bypassing the title screen entirely and jumping straight to the Continue/New Game menu.

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-20 12:39:14
I've made a new commit that updates the payload: https://github.com/Wack0/gba-gen3multiboot/commit/d448823ffb540cb462548552efe4b5650e66de95

It now supports every Generation III game (except Pokémon LeafGreen v1.1 (Japan) due to it being undumped).

I haven't tested some Generation III games, mostly Sapphire which in every instance have the same offsets as Ruby. If the payload doesn't work on one of your Generation III games, post here, or create a GitHub issue, or both.

Payload code now goes into the [tt]payload()[/tt] function in [tt]payload.c[/tt], because [tt]main()[/tt] is now over 200 lines long to detect the Generation III game.

Coming soon™: actual save-block structure definition. "Soon™" because I don't really want to do that right now. If anyone wants to do it for memake a start or help me with it, pull requests would be much appreciated - it may motivate me to finish it sooner. :)

Re: Gen III Remote Code Execution

Posted by: Unused Trainer
Date: 2017-02-21 01:43:51
So even Fire Red/Leaf Green game?

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-21 04:04:11

So even Fire Red/Leaf Green game?


Yes, although with the following caveats:

- some payloads designed for RSE won't work on FRLG (the example one works fine everywhere though)
- parts of the FRLGE save files are crypted, working on rectifying that

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-21 09:10:21
I have just made a patcher for the Interactive Multi-Game Demo Disc Version 16 (USA) ISO because I got too bored of doing it manually.

It's made in C#, here's the code:

using System;
using System.IO;
using System.Text;

class ReplacePayload {

static bool ByteArrayCompare(byte[] a1,int offseta1, byte[] a2,int offseta2,int length) {
if (offseta1 + length > a1.Length)
return false;
if (offseta2 + length > a2.Length)
return false;

  for (int i=0; i<length; i++)
if (a1[offseta1+i]!=a2[offseta2+i])
            return false;

return true;
}

public static void Main(string[] args) {
if (args.Length < 2) {
Console.WriteLine("Usage: {0} <ISO path> <payload path>",System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
Console.WriteLine("ISO must be \"Interactive Multi-Game Demo Disc Version 16 (USA).iso\"");
return;
}
// read the payload
byte[] payload;
try {
payload = File.ReadAllBytes(args[1]);
} catch (Exception e) {
Console.WriteLine("An error occured when reading the payload.");
Console.WriteLine(e.ToString());
return;
}
// check the Nintendo logo
byte[] NintendoLogo = Convert.FromBase64String("JP+uUWmaoiE9hIIKhOQJrREki5jAgX8ho1K+GZMJziAQRkpK+Ccx7FjH6DOC486/hfTflM5LCcGUVorAE3Kn/J+ETXOjypphWJejJ/wDmHYjHcdhAwSuVr84hABApw79/1L+A2+VMPGX+8CFYNaAJaljvgMBTjji+aI0/7s+A0R4AJDLiBE6lGXAfGOH8Dyv1iXkizgKrHIh1PgH");
if (!ByteArrayCompare(payload,4,NintendoLogo,0,NintendoLogo.Length)) {
Console.WriteLine("The payload file is not a valid GBA binary (bad Nintendo logo)");
return;
}
// check the length
if (payload.Length > 31872) {
Console.WriteLine("The payload file is too big. Payload files to be patched in to the ISO can not be larger than 31872 bytes.");
return;
}
// open the file
FileStream fs;
try {
fs = new FileStream(args[0],FileMode.Open,FileAccess.ReadWrite);
} catch (Exception e) {
Console.WriteLine("An error occured when opening the ISO.");
Console.WriteLine(e.ToString());
return;
}

// make sure this ISO is the right one
byte[] gameCodeBytes = new byte[6];
try {
fs.Read(gameCodeBytes,0,6);
} catch (Exception e) {
fs.Close();
Console.WriteLine("An error occured when reading the game code from the ISO.");
Console.WriteLine(e.ToString());
return;
}
if (Encoding.ASCII.GetString(gameCodeBytes) != "D79E01") {
fs.Close();
Console.WriteLine("This ISO is not Interactive Multi-Game Demo Disc Version 16 (USA)!");
return;
}
// patch the jumps
foreach (long offset in new long[] { 0x45ded5fc, 0x45ecdf1c }) {
try {
fs.Seek(offset,SeekOrigin.Begin);
fs.WriteByte(0x48);
fs.WriteByte(0x00);
} catch (Exception e) {
fs.Close();
Console.WriteLine("An error occured when patching the Berry Glitch fix executable in the ISO.");
Console.WriteLine(e.ToString());
return;
}
}
// patch the payload
try {
fs.Seek(0x45f2ae30,SeekOrigin.Begin);
fs.Write(payload,0,payload.Length);
} catch (Exception e) {
fs.Close();
Console.WriteLine("An error occured when patching the multiboot payload in the ISO.");
Console.WriteLine(e.ToString());
return;
}
fs.Close();
Console.WriteLine("The ISO has been patched successfully and now contains your payload and works with any Gen 3 game.");
}
}


To compile it, just save it out as [tt]replace_payload.cs[/tt], open up a command prompt window in the directory you saved it to and run [tt]C:\windows\Microsoft.NET\Framework\v4.0.30319\csc replace_payload.cs[/tt] (for .NET 4.x) or [tt]C:\windows\Microsoft.NET\Framework\v2.0.50727\csc replace_payload.cs[/tt] (for .NET 2.x). It supports both.

You should also be able to use [tt]mono[/tt] in other operating systems too, I believe there the command would be [tt]msc replace_payload.cs[/tt]
(you can also use the official MS .NET Framework in [tt]wine[/tt] if that's your thing.)

It's a command line executable, just run it like [tt]replace_payload.exe "Interactive Multi-Game Demo Disc Version 16 (USA) - Copy.iso" gba_pkjb.gba[/tt]

Obviously replace the ISO and multiboot image paths as appropriate. Run it on a copy of the ISO as it will overwrite the ISO.

Re: Gen III Remote Code Execution

Posted by: Yeniaul
Date: 2017-02-21 09:27:04
Remember, look at how the encryption behaves, not how it works.

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-21 15:01:50
New commits, added FR/LG/E secure-area decryption, party reloading from the loaded save file after calling the payload (so now it's possible to modify the party and have the changes reflected; watch out for the crypto and the checksumming though!), and some helpful macros ([tt]GAME_x[/tt] where [tt]x[/tt] can be [tt]RUBY, SAPP, RS, FR, LG, FRLG, EM[/tt] and [tt]LANG_JAPAN[/tt]) to more easily allow for payloads compatible with all of Gen III.

About "payloads compatible with all of Gen III", here's a Hall of Fame payload. It contains partial savedata structures that are themselves partially from pokeruby. Eventually more complete structures will get integrated into the project.

/*
* Example Gen3-multiboot payload by slipstream/RoL 2017.
*
* This software may be modified and distributed under the terms
* of the MIT license.  See the LICENSE file for details.
*
* payload.c: place where user payload should go :)
*/

#include <gba.h>
#include "payload.h"

typedef struct {
s16 x;
s16 y;
} Coords16;

typedef struct {
s8 mapGroup;
s8 mapNum;
s8 warpId;
s16 x;
s16 y;
} WarpData;

// ugh..

struct PokemonSubstruct0
{
    u16 species;
    u16 heldItem;
    u32 experience;
    u8 ppBonuses;
    u8 friendship;
};

struct PokemonSubstruct1
{
    u16 moves[4];
    u8 pp[4];
};

struct PokemonSubstruct2
{
    u8 hpEV;
    u8 attackEV;
    u8 defenseEV;
    u8 speedEV;
    u8 spAttackEV;
    u8 spDefenseEV;
    u8 cool;
    u8 beauty;
    u8 cute;
    u8 smart;
    u8 tough;
    u8 sheen;
};

struct PokemonSubstruct3
{
/* 0x00 */ u8 pokerus;
/* 0x01 */ u8 metLocation;

/* 0x02 */ u16 metLevel:7;
/* 0x02 */ u16 metGame:4;
/* 0x03 */ u16 pokeball:4;
/* 0x03 */ u16 otGender:1;

/* 0x04 */ u32 hpIV:5;
/* 0x04 */ u32 attackIV:5;
/* 0x05 */ u32 defenseIV:5;
/* 0x05 */ u32 speedIV:5;
/* 0x05 */ u32 spAttackIV:5;
/* 0x06 */ u32 spDefenseIV:5;
/* 0x07 */ u32 isEgg:1;
/* 0x07 */ u32 altAbility:1;

/* 0x08 */ u32 coolRibbon:3;
/* 0x08 */ u32 beautyRibbon:3;
/* 0x08 */ u32 cuteRibbon:3;
/* 0x09 */ u32 smartRibbon:3;
/* 0x09 */ u32 toughRibbon:3;
/* 0x09 */ u32 championRibbon:1;
/* 0x0A */ u32 winningRibbon:1;
/* 0x0A */ u32 victoryRibbon:1;
/* 0x0A */ u32 artistRibbon:1;
/* 0x0A */ u32 effortRibbon:1;
/* 0x0A */ u32 giftRibbon1:1;
/* 0x0A */ u32 giftRibbon2:1;
/* 0x0A */ u32 giftRibbon3:1;
/* 0x0A */ u32 giftRibbon4:1;
/* 0x0B */ u32 giftRibbon5:1;
/* 0x0B */ u32 giftRibbon6:1;
/* 0x0B */ u32 giftRibbon7:1;
/* 0x0B */ u32 fatefulEncounter:5; // unused in Ruby/Sapphire, but the high bit must be set for Mew/Deoxys to obey in FR/LG/Emerald
};

union PokemonSubstruct
{
    struct PokemonSubstruct0 type0;
    struct PokemonSubstruct1 type1;
    struct PokemonSubstruct2 type2;
    struct PokemonSubstruct3 type3;
    u16 raw[6];
};

// yes, I know, international lengths, who cares..
#define POKEMON_NAME_LENGTH 10
#define OT_NAME_LENGTH 7

struct BoxPokemon
{
    u32 personality;
    u32 otId;
    u8 nickname[POKEMON_NAME_LENGTH];
    u8 language;
    u8 isBadEgg:1;
    u8 hasSpecies:1;
    u8 isEgg:1;
    u8 unused:5;
    u8 otName[OT_NAME_LENGTH];
    u8 markings;
    u16 checksum;
    u16 unknown;

    union
    {
        u32 raw[12];
        union PokemonSubstruct substructs[4];
    } secure;
};

struct Pokemon
{
    struct BoxPokemon box;
    u32 status;
    u8 level;
    u8 pokerus;
    u16 hp;
    u16 maxHP;
    u16 attack;
    u16 defense;
    u16 speed;
    u16 spAttack;
    u16 spDefense;
};

typedef struct {
Coords16 pos;
WarpData location;
WarpData warps[4];
u16 battleMusic;
u8 weather;
u8 unk_2F;
u8 flashUsed;
u16 mapDataID;
// there's a better way to do this, but for PoC purposes it's fine
union {
struct {
u16 mapView[0x100];
u8 playerPartyCount;
struct Pokemon party[6];
} rse;
struct {
u8 playerPartyCount;
struct Pokemon party[6];
} frlg;
} version;
} partialSaveBlock1;

// Your payload code should obviously go into the body of this, the payload function.
void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) {
// HoF-warp example payload!
partialSaveBlock1* psb1 = (partialSaveBlock1*)SaveBlock1;
if (GAME_FRLG) {
psb1->location.mapGroup = 1; // generic indoors?
psb1->location.mapNum = 80; // Hall of Fame
// set coords to the same place that the champions' room script sets them to
psb1->location.x = psb1->pos.x = 5;
psb1->location.y = psb1->pos.y = 12;
psb1->mapDataID = 0xDA; // from HoF map-header
} else {
psb1->location.mapGroup = 16; // Ever Grande City
psb1->location.mapNum = 11; // Hall of Fame
// set coords to the same place that the champions' room script sets them to
psb1->location.x = psb1->pos.x = 7;
psb1->location.y = psb1->pos.y = 16;
psb1->mapDataID = ( GAME_EM ? 298 : 299 ); // from HoF map-header
}
psb1->location.warpId = 0xff;
// make sure the HoF script doesn't crash, which it will do if 0 pokémon
if (!GAME_FRLG) {
if (psb1->version.rse.playerPartyCount == 0) {
psb1->version.rse.playerPartyCount = 1;
// this isn't enough, the heal animation recalculates the party count ignoring empty spots
// so let's hack together one. i don't care about it becoming a bad egg at all.
psb1->version.rse.party[0].box.personality++;
}
} else {
if (psb1->version.frlg.playerPartyCount == 0) {
psb1->version.frlg.playerPartyCount = 1;
// this isn't enough, the heal animation recalculates the party count ignoring empty spots
// so let's hack together one. i don't care about it becoming a bad egg at all.
psb1->version.frlg.party[0].box.personality++;
}
}
}


Also on the to-do list: Pokémon data structure element getters/setter functions (yaknow, to deal with the substructures and the crypto and the checksumming).

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-02-26 08:44:59
New major commit.



Now, writing payloads should be easier.

Here's another example payload that adds a ramscript, which replaces Brendan's mother's script (R/S/E)/Red's mother's script (FR/LG), causing them to say "Pwned!". (Yes, I know you won't be able to see it if you're playing R/S/E as May, but really, it's such a small change that you should be able to figure it out pretty easily.)


/*
* Example Gen3-multiboot payload by slipstream/RoL 2017.
*
* This software may be modified and distributed under the terms
* of the MIT license.  See the LICENSE file for details.
*
* payload.c: place where user payload should go :)
*/

#include <gba.h>
#include <string.h>
#include "payload.h"

// Your payload code should obviously go into the body of this, the payload function.
void payload(pSaveBlock1 SaveBlock1,pSaveBlock2 SaveBlock2,pSaveBlock3 SaveBlock3) {
struct RamScript* ramScript;
if (GAME_RS) ramScript = &(SaveBlock1->rs.ramScript);
else if (GAME_FRLG) ramScript = &(SaveBlock1->frlg.ramScript);
else if (GAME_EM) ramScript = &(SaveBlock1->e.ramScript);
else return;
ramScript->data.magic = 0x33;
if (GAME_FRLG) {
ramScript->data.mapGroup = 4; // Pallet Town indoors
ramScript->data.mapNum = 0; // Red's house 1F
ramScript->data.objectId = 1; // Red's mother
} else {
ramScript->data.mapGroup = 1; // Littleroot Town indoors
ramScript->data.mapNum = 0; // Brendan's house 1F
ramScript->data.objectId = 1; // Brendan's mother
}
u8 script[] = ""
"\xb8\x00\x00\x00\x00" // setvaddress 0
"\xbd\x0e\x00\x00\x00" // vtext msgtext
"\x66" // waittext
"\x6d" // waitbutton
"\x6c" // release
"\x02" // end
"\xCA\xEB\xE2\xD9\xD8\xAB\xFF"; // msgtext: "Pwned!"
// copy the script to its rightful place
memcpy(&(ramScript->data.script),script,sizeof(script));
// fix the checksum
ramScript->checksum = CalculateRamScriptChecksum(ramScript);
// all done!
return;
}

Re: Gen III Remote Code Execution

Posted by: suloku
Date: 2017-03-18 08:44:44
A little of topic here, but since you reversed the transfered rom and know how game detection works, modifiying the colosseum USA/JAP bonus disc to accept other region carts would be feasible? I'd really like to test that. I checked a little the code, do they only use gamecodes for that?


Another technique that might be useful is to take advantage of the Mystery Events feature that overrides an NPC script (used to install Normans Eon Ticket script). By storing a script that uses the callasm command, you could execute code on demand by talking to a particular NPC.

By writing such a script into the save file using multiboot, it might be possible to do this even in games where Mystery Events is unavailable (FireRed/LeafGreen and non-Japanese Emerald).


It is, in fact I found that Emerald/FRLG seem to use the same slot for mistery gift script for the WonderCard script (not really checked with jap versions, since the only mistery gift script was for RS, as eon ticket in emerald is hardcoded and mistery gift only enabled an in-game flag).

I'm not sure if this reachead Glitch City, but I coded a program to create custom wondercards, and what you suggest can easily be done with a wondercard too, meaning you could even send the script+asm to other people via the wifi adapter. There's 992 bytes for the script available if I remember correctly, and it can easily be assigned to another npc.

Re: Gen III Remote Code Execution

Posted by: Stackout
Date: 2017-03-18 16:34:58

A little of topic here, but since you reversed the transfered rom and know how game detection works, modifiying the colosseum USA/JAP bonus disc to accept other region carts would be feasible? I'd really like to test that. I checked a little the code, do they only use gamecodes for that?


I reversed the transfer process itself, not any transfered multiboot images.