title: Juniors CTF 2016 - Joy500 Oldschool NES Rom Write Up author: depierre published: 2016-11-27 categories: CTF, Security keywords: juniors, ctf, write-up, misc, 500, challenge, nes, rom I played the Juniors CTF 2016 this weekend with some friends of mine and it was quite fun! Since we missed the registration deadline, I sneakily joined the [Securimag](https://securimag.org/wp/) team and played with them. ![Oldschool challenge](/static/images/juniors2016/oldschool_challenge.png) One of the challenges was a modified NES ROM of Castlevania II - Simon's Quest. The challenge was not really hard after all but [@xarkes](https://twitter.com/xarkes_) and I thought that it was refreshing to play with a NES ROM for once :) # NES ROM: Castlevania II - Simon's Quest Based on the challenge description, we supposed that we had to finish the game in order to get the flag. The first thing we did was downloading the original ROM and diffing the two files: :::bash depierre$ radiff2 Castlevania-II_eng.nes Castlevania\ II\ -\ Simon\'s\ Quest\ \(USA\).nes 0x000125c6 d3cad8edccc6d2cad8ed0101 => c6d1d9cdd4dacccd01d9cdca 0x000125c6 0x000125d5 c6d7caeddc => c8d4d3cbd7 0x000125d5 0x000125dc c9cad7cbdad1 => d9c6d9ced4d3 0x000125dc 0x000125e5 01010101010101 => c7cad9dccacad3 0x000125e5 0x000125ed 0101010101 => d8ced2d4d3 0x000125ed 0x000125f5 010101 => c6d3c9 0x000125f5 0x000125f9 01010101010101 => c9d7c6c8dad1c6 0x000125f9 0x00012603 010101 => cdc6d8 0x00012603 0x00012607 01010101010101010101 => c8d4d3c8d1dac9cac9ec 0x00012607 0x00012614 0101010101 => d8ced2d4d3 0x00012614 0x0001261a 0101010101010101 => c8d4dad1c9d3eed9 0x0001261a 0x00012625 01010101010101 => d8dad7dbcedbca 0x00012625 0x0001262d 010101 => cdced8 0x0001262d 0x00012633 0101010101 => cbc6d9c6d1 0x00012633 0x00012639 01010101010101 => dcd4dad3c9d8eb 0x00012639 0x00012643 0101010101010101010101010101 => d9d7c6d3d8ded1dbc6d3cec6eed8 0x00012643 0x00012654 01010101 => d4d3d1de 0x00012654 0x00012659 01010101 => cdd4d5ca 0x00012659 0x0001265e 0101 => ced8 0x0001265e 0x00012661 01 => c6 0x00012661 0x00012665 0101010101 => ded4dad3cc 0x00012665 0x0001266b 010101 => d2c6d3 0x0001266b 0x0001266f 010101 => dccdd4 0x0001266f 0x00012675 01010101 => dcced1d1 0x00012675 0x0001267a 01010101010101 => d9d7cedad2d5cd 0x0001267a 0x00012684 01010101 => d4dbcad7 0x00012684 0x00012689 01010101 => cadbced1 0x00012689 0x0001268e 010101 => c6d3c9 0x0001268e 0x00012694 010101 => d7cec9 0x00012694 0x00012698 010101 => d9cdca 0x00012698 0x0001269c 01010101 => c8ced9de 0x0001269c 0x000126a3 0101 => d4cb 0x000126a3 0x000126a6 010101010101010101 => c9d7c6c8dad1c6eed8 0x000126a6 0x000126b2 010101010101 => c9cac6c9d1de 0x000126b2 0x000126b9 010101010101 => c8dad7d8caeb 0x000126b9 The patch is very small. Since the two ROMs are very similar so we thought that: + The patch modified the end-sequence, so that when you beat the boss the code behaves in a different manner + The patch modified the outro, so that when you finish the game the image is different # What was patched? We found a tool named [NESRomTool](https://sadistech.com/nesromtool/romdoc.html) that can extract the PRG (program data) as well as the CHR (character data). Running NESRomTool on each ROM and using [binwally](https://github.com/bmaia/binwally) to diff the files showed that there was no difference in the sprites: :::batch depierre$ ./NESRomTool -f Castlevania-II_eng.nes -xs +a +a f1 depierre$ ./NESRomTool -f Castlevania\ II\ -\ Simon\'s\ Quest\ \(USA\).nes -xs +a +a f2 depierre$ ./binwally.py f1 f2 | grep -i differs depierre$ It would mean that the code of the ROM has been patched maybe. So we diffed the program data: :::batch depierre$ ./NESRomTool -f Castlevania-II_eng.nes -xp +a g1 depierre$ ./NESRomTool -f Castlevania\ II\ -\ Simon\'s\ Quest\ \(USA\).nes -xp +a g2 depierre$ ./binwally.py g1 g2 | grep -i differs 99 differs Castlevania-II_eng.nes.5.prg depierre$ Trying to dissassemble the patch didn't yield anything meaningful to us, mostly due to the fact that NES ASM is quite obscure (in a sense that you need to understand how the NES handles banks, sprites, sounds, etc. otherwise you have no idea what the code is doing). Instead, we decided to play the game. Or more specifically, have someone very, *very* good play for us :) Who could beat the game every single time and as fast as possible? [**TAS of course!**](http://tasvideos.org/1119M.html) The latest TAS finishes the game in **29:45.02** and I am sure that the NES emulator will allow us to increase the speed of the emulation so we can go down to a couple of minutes only ;) We used [FCEUX](http://www.fceux.com/web/version.html) in order to emulate the ROM, since it seemed to be the only one capable of supporting the *.fcm* TAS file: ![TAS ending](/static/images/juniors2016/castelvania.gif) Sadly, there was no flag showing up when finishing the game... Even slowing down the emulation speed to 3% (in case the flag was only shown for a frame or two) didn't help. What the hell man? Did they lie about the challenge? Using the emulator, we decided to patch the patch and replace everything with the [BRK](http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php?title=BRK) opcode, hoping that the game will freeze or crash when reaching the end: ![Patched patched ROM](/static/images/juniors2016/patch.png) But nothing froze, nothing crashed... # No but really, what *was* patched? Ok, let's take a step back. What is that patch? It doesn't seem to be a patch for the sprites or a patch for the code. We decided that maybe it was time for us to better understand how the Castelvania ROM was organized. We looked on the Internet trying to find some information about the offset `0x000125c6` and if it had any specific meaning. And [we found it](http://www.thealmightyguru.com/Games/Hacking/Wiki/index.php?title=Castlevania_II:_Simon%27s_Quest#Ending): > 124CC-127BF - Ending Dialog They only patched the ending dialogs of the ROM. But how come don't we see the flag when running the TAS? Let's parse the new dialogs anyway... Using the information from the wiki, we parsed the new dialogues with a quick and dirty python script: :::python table = { '\x01': ' ', '\x02': '/', '\x21': '\\', '\xC6': 'A', '\xC7': 'B', '\xC8': 'C', '\xC9': 'D', '\xCA': 'E', '\xCB': 'F', '\xCC': 'G', '\xCD': 'H', '\xCE': 'I', '\xCF': 'J', '\xD0': 'K', '\xD1': 'L', '\xD2': 'M', '\xD3': 'N', '\xD4': 'O', '\xD5': 'P', '\xD6': 'Q', '\xD7': 'R', '\xD8': 'S', '\xD9': 'T', '\xDA': 'U', '\xDB': 'V', '\xDC': 'W', '\xDD': 'X', '\xDE': 'Y', '\xDF': 'Z', '\xE0': '0', '\xE1': '1', '\xE2': '2', '\xE3': '3', '\xE4': '4', '\xE5': '5', '\xE6': '6', '\xE7': '7', '\xE8': '8', '\xE9': '9', '\xEA': '?', '\xEB': '.', '\xEC': ',', '\xED': '-', '\xEE': '\'', '\xEF': '&'} o_beg = 0x124cc o_end = 0x127bf ending = '' raw_ending = '' with open('Castlevania-II_eng.nes', 'rb') as f: f.read(o_beg) raw_ending = f.read(o_end - o_beg) for i in range(0, len(raw_ending), 1): ending += table.get(raw_ending[i:i+1], ' ') print ending And lo and behold! :::bash hl_lines="3" depierre$ python2 read.py THE BATTLE HAS/ 'CONSUMMATED./\ NOW PEACE AND/\ SERENITY HAVE BEEN RESTORED/ 'TO TRANSYLVANIA/\ AND THE PEOPLE/\ ARE FREE OF DRACULA'S/ 'CURSE FOREVER./\ AND YOU, SIMON/\ BELMONT, WILL ALWAYS BE/ 'REMEMBERED FOR/\ YOUR BRAVERY/\ AND COURAGE. NES-GAMES- / 'ARE-WONDERFUL/ \ /\ / ' /\ /\ / ' / \ /\ / ' /\ /\ THE ENCOUNTER/ 'WITH DRACULA/\ IS TERMINATED./ \ SIMON BELMONT HAS PUT AN END/ 'TO THE ETERNAL/\ DARKNESS IN/\ TRANSYLVANIA. HIS BLOOD AND/ 'SWEAT HAVE/\ PENETRATED THE/ \ EARTH AND WILL INDUCE MAGIC &/ 'HAPPINESS FOR/\ THOSE WHO WALK/\ ON THIS LAND Flag: **NES-GAMES-ARE-WONDERFUL** # Indeed, Castlevania has 3 endings... Castlevania II - Simon's Quest has in fact 3 different endings, depending on how fast you finish the game. Since the TAS we used finishes the game as fast as possible, we got the best ending. But the flag was only shown for the normal ending... I feel sorry if any of the teams played the game too well and didn't get the flag at the end :D ![Juniors 2016 Scoreboard](/static/images/juniors2016/scoreboard.png) By the end of the CTF, we solved quite a lot of challenges and reached the 5th place :)