The CartSwap ACE - Using Pokémon to ACE / credits warp other games
Posted by: ISSOtm
Date: 2016-12-02 15:22:15
This is a Proof of Concept (or PoC) that we can use Pokémon Red, Blue and Yellow to manipulate other games.
The principle is extremely simple :
[li]Set up in Pokémon R/B/Y[/li]
[li]Temporarily hang the CPU[/li]
[li]Switch cartridges while the CPU is doing nothing[/li]
[li]Do things with the new cartridge[/li]
This PoC limits itself to rebooting the cartridge, but a more useful application would be modifying save files, stuff like that. This exploit just proves this is possible.
[size=18pt]How to do this ?[/size]
First, you'll need Pokémon Red, Blue or Yellow, and a Game Boy Color. I insist on GBC, because it won't work on any GBA model.
It may or may not work on black&white GBs, but nobody tested it yet.
For obvious reasons, this cannot be done on emulators.
Once you are running R/B/Y on your GBC, you need to acquire 8F/ws m arbitrary code execution. Visit our wiki article.
That is, obtain the item, and set up the bootstrap accordingly.
Then, you need to setup your inventory exactly like this, starting from the first item :
(V Warning, these panels scroll V)
8F / ws m
Any item x[any qty]
Ice Heal x243
Protein x99
Antidote x106
Awakening x240
Escape Rope x27
Poké Ball x122
Great Ball x179
TM06 x255
Super Repel x247
Fresh Water x4
Burn Heal x177
Fire Stone x241
Lemonade x1
TM33 x[whatever]
[the rest of the item pack doesn't matter]
Here is a method to get all of this :
[li]Obtain 8F and set the party up[/li]
[li]Empty your bag, save for the first two slots[/li]
[li]Buy Antidote x1, Awakening x1, Escape Rope x27, Poké Ball x1 and Burn Heal x1 at Pewter City Mart[/li]
[li]Buy Ice Heal x1, Great Ball x1, Super Repel x1 and TM33 x1 (or more, it doesn't matter) at the Celadon Dept. Store 1F[/li]
[li]Buy Fire Stone x1 at Celadon Dept Store 3F[/li]
[li]Buy Protein x99 (or buy Protein x1 and duplicate it later) at Celadon Dept Store 4F[/li]
[li]Buy Lemonade x1 or x11 and Fresh Water x4 at Celadon Dept Store 5F[/li]
[li]Defeat Koga to get TM06 (or retrieve it from your storage system if you already beat him)[/li]
[li]MissingNo-duplicate Ice Heal, Antidote, Awakening, Poké Ball, Great Ball, TM07, Super Repel, Burn Heal and Fire Stone.[/li]
[li]Toss Ice Heal x14, Antidote x23, Awakening x17, Poké Ball x135 (you can toss Poké Ball x99 then Poké Ball x36), Great Ball x78, TM07 x2, Super Repel x10 and Fire Stone x16[/li]
[li]Duplicate again Ice Heal, Awakening, Poké Ball, Great Ball, TM07, Super Repel and Fire Stone[/li]
[li]Swap items using the SELECT button to move items around until you match the item pack described above.[/li]
(For newbies, "MissingNo-duplicate" means "encounter or capture MissingNo with the item you wish to duplicate in the sixth slot of your item pack")
I then recommend that you save. You're going to pull your cartridge out, anyway, so it's a good idea to save this hard work :D
If your item pack doesn't match what is described above, I take no responsibility from what happens next, including crashes, corruptions and loss of save data. YOU HAVE BEEN WARNED.
Then use 8F / ws m. If the game just freezes, you probably got it right. If not, that's where you'll be happy you saved.
You now have like ten seconds to pull your Pokémon game out of the Game Boy and place another cartridge in.
Then the game in place now will start, but it will believe it is running on a black & white GB instead of a GBC. Yep.
That's what this exploit does : start a game without going through the "Game Boy" script, and thus you could, for example, edit your save file !
[size=18pt]Tech details[/size]
When pulling the cartridge out of the socket, the CPU doesn't hang, reset or anything. What happens is all reads from ROM return hex:FF.
Strangely, it appears that the GBC boot ROM may actually be ran again. I don't know why this doesn't work 100% of the time.
The thing is, Furrtek proved it was possible to remove the cartridge and have the CPU still run. (See TheZZAZZGlitch's post below).
As such, the idea was to prevent the CPU from running anything ROM while cartridges were being swapped.
This implied two things.
First, disabling interrupts. Because all reads from ROM return $FF, this would result in constant rst 38h, and as such, a stack overflow that would erase all RAM.
Second, running code from a memory location NOT in ROM nor SRAM.
We were left with VRAM, WRAM (and Echo RAM, but that's essentially the same :P), OAM and HRAM.
WRAM was the good candidate, since all ACE exploits ran code from WRAM.
We thus needed to disable interrupts, temporarily hang the CPU while in RAM, and resume once the cartridge was back in.
I first tried a code that first waited to read a $FF from ROM, then boot back up as it read no more $FF. It got a 12% success rate, probably from badly engaged pins.
Then TheZZAZZGlitch suggested a code that just waited for a set amount of time. After many mistakes, I finally made the code ! And here it is.
Here is the GBz80 assembly code that is actually ran by the exploit, very dirty and hackish, but what matters are the items :D
dec c ; padding
di
inc h ; padding
ld h, e ; $0122
dec bc ; padding
ld l, d ; hl = $0100
ld c, $F0 ; adjust this for delay : the more there are, the less you'll wait. 240 give ~12 seconds of delay.
dec e ; de = $0000
.delayCartReboot
dec de
inc b ; padding
ld a, d
inc bc ; padding ; won't affect C since this is run 256 times
or e ; a = $00 if and only if de = $0000. Also resets the C flag, so the next op will really be an "add".
adc a, $FF ; will overflow unless a = $00, ie de = $0000
jr c, delayCartReboot
inc a ; a becomes $00 (it was $FF)
inc b ; padding
inc c ; we need C to be zero
or c ; A = $00 at this point, so essentially this is "ld a, c" but this updates the Z flag
jr nz, delayCartReboot
ld a, $01
jp (hl)
This code is full of padding instructions, intended to make the code "viable" as items, because some values are represented by invalid items, or some we can't the quantity of.
They are intended to not trash sensible data (RAM and important registers) and not lock execution.
Step-by-step explanation :
First, we have the instruction that disables interrupts. It could have replaced the "inc b" instruction sandwiched between the "inc a" and "inc c" in the later part of the code, saving one slot, but this would be dangerous.
Then, I use the fact that, when this code starts, register DE's value is hex:0001. As such, I make H equal E ($00) and L equal D ($01), that gives HL = $0100, the memory address the Game Boy boot ROM jumps to when it gives control to the game. We will preserve this value during the whole process as to make the final jump a simple "jp (hl)".
We then decrement E to make DE equal $0000, the value required for all entries in the following loop.
We then enter the two intricate loops, marked each by their jr instruction.
The inside one simply decrements DE, and ends when the bitwise OR of D and E plus $FF doesn't trigger overflow.
This only happens when D OR E = 0, ie when D = E = 0, ie when DE = $0000. Tests gave that these 65536 cycles (due to overflow) took roughly a tenth of a second.
This complicated pattern of recognizing D = E = 0 was done because circumstances restricted us to only the NZ, Z and C conditions of the JR instruction, and NZ was needed by both. So we figured this setup could translate a NZ into a C !
Note that even though the C register is very precious, it is modified through every iteration by the "inc bc". Yet, it is incremented 65536 = 256 * 256 times… so, the value before entry and after exit will be the same.
The extern loop increments C (which was trashed so much it was preserved :P), and returns when it is 0. As such, the higher C is set in the preamble, the less this extern loop re-loops. And there you go !
Then, the last two instructions. The jp (hl) jumps to $0100 (preserved from the preamble), performing the "reboot", while A is set to 1 The reason for this is that games functioning for both GB and GBC use the value of register A to determine the console they are being played on. $11 indicates GBC, and usually all other value are treated as B&W GB. (The GB ouputs a $1, which was also convenient, so I chose 1).
Because Pokémon R/B make the GBC boot in "B&W compatibility", we need games to boot as if on a GB.
[size=18pt]Original post[/size]
I was browsing Twitter, and then I found this. https://www.youtube.com/watch?v=xayxmTLljr8
Now, I'm wondering : what if we ran a 8F payload that did this :
1. Disable ALL interrupts
2. Loop forever until something occurs (button press, I think)
3. Do some more stuff (enabling interrupts and whatnot :P)
And then, during step 2, we pulled off the Pokémon R/B/Y cartridge and plugged some other cart inside ?
I don't see why the console would crash, no I/O seems to R/W/J/X from ROM… soooo it just might be possible to transfer ACE's between games ! That'd be hella sweet.
But I don't know if there isn't a protection that freezes the CPU when the cartridge is removed ! That's the deal.
Ideas :
1. If it works, we'd just need to make two payloads and transfer one to a part of memory where it won't be overwritten… but first we need to know if this can theoretically work.
2. If the console stops, enable the Joypad interrupt ONLY, then run a STOP and trigger the interrupt once cartridges have been stop'n'swop'd. Since the Joypad interrupt points to a reti in R/B(/Y?), this should be fine.
8F setup that might work :
8F
Item x[qty]
TM43 x4
Escape Rope x29
Repel x15
Lemonade x16
Hyper Potion x118
[More stuff]
Registers on startup :
af = 6300 [a=63, f=00]
bc = 22B8 [b=22, c=B8]
de= 0001 [d=00, e=01]
hl= D322 [h=D3, l=22]
All flags reset
di ; prevent bad code from being executed. Interrupts will stack requests in FFFF, but meh.
inc b ; filler
dec e ; de = $0000
dec de ; de = $FFFF
ld e, $0F ; de = $FF0F
ld a, $10
ld (de), a
halt
; return to game code, perform ACE, write something to VRAM ?