NSEC21 Rare Metal Sequencer, pt. 1 - Mon, May 24, 2021 - Lucas Bajolet Barberousse
Rare Metal Sequencer
This challenge is somewhat particular as far as reverse-engineering challenges go, as we’re not dealing with standard architectures/OSes, nope!
In this case, we’re given a SNES rom called sequencer.smc
, in which there’s obviously a flag to find, so first up, let’s get it working shall we!
Making the rom work
First off, since this is homebrew, and because the original hardware is only documented as a collaborative effort by hardware enthusiasts (AFAIK at least, there are no official manuals on the hardware that can be consulted), there is bound to be inaccuracies in emulators.
So, in this case, we’ve tried a few of what was available (at least on Linux):
- bsnes: both in accurate and conpatibility modes, the ROM failed to boot
- snes9x: same issue, cannot boot the ROM
- ZSNES: YEP, that works alright
Let’s go with ZSNES in all its 386-style retro UX to make it all work:
Alright, instructions clear, let’s input something!
OK, didn’t work as expected.
We’ll make it clear by that point: if a bad sequence is input, we have to go back to a state that allows us to input more buttons, so either a save-state, or a reset.
Finally, by pure luck, we tried to push all the buttons one by one to see if there was a difference, and by pressing the Y
key, we do get a slight change:
Note: the FLAG-[...]
output can be printed at any point by pushing start, unless a bad combination was input.
Past that point, trying any button didn’t make us progress, so we resorted to the best tool for the job: reverse-engineering!
Disassembling SNES code is not a standard feat in many tools, fortunately one does well, and it’s available for all of us to use: radare2
:)
Reversing the ROM
First up, let’s start by saying that there’s actually a FLAG-.*
string in there, right at address 0x884b
, however it is 2044B long, and is obviously not as simple as having a strings
give us a flag.
We then need to get the place where inputs are handled so we can try to understand the logic that makes the game happy.
This is located in fcn.000081d3
.
We’ll have a bit of explaining to do if we want to make it understandable what happens (this video is an amazing resource on input handling, which we used to get our info from).
What happens when you press a button, without going into the nitty-gritty of electrical signals, is that each clock cycle for the controller, the input buttons are flushed to a series of addresses, as two bytes.
Each possible button is encoded as one bit in either of these two addresses (at least for one controller).
The following diagram will make it clear which button is which bit, in which byte:
So in our case, if we go back to the assembly code, we can see the sequence:
- Byte 0x4219 is read, and stored in the accumulator, and then stored in address 0x121c
- The value registered at the last frame is then XORd over the current one
- The end value is then stored in 0x1214
This is for one byte over at 0x4219, and the same sequence is then repeated for address 0x4218.
This leads us to the next block:
So basically, if there’s a value set at address 0x1200, we take the jump, otherwise we continue.
We can try to follow the branch taken if the predicate is false.
So here, we have a first button check indeed.
Remember that what is actually at address 0x1218 is what was read from address 0x4219, so that matches any of the AXLR
buttons of the first controller.
In this case, the code jumps to the next block that is not a fast exit with invalid combination if none of those buttons were pushed.
This does corroborate what we had experienced empirically with the ROM at first.
Let’s move on.
Next block is also quite small.
Alright, so here we read address 0x1214, which was where the values of address 0x4218 were written, so that represents any button of the following sequence BYsSUDLR
(s -> select, S -> start).
The XOR 0x40 ensures that only one button is pressed, by the logic here’s what we should have as value:
01000000 -> which is our array corresponds to the Y
button.
So that was what we executed when we first opened the rom.
What happens afterwards is that the value gets written to 0x1200, and we eventually quit the function.
So that’s the logic for each combination in a nutshell.
We have other combinations to decipher one-by-one:
- @0x825e -> 0x1214 ^ 0x84 -> combination of the B + Down buttons
- @0x828f -> 0x1214 ^ 0x28 -> combination of the select and Up buttons
- @0x82bd -> 0x1218 ^ 0x20 -> L button
These four sequences should get us to the end of the challenge:
Of course that wasn’t the end of it, after we submitted that flag, we had a second ROM to work with, which will be covered in another article :)