I achieved arbitrary code execution on a Tamagotchi using a bug in figure ROM processing. This capability should allow me to dump the Tamagotchi code ROM after some analysis, as well as allow me to ‘hack’ my Tamagotchi using the full capabilities of the microprocessor.
I mentioned in an earlier post that I saw some ‘freezing’ behaviour in the figure game functionality. Game logic is controlled in its entirety by a single bit ‘game code’, which I suspected was the index of a jump table containing all Tamagotchi functionality, and invalid game codes, especially those in the middle of the 0-255 range cause the Tamagotchi to freeze, requiring reset.
Looking into how 6502 (the Tamagotchi microcontroller architecture) works, I thought this might be a sign that the microcontroller was veering off into unexpected code. Memory in 6502 is mapped into a single address space, and there’s no MMU or other memory protection. If unmapped memory is accessed, it returns 0 or some other garbage value. Invalid instructions do not cause a reset, but execute undefined behaviour taking a non-deterministic amount of time. This meant that freezing was unlikely to be due to an error being detected by the microcontroller (of course, it was possible that an error was being detected by the code on the microcontroller, and it handled the error by going into a tight loop, but this struck me as unlikely, as the Tamagotchi tends to handle detected errors by resetting). It also meant that exploiting a bug in 6502 should be reasonably forgiving, as if the PC ends up somewhere before the intended address, it will likely execute any garbage instructions in sequence, and end up at the right address anyhow.
My first thought was that maybe the invalid “game codes” corresponded to indexes outside of the jump table, so the microcontroller was jumping to addresses that were actually other data. Since LCD RAM is the only memory that can be controlled by a figure, I was hoping that maybe one of these values would cause a jump into LCD RAM. So I filled up my LCD with a NOP slide and shell code and hoped.
I tried all 255 indexes, but none of them worked. I did notice two interesting things, though. First, the indexes in the middle of the range weren’t all invalid. Some of them showed valid screens, although these screens didn’t always work. This meant that I probably wasn’t jumping to an unintended address, but something else was happening to cause freezing, such as the stack wasn’t set up correctly for the jump. Secondly, the index 0xCC had some interesting behaviour. If bit 3 of the 68th byte in the LCD RAM was set, it played a repeated beeping, and would detect when the figure was removed, and return to the main screen. If this bit wasn’t set, it would play no sound, and freeze. Based on the sound, I guessed that this was the location the Tamagotchi jumps to when playing a sound when a key is pressed. So the logic would be “check if sound is enabled (probably based on an address on the stack), and if set play the sound and return to the address on the stack otherwise return to a different address on the stack”. Except the stack was messed up, so it was checking the LCD RAM for the bit, and returning to the address where it started instead of the correct address.
I found this perplexing. Considering that this behavior confirmed that pointers to the LCD RAM were being put on the stack before the jump, I found it surprising that none of the 255 possibilities were causing code execution. Eventually, I suspected my shellcode might be wrong. Since I have no sample microcontroller to test code on, and don’t know the locations of the ports, I thought this was likely. My original code was supposed to flash the IR LED, but I moved to the less ambition jumping to reset (since practically nothing will reset a 6502 microcontroller, this would be a good indicator of code execution). I also checked my code with the SunPlus compiler. And found I was using entirely the wrong instruction set. With the right instruction to byte value mapping, the Tamagotchi reset on the third “game code” I tried, 0xd4.
Playing with different instructions, I found the code execution was very unreliable, especially when calling longer instructions like stores and loads. Eventually, I figured out that the Tamagotchi LCD does not use one contiguous piece of LCD RAM, but uses at least two separate pieces. Jumping to one of these locations immediately after starting execution made it much more reliable.
Below is an example of changing the LCD using stores and loads. The area circled in blue is the first code that gets executed, and the area circled in green is the code in the contiguous memory it jumps to. The white boxes (circled in red) are the contiguous pieces of memory, set to be white using STA. The entire row is part of the memory, but I only set one byte (four pixels) per row.
For people trying this at home, the LCD RAM locations so far are:
Rows 1-7: 0x10cc-0×1107
Rows 8-18: 0×1120-0x117C
Row 17: 0x10B4-0x10CB
Row 18-31: ???
Next step: chain this together to dump the code!