I'd like to start by mentioning that I enjoyed your article, it goes into a lot of original research that I haven't seen documented yet, but one thing that bothers me is that the mechanics of the final exploit are never explained, so I decided to research this myself.
Character 3F is <ENEMY> which causes the text parser to call PrintEnemysName to print wOTClassName during link battles. Writing this in wOTClassName causes an infinitely recursive loop when <ENEMY> is parsed.
This loop causes a stack overflow, putting the stack pointer in SRAM. Thankfully, nothing in this loop is reading values from the stack, as it's only pushing and calling. However, once an interrupt triggers, it needs to store the address to return to. Since we can't write to SRAM as it's closed, writes will be ignored, and reads will (usually) all be $FFFF.
However, once the interrupt returns through reti, it returns into $D9D9 thanks to open bus behavior (D9 is the reti instruction). Open bus behavior is when a device stops pulling data lines (as is the case here since SRAM is closed), and for a short time the last value is preserved and will be read, in this case D9 from the last instruction read. This jumps lands in a nop sled, until the timer interrupt occurs.
The timer interrupt, since the mobile adapter is turned on, will attempt to bankswitch to bank $44, however, in doing so it'll encounter a ret instruction instead, now jumping to $C9C9, which is part of WRAM. This time interrupts are disabled (reti enables them), so it can slide indefinitely along WRAM. With some luck there's no bytes in the way that break the slide, and it'll end up
at the controllable data area, $CA4F.
This is a lucky turn of events, since if the timer interrupt happens at a moment when the stack pointer hasn't fully reached SRAM yet, different things can happen depending on what code finally tries reading from the SRAM. One particularly bad example is when a ret instruction reads only the upper half of the address from WRAM, and the lower half from SRAM (with open bus
behavior, so always C9), as this will jump to the address $xxC9, where xx is the upper part of the real return address. And that's without mentioning that open bus behavior is hard to predict, and while it might work with an MBC30 in GBC double-speed mode (the few people I've consulted say it would probably be fast enough to read the same value twice), it likely doesn't work with flashcarts and a fair amount of different emulators.
I'd like to start by mentioning that I enjoyed your article, it goes into a lot of original research that I haven't seen documented yet, but one thing that bothers me is that the mechanics of the final exploit are never explained, so I decided to research this myself.
Character 3F is
<ENEMY>which causes the text parser to callPrintEnemysNameto print wOTClassName during link battles. Writing this inwOTClassNamecauses an infinitely recursive loop when<ENEMY>is parsed.This loop causes a stack overflow, putting the stack pointer in SRAM. Thankfully, nothing in this loop is reading values from the stack, as it's only
pushing andcalling. However, once an interrupt triggers, it needs to store the address to return to. Since we can't write to SRAM as it's closed, writes will be ignored, and reads will (usually) all be $FFFF.However, once the interrupt returns through
reti, it returns into $D9D9 thanks to open bus behavior (D9 is the reti instruction). Open bus behavior is when a device stops pulling data lines (as is the case here since SRAM is closed), and for a short time the last value is preserved and will be read, in this case D9 from the last instruction read. This jumps lands in a nop sled, until the timer interrupt occurs.The timer interrupt, since the mobile adapter is turned on, will attempt to bankswitch to bank $44, however, in doing so it'll encounter a ret instruction instead, now jumping to $C9C9, which is part of WRAM. This time interrupts are disabled (reti enables them), so it can slide indefinitely along WRAM. With some luck there's no bytes in the way that break the slide, and it'll end up
at the controllable data area, $CA4F.
This is a lucky turn of events, since if the timer interrupt happens at a moment when the stack pointer hasn't fully reached SRAM yet, different things can happen depending on what code finally tries reading from the SRAM. One particularly bad example is when a
retinstruction reads only the upper half of the address from WRAM, and the lower half from SRAM (with open busbehavior, so always C9), as this will jump to the address $xxC9, where xx is the upper part of the real return address. And that's without mentioning that open bus behavior is hard to predict, and while it might work with an MBC30 in GBC double-speed mode (the few people I've consulted say it would probably be fast enough to read the same value twice), it likely doesn't work with flashcarts and a fair amount of different emulators.