[Reverse] WPI CTF 2022 - PokemonRematch

PokemonRematch

Solves: ??

Points: ???

Description:
Beat the game.

1 flag for beating the game, 2 flags if S.S.Anne doesn’t deport from the dock.

Attachment:

download
41c4dfc1e3e282b2a149b0accdc477ca

TLDR

  • Decrypt the rom by reversing the emulator
  • Use Cheat search to find exit map connections address
  • Change the warp location constant to hall of fame room (0x76)

Introduction

The challenge offered two emulators for two operating systems (linux and macos) and a ROM.

1
2
3
4
5
$ file pokered.gb
pokered.gb: data

$ file emulator_linux
emulator_linux: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped

From the file command, we can actually see that the ROM is weird since it doesn’t detect it as a real GB ROM.

For example, a real GBA ROM would output something like this:

1
2
file real.gba   
real.gda: Game Boy ROM image: "POKEMON RED" (Rev.00) [SGB] [MBC3+RAM+BATT], ROM: 8Mbit, RAM: 256Kbit

From this, we can assume the ROM must be encrypted and the emulator must be decrypting it before loading it.

There is a high chance that the author of the challenge didn’t implement an emulator from scratch, so this is probably a modified emulator from an open source project.

From the file command file emulator_linux we can see the symbols weren’t stripped, so we can easily identify the functions by their real names.

If we search for load, we can see the namespace is FunkyBoy:

FunkyBoy is an open source project, and we can take a look at the source code in github.

From the challenge description and in the begining when we start a new game professor Oak will tell us that he will give us two flags, one if we beat the game and another one if we beat the game without the boat S.S.Anne departing.

We could play the ROM and beat the game, but unfortunately it seems to be pretty hard to do so since the game seems to have been modified to be harder to beat (Brock has 22 level Pokemon).

Dumping the ROM

I used two methods to dump the ROM, GDB and Frida.

GDB

We can use the command dump memory but first we need to find a place to breakpoint and dump the ROM.

By looking at the original code of the emulator, we can see the rom is being loaded at the function Memory::loadROM.

First it reads the header here and much later it reads the rest here.

As we can see here, the ROM raw_bytes are eventually saved in a class variable rom.

We could have imported the structures to make IDA/Ghidra code much more readable, but to be honest, it takes some time to fix and import, and the code is not very hard to understand and locate where the encryption is being done.

As we can see below, the code shows up right after file reading:

We can setup our breakpoint at 0x40d6cf, but before that, we would need to know the size of the ROM. Looking around on github, we can find a place where the ROM size is calculated.

We can locate this in IDA/Ghidra by looking for the call to romSizeInBytes:

Finally we use GDB to dump from memory:

1
2
3
4
5
6
7
8
9
10
11
pwngdb> b *0x40d6cf
pwngdb> b *0x40D598
pwngdb> r pokered.gb
Breakpoint 2, 0x000000000040d598 in FunkyBoy::Memory::loadROM(std::istream&, bool) ()
► 0x40d598 mov eax, eax
pwngdb> p/x $rax
$1 = 0x100000
pwngdb> c
Breakpoint 1, 0x000000000040d6cf in FunkyBoy::Memory::loadROM(std::istream&, bool) ()
► 0x40d6cf movzx eax, byte ptr [r13 + 0x149]
pwngdb> dump memory dump_gdb.gba $r13 $r13+0x100000

Checking the signature:

1
2
$ file dump_gdb.gba
dump.gda: Game Boy ROM image: "POKEMON RED" (Rev.00) [SGB] [MBC3+RAM+BATT], ROM: 8Mbit, RAM: 256Kbit

Frida

We can also use a very cool project named frida.

This can be easily installed with the following commands:

1
2
$ pip install frida
$ pip install frida-tools

This project makes it very easy to hook functions, and there is a very good function to hook and get the pointer of the ROM variable.

The most interesting thing about Frida is that we can easily use JavaScript to modify the behaviour when a certain function is called or even call other functions inside of it.

Here is an example how we could dump the file using frida:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var romSizeInBytes_ptr = 0x4126A0; //ghidra
var getROMHeader_ptr = 0x40DFC0; //ghidra
var romSize_offset = 0x0148; // https://github.com/kremi151/FunkyBoy/blob/74bdcaf8b876d18293ba833d977a5892c9ef65d7/core/source/cartridge/header.h#L39

Interceptor.attach(ptr(getROMHeader_ptr), {
onEnter: function(args) {},
onLeave: function(retval) {
var fd = new File("dump.gda", "wb");
var romSizeInBytes = new NativeFunction(ptr(romSizeInBytes_ptr), 'uint32', ['uint8']);
var romSize = retval.add(romSize_offset).readU8();
var realRomSize = romSizeInBytes(romSize);
if (fd && fd != null) {
fd.write(Memory.readByteArray(retval, realRomSize));
fd.flush();
fd.close();
}
}
});

To run frida we can use the following command:

1
$ frida -l hook.js -f ./emulator_linux pokered.gb --no-pause

Getting to the hall of fame

Now that we dumped the file, we can use other emulators to debug the ROM.

To exploit the game, my theory was to find the offset of the warp_location when the player enters a door or switches to a new map and change it to the Hall of Fame room.

The bgb emulator is excellent because it has a memory searcher similar to Cheat Engine and also a debugger:

1
$ wine bgb.exe dump.gda

I used the cheat memory searcher to find the offset required.

First I clicked on start (we get all the addresses listed in the window):

So if we exit the door, we will be teleported to another map. It’s logical to assume that the value stored in the offset where the connections between the maps will change. After we exit the map, we can select the check box not -> equal to-> search With this, we can see we eliminated 40k possibilities:

Then I decided to reenter the room and try the same method with not -> equal to -> the previous value -> search but the number of addresses reduced was very low.

So instead I decided to move around the room and use multiple not -> above to -> the previous value -> search and not -> below -> the previous value -> search and I got as far as 1000 addresses:

Following this, I noticed that there were a lot of 0A and 0B addresses in memory. I decided to risk it and remove them with not -> equal to -> this value: 0A -> search and not -> equal to -> this value: 0B -> search.

With this, I was able to reduce it to 100 addresses:

It becomes difficult to reduce it further from here… So I searched in bubblepedia on possible warp_location values, and I found the one that could lead me to the blue house:

The constant for Blue’s house is 39. Converting to hexadecimal, we get 0x27.

Exiting the house and going near Blue’s house, we can use the filter equal to -> this value: 27 -> search:

We are left with two offsets, D3B6 and D73C by choosing to modify the first address to 2A (Poké Mart (Viridian City) ):


We are teleported to the Poké Mart after entering the door:

Now that we know how to teleport, we must first leave the pokemart and return to the blues’ house door.

Then we need to figure out what the constant is for the Hall of Fame. I saw on bubblepedia that the Hall of Fame constant is 118:

Meanwhile I found the complete list of the warp locations here in the pokemon red source code.

By changing the constant to 118 -> 0x76, we finally get teleported to the final room of the game and win:

The flags were FatPika:IsBEST! and GottaCaTcH!em