An old school RE challenge was published on August 07th and has been solved by several people. This blog post provides a detailed solution on how to solve this challenge followed by the winner write-up.
The challenge provided was not so complicated, its difficulty mainly relying on its architecture which is rarely encountered in CTF.
Indeed, The program to analyze is an Atari ST ROM of the game MetroCross.
The ROM was modified in order to add a cheat code, which was not originally present in the game and which permits to reach the last level.
The goal of the challenge was to analyze the ROM to find this code.
If you check the Atari ST wikipedia webpage, you notice that the ST runs on the Motorola 68000 CPU.
Good news, this processor is supported by IDA and a debug version of the steem emulator exists, providing a debugger with all its basic features (set a breakpoint, view/edit the memory content, etc.)
Hence, it is possible to work on the challenge statically through IDA and dynamically with the emulator.
In this writeup, the emulator will be used to dump the memory while running the game, to locate the "main" function and to validate that the cheat code found works. When reversing an Atari ST game, it is easier to work on a memory dump for different reasons:
- You will not lose time with complicated code from a packer;
- If you want to fully reverse the game, it is easier to have a maximum of resources uncompressed in memory (sprites, music, etc.);
- You will have the same addresses on the disassembler and the emulator.
Set up your emulator (don't forget to use the TOS v1.00), load the game and when you reach the game menu, pause the emulator and dump the memory content to a file.
The dump can be loaded in IDA by setting the processor type to Motorola MC 68000 . If everything went fine, you should have the same addresses in IDA and the debugger.
Locate the "main" function
An easy way to know at which address the TOS (the Atari ST OS) has loaded the game is to use the emulator. Once this address is known, we will be able to start reversing from the beginning of the game main code.
The emulator being currently paused, let's check at which part of the code is the Program Counter (PC):
At the end of this snippet, there is an instruction "RTE" (Return From Exception).
PC points to the code that belongs to an interrupt handler (caused by the emulator being paused).
This code is not of interest, we can then select "Debug > Run to RTE" to leave the exception handler code.
Then, to locate the "main" function, we will put a breakpoint at the instructions "RTS" (Return To Subroutine, the equivalent of a RET instruction in x86) and click on "run" then "trace into".
The methodology used here is straightforward: backtrace the call stack to get to the main function.
Following this methodology, we can conclude that the game code base is located at 0xA304.
At the beginning of the main function, there are three calls to the same function (the one we renamed ReadFileInMemory) with the following strings amongst the arguments:
This function is used to load a resource into memory (game data).
For example, the first call at this function is set up that way:
lea (aPicsManDat).l, a0 ; a0 = path of the resource to load move.l #unk_4FE2A, d6 ; d6 = 0x4FE2A (the address where the resource will be loaded) move.l #$41A0, d7 ; d7 = 0x41A0 (the resource size) bsr.w ReadFileInMemory ; call to the function ReadFileInMemory()
In the code of the function ReadFileInMemory, registers are initialized and the trap instruction is called three times. The trap instruction is used to call the Atari ST subsystem functions, the three following ones exist:
- GEMDOS (through the trap #1 instruction)
- the GEMDOS BIOS interface is used to make high level I/O operations (file handling, etc.)
- GEMBIOS (through the trap #13 instruction)
- the Atari BIOS is the interface between GEMDOS and the hardware. It can be used to call low-level I/O functions (i.e: retrieve a keypress, write a disk sector etc.)
- XBIOS (through the trap #14 instruction)
- EXtended BIOS: similar to the BIOS but provides features more specific to the Atari (i.e: play music, change screen color etc.)
The book Atari ST Internals is a great documentation to get all the subsystem functions details.
To call a subsystem function, the function number and its parameters must be pushed on the stack. The function return value is stored in the d0 register.
Example: call to fopen()
clr.w -(sp) ; 2nd argument: 0 (read_only) move.l a0, -(sp) ; 1st argument: file_path move.w #$3D, -(sp) ; function number: 0x3D corresponds to fopen() trap #1 ; fopen() is provided by the GEMDOS interface, so the trap #1 is used addql #8, sp ; stack cleaning tst.l d0 ; check return value bmi.w loc_105d2 ; jmp if d0 is negative
In the function ReadFileInMemory, there are calls to respectively fopen(), fread() and fclose().
Now that you are familiar with the trap instructions, let's dig into the function we renamed LoadMusic.
By looking at the strings referenced in this function, one can guess that the function is used to load the music data files. However, several elements make us think that the original behaviour of this function has been altered.
- There is no music in this modified ROM unlike the original one.
- There are suspicious "nop" instructions in the code.
According to the ST Internals book, the function number 5 when the trap #13 is executed is setexc, which has the following prototype:
- setexc: set exception vector
- LONG setexc(vecnum, vec)
- vecnum: the vector number
- vec: the address to setup in the vector slot
As its name suggests, this function is used to set a new exception vector. In our case, two exception vectors are set:
- setexc(35, @off_11384)
- setexc(36, @off_12650)
The vector numbers 35 and 36 respectively correspond to the trap #3 and #4.
Therefore, if "trap #3" is triggered, the code located at 0x11384 will be executed. In a similar way, if "trap #4" is triggered, the code located at 0x12650 will be executed.
By using the IDA search option to look for all the trap instructions (alt+t => "trap" then check "find all occurences"), we can see that both trap instructions are referenced in only one part of the code.
Reverse Engineering the "hijacked" IKBD handler
The "trap #3" instruction is located in the part of the code that handles the keyboard interrupts:
move.b ($FFFC02).w, d0 nop trap #3 [...]
0xFFFC02 is the static address of the IKDB data register. This address references the current keyboard "scancode"1. In this case, the "hook" is installed on the keyboard read handler (IKBD handler). The keyboard scancode is read in d0 and the trap #3 is executed. Therefore, the first custom routine, located at 0x11384, is executed as well. One must note that this handler is called each time a keypress is hit, so is our custom handler.
Below is the annotated "hook" function, the reader can take a look at the Atari ST scancode in order to understand the following code.
RAM:00011384 hook: RAM:00011384 andi.w #$FF,d0 ; d0 contains the scancode RAM:00011388 move.b (sum).l,d4 ; sum contains 0 RAM:0001138E cmp.b #9,d0 RAM:00011392 RAM:00011392 loc_11392: RAM:00011392 ble.w locret_11410 RAM:00011396 cmp.b #$33,d0 ; scancode must be between 0x10 and 0x32 ('A' and '?') RAM:0001139A bge.w locret_11410 RAM:0001139E RAM:0001139E loc_1139E: RAM:0001139E cmp.b #$20,d0 ; ; keyscan == 'D' ? RAM:000113A2 bne.s loc_113B2 RAM:000113A4 tst.b d4 ; sum == 0 ? RAM:000113A6 bne.w loc_1140A RAM:000113AA add.b d0,(addr).l ; if sum == 0: add 0x20 to sum RAM:000113B0 rte RAM:000113B2 ; --------------------------------------------------------------------------- RAM:000113B2 RAM:000113B2 loc_113B2: RAM:000113B2 cmp.b #$13,d0 ; keyscan == 'R' ? RAM:000113B6 bne.s loc_113C8 RAM:000113B8 cmp.b #$20,d4 ; sum == 0x20 ? RAM:000113BC bne.w loc_1140A RAM:000113C0 add.b d0,(addr).l ; if sum == 0x20: add 0x13 to sum RAM:000113C6 rte RAM:000113C8 ; --------------------------------------------------------------------------- RAM:000113C8 RAM:000113C8 loc_113C8: RAM:000113C8 cmp.b #$16,d0 ; keyscan == 'U' ? RAM:000113CC bne.s loc_113DE RAM:000113CE cmp.b #$33,d4 ; sum == 0x33 ? RAM:000113D2 bne.w loc_1140A RAM:000113D6 add.b d0,(addr).l ; if sum == 0x33: add 0x16 to sum RAM:000113DC rte RAM:000113DE ; --------------------------------------------------------------------------- RAM:000113DE RAM:000113DE loc_113DE: RAM:000113DE cmp.b #$31,d0 ; keyscan == 'N' ? RAM:000113E2 bne.s loc_113F4 RAM:000113E4 cmp.b #$49,d4 ; sum == 0x49 ? RAM:000113E8 bne.w loc_1140A RAM:000113EC add.b d0,(addr).l ; if sum == 0x49: add 0x31 to sum RAM:000113F2 rte RAM:000113F4 ; --------------------------------------------------------------------------- RAM:000113F4 RAM:000113F4 loc_113F4: RAM:000113F4 cmp.b #$25,d0 ; '%' ; keyscan == 'K' ? RAM:000113F8 bne.s loc_1140A RAM:000113FA cmp.b #$7A,d4 ; 'z' ; sum == 0x7A ? RAM:000113FE bne.s loc_1140A RAM:00011400 move.b #$18,(loc_A3C6+3).l ; if sum == 0x7A and current keyscan == 'K', self modify the code located at 0xa3c6 to copy the value $24 to the address corresponding to the current level RAM:00011408 rte RAM:0001140A ; --------------------------------------------------------------------------- RAM:0001140A RAM:0001140A loc_1140A: RAM:0001140A RAM:0001140A clr.l (addr).l ; if the keys 'D', 'R', 'U', 'N', 'K', are not hit in that order, the variable 'sum' is reset to 0. RAM:00011410 RAM:00011410 locret_11410: RAM:00011410 RAM:00011410 rte RAM:00011410 ; End of function hook
Because we are working on a memory dump, we did not have to bother with a little technique that was introduced in this challenge. Indeed, if you were reversing the ROM statically, you would have seen the instruction cmp.b #$14, d0 at address 0x1139E, making you believe that the cheat code is "TRUNK". But if you take a look at the end of the function we previously called "LoadMusic", you can see the instruction _move.b #32,(loc_1139E+3).l that is used to self modify the code from the hook, patching the 'T' to 'D'.
Last but not least, the "trap #14" executed at the main function after the main game menu, will execute the second custom routine that will just uninstall the hook. This trap is installed right after the player leaves the main game menu, which is why the cheat code will not work if it is not typed on the menu.
move.l #$24000FF,(sub_10C16).l rte
The previous code restores the original code of the IKBD handler by patching the instructions 'nop ; trap #3' with 'andi.w #$FF, d0'
Write-up of the winner
Congratulations to the winner Orion from the well-known former french cracking team The Replicants. You can read his solution below:
Hi fellow hackers,
The cheatcode is 'drunk'
The main difficulty is that the game assumes it is loaded by the TOS at fixed address $A304. The executable is actually relocatable but it contains several hard-coded addresses based on the previous assumption.
In particular, the routine located at $11ED2 installs a TRAP#3 handler at hard-coded address $11384 (the routine which checks the scancodes! see below).
Therefore, trying to run the game with a different TOS version or under a debugger leads to a crash because the game is loaded at a different address.
In order to be able to trace the game, I did the following:
- I wrote a small program that loads my debugger (adebug) at the end of the RAM. This small program is started from the auto folder and thus it is loaded by the TOS at address $A304!
- I wrote another small program that loads and relocate metro.prg "in place" (where the prog has been loaded)
- Under adebug, I load this second small program at address $A304 (hence overwriting the first small prog) and I trace it. metro.prg is loaded and relocated at address $A304 where it is supposed to run.
- I traced the game until I found it installs an IKBD handler ($118).
- I traced that handler and found it executes a TRAP#3 with the scancode of the key pressed passed as parameter in d0.
- The TRAP#3 handler merely verifies the cheatcode: d r u n k
- If the cheatcode is entered, then value $18 is saved at address $a3c9, leading the last level to be loaded.
Orion ^ The Replicants
One of the people who solved the challenge (Nicolas Iooss) found an interesting easter egg in the original game.
This easter egg can be triggered when you have to enter a name in the high score table.
If you have the highest score and enter the name SUGREF ..., we let you the surprise to find out what happens by yourself ;-)
Funny fact is that "SUGREF" matches the name "FERGUS" if you read it upside down. One of the guy in charge of porting the game from arcade to micro-computer is named "Fergus mcGover" and he used to insert easter egg in its ports.
Special congratulations to Nicolas Iooss who found out this easter egg that was not known before!
"scancode" is the term for "key-code" in Atari ST terminology ↩