This is the first part of the blogging for NAOMI games, mostly in exploit.ninja, but also can be downloaded from the github HERE.
As part of the reversing for Arcade games, in particular NAOMI and NAOMI2 cartridges a good friend of mine asked me for help on figuring out the format, as even MAME takes it as a bit of “magic” (they split the cartridges and load them into arbitrary addresses) and the knowledge is closed at this point, wanting to get the scene bigger we decided to hop into it and try to find some information.
Introduction¶
NAOMI and NAOMI2 platforms are based on the SH4 chipset, specifically running on little endian mode (this is important to read the correct address), using this knowledge we can check and verify the correct “base address” for the cartridges.
One cannot take the game as the start of the process, this is because the BIOS itself is the one that enables and sets up the I/O ports, reads the interrupt table and also verifies the cartridge quickly, the reversing of the BIOS is actually another process which will also be documented later on.
The Game Cartridge¶
The beggining of the game (real address 0x0) contains on the first 0x130 bytes (304 characters) the “Game Header” which is used to verify the platform, development company and the different regions for the game, this is probably not only for language but also for hidden levels and could be used even for characters.
SEGA usually puts the last 3 region headers as SAMPLE, other companies tend just to add the game name without a specific named region, SEGA was really descriptive on the zones, some companies didn’t care much (this was specially true if the game had only one language)
An example of a SEGA game header is:
Ricksons-MacBook-Air:NAOMI TheCatThatHacks$ hexdump -C CosmicSmash.bin | head -n 30
00000000 4e 41 4f 4d 49 20 20 20 20 20 20 20 20 20 20 20 |NAOMI |
00000010 53 45 47 41 20 43 4f 52 50 4f 52 41 54 49 4f 4e |SEGA CORPORATION|
00000020 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000030 43 4f 53 4d 49 43 20 53 4d 41 53 48 20 49 4e 20 |COSMIC SMASH IN |
00000040 4a 41 50 41 4e 20 20 20 20 20 20 20 20 20 20 20 |JAPAN |
00000050 43 4f 53 4d 49 43 20 53 4d 41 53 48 20 49 4e 20 |COSMIC SMASH IN |
00000060 55 53 41 20 20 20 20 20 20 20 20 20 20 20 20 20 |USA |
00000070 43 4f 53 4d 49 43 20 53 4d 41 53 48 20 49 4e 20 |COSMIC SMASH IN |
00000080 45 58 50 4f 52 54 20 20 20 20 20 20 20 20 20 20 |EXPORT |
00000090 43 4f 53 4d 49 43 20 53 4d 41 53 48 20 49 4e 20 |COSMIC SMASH IN |
000000a0 4b 4f 52 45 41 20 20 20 20 20 20 20 20 20 20 20 |KOREA |
000000b0 43 4f 53 4d 49 43 20 53 4d 41 53 48 20 49 4e 20 |COSMIC SMASH IN |
000000c0 41 55 53 54 52 41 4c 49 41 20 20 20 20 20 20 20 |AUSTRALIA |
000000d0 53 41 4d 50 4c 45 20 20 20 20 20 20 20 20 20 20 |SAMPLE |
000000e0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
000000f0 53 41 4d 50 4c 45 20 20 20 20 20 20 20 20 20 20 |SAMPLE |
00000100 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000110 53 41 4d 50 4c 45 20 20 20 20 20 20 20 20 20 20 |SAMPLE |
00000120 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
An example of a game that has no “difference” on the regions looks like:
Ricksons-MacBook-Air:NAOMI TheCatThatHacks$ hexdump -C ZeroGunner2.bin | head -n 30
00000000 4e 41 4f 4d 49 20 20 20 20 20 20 20 20 20 20 20 |NAOMI |
00000010 50 53 49 4b 59 4f 20 43 4f 2e 2c 4c 54 44 2e 20 |PSIKYO CO.,LTD. |
00000020 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000030 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
00000040 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000050 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
00000060 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000070 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
00000080 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000090 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
000000a0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
000000b0 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
000000c0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
000000d0 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
000000e0 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
000000f0 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
00000100 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000110 5a 45 52 4f 20 47 55 4e 4e 45 52 20 32 20 20 20 |ZERO GUNNER 2 |
00000120 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
From here we can infer:
- First 16 characters (0x10) are reserved for the “platform” (always NAOMI)
- Next we have 32 characters (0x20) for the developer of the game
- From there we have 8 times slots of 32 characters (0x20) for the different regions
Size | Description |
---|---|
0x10 | Plaform |
0x20 | Dev Company |
0x20 | Region 1 |
0x20 | Region 2 |
0x20 | Region 3 |
0x20 | Region 4 |
0x20 | Region 5 |
0x20 | Region 6 |
0x20 | Region 7 |
0x20 | Region 8 |
Let’s analyze the now “known” structure of the information, as we are guessing we shall call the function DSPGetAJob, in honor of e-begger DarkSydePhil and his 2,000 USD “cat reveal” and other scams.
from ctypes import *
import struct
import os, sys
from pprint import pformat
# This is a class to describe and apply into the beggining of the game the NAOMI game header
class DSPGetAJob(LittleEndianStructure):
"""The Games and even the NetDimm software for network boot have the same structure
|Platform|Developer|Title Region 1|Title Region 2|...|Title Region 8|
So we create that as a class and apply it, since is a lazy way of doing it, we name it
based on a scrub like DSP.
For more details on the structure read the previous block, for more details on DSP .. nothing I could do! *SNORT*
"At least I'm not a 2,000 USD cat" --Rickson <TheCatThatHacks>
"""
_pack_ = 1
_fields_ = [('platform', c_char * 0x10),
('development company', c_char * 0x20),
('region 1', c_char * 0x20),
('region 2', c_char * 0x20),
('region 3', c_char * 0x20),
('region 4', c_char * 0x20),
('region 5', c_char * 0x20),
('region 6', c_char * 0x20),
('region 7', c_char * 0x20),
('region 8', c_char * 0x20)]
def __init__(self, file):
if file.readinto(self) != sizeof(self):
raise EOFError("Not enough bytes in buffer to parse header, nothing I could do d00d!!")
def dict_export(self):
d = dict()
for (varkey, vartype) in self._fields_:
d[varkey] = getattr(self, varkey).rstrip() # We use rstrip to remove the spaces, looks nicer
return d
def __repr__(self):
d = self.dict_export()
return pformat(d, indent=1, width=64)
# Now we run the stuff to show the results
print("[ - ] Opening file CosmicSmash.bin")
filer = open("CosmicSmash.bin", "rb")
print("[ - ] File open, extracting header d00000d *snort*")
snort = DSPGetAJob(filer)
print("[ + ] Header information is:\n")
print(snort)
print("\n[ - ] Not enough contributions d0000d finishing")
Well that was easy! So now let’s analyze the next set of bytes, from 0x130 to 0x500:
nahual@Neuromancer ~/projects/NAOMI_local $ hexdump -C -s 0x130 -n 0x3e0 -v CosmicSmash.bin
00000130 d0 07 0c 1c 42 43 48 42 01 00 00 00 00 00 00 00 |....BCHB........|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 |................|
00000160 00 00 04 00 2b c8 69 e7 04 00 c3 e0 b6 43 04 00 |....+.i......C..|
00000170 96 c8 66 f7 04 00 60 2e bf 4c 04 00 81 bb 26 7e |..f...`..L....&~|
00000180 04 00 a8 b3 ea f2 04 00 c5 05 84 a0 04 00 13 9e |................|
00000190 35 75 ff ff 00 00 00 00 ff ff 00 00 00 00 ff ff |5u..............|
000001a0 00 00 00 00 ff ff 00 00 00 00 ff ff 00 00 00 00 |................|
000001b0 ff ff 00 00 00 00 ff ff 00 00 00 00 ff ff 00 00 |................|
000001c0 00 00 ff ff 00 00 00 00 ff ff 00 00 00 00 ff ff |................|
000001d0 00 00 00 00 ff ff 00 00 00 00 ff ff 00 00 00 00 |................|
000001e0 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
000001f0 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000200 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000210 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000220 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000230 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000240 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000250 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000260 43 52 45 44 49 54 20 54 4f 20 53 54 41 52 54 20 |CREDIT TO START |
00000270 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000280 43 52 45 44 49 54 20 54 4f 20 43 4f 4e 54 49 4e |CREDIT TO CONTIN|
00000290 55 45 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |UE |
000002a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000310 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000360 00 00 02 00 00 00 02 0c 00 00 10 00 00 00 15 00 |................|
00000370 00 00 40 0c 00 00 02 00 00 00 19 00 00 00 48 0c |..@...........H.|
00000380 00 00 02 00 ff ff ff ff 00 00 00 00 00 00 00 00 |................|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003c0 00 00 12 00 00 00 02 0c 00 00 03 00 ff ff ff ff |................|
000003d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 00 02 0c 00 00 02 0c ff 00 00 01 00 ff ff ff |................|
00000430 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000440 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000450 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000460 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000470 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000480 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000490 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004b0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004c0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004d0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004e0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004f0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000500 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000510
nahual@Neuromancer ~/projects/NAOMI_local $
So let’s compare with another binary, in this case ZeroGunner.bin:
nahual@Neuromancer ~/projects/NAOMI_local $ hexdump -C -s 0x130 -n 0x3e0 -v ZeroGunner2.bin
00000130 d1 07 04 01 42 44 43 30 01 00 00 00 00 00 00 00 |....BDC0........|
00000140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 |................|
00000160 00 00 04 00 5f 9d 61 df 04 00 17 80 2e d2 04 00 |...._.a.........|
00000170 3d 8b 46 48 04 00 d0 b5 0e d9 04 00 53 be 0c b7 |=.FH........S...|
00000180 04 00 fe a5 57 75 04 00 55 09 73 7c 04 00 02 94 |....Wu..U.s|....|
00000190 9d 39 04 00 13 97 45 47 04 00 4b 09 14 3f ff ff |.9....EG..K..?..|
000001a0 00 00 11 11 ff ff 00 00 11 11 ff ff 00 00 11 11 |................|
000001b0 ff ff 00 00 11 11 ff ff 00 00 11 11 ff ff 00 00 |................|
000001c0 11 11 ff ff 00 00 11 11 ff ff 00 00 11 11 ff ff |................|
000001d0 bf a6 d4 df ff ff 34 12 76 98 ff ff 78 56 32 54 |......4.v...xV2T|
000001e0 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
000001f0 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000200 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000210 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000220 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000230 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000240 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000250 00 02 00 01 00 00 00 00 01 01 01 01 01 01 01 01 |................|
00000260 43 52 45 44 49 54 20 54 4f 20 53 54 41 52 54 20 |CREDIT TO START |
00000270 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 | |
00000280 43 52 45 44 49 54 20 54 4f 20 43 4f 4e 54 49 4e |CREDIT TO CONTIN|
00000290 55 45 20 20 20 20 20 20 20 20 20 20 20 20 20 20 |UE |
000002a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000002f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000310 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000360 00 00 00 00 00 00 02 0c 00 00 0e 00 00 00 0e 00 |................|
00000370 00 00 10 0c 00 00 10 00 00 00 1e 00 00 00 20 0c |.............. .|
00000380 00 00 10 00 00 00 2e 00 00 00 30 0c 60 54 00 00 |..........0.`T..|
00000390 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003c0 00 00 00 00 00 00 02 0c 00 00 0e 00 00 00 0e 00 |................|
000003d0 00 00 10 0c 00 00 10 00 00 00 1e 00 00 00 20 0c |.............. .|
000003e0 00 00 10 00 00 00 2e 00 00 00 30 0c 60 54 00 00 |..........0.`T..|
000003f0 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 10 02 0c 04 10 02 0c ff 02 00 01 00 ff ff ff |................|
00000430 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000440 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000450 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000460 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000470 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000480 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000490 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004a0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004b0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004c0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004d0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004e0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
000004f0 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000500 42 60 01 77 54 62 03 63 14 73 6c 33 04 76 22 23 |B`.wTb.c.sl3.v"#|
00000510
nahual@Neuromancer ~/projects/NAOMI_local $
From this quick glance we can see large patterns between the 2 binaries, so let’s actually create a quick script to read every 4 bytes and check the difference, why 4 bytes? we are just trying to “guess” if there are any address or any different information
import binascii
import struct
with open("CosmicSmash.bin", "rb") as cosmicsmash:
with open("ZeroGunner2.bin", "rb") as zerogunner2:
print("[ + ] Comparing CosmicSmash.bin to ZeroGunner2.bin from 0x130 to 0x500")
x = 0x130
while x < 0x500:
cosmicsmash.seek(x)
zerogunner2.seek(x)
# This is not pythonic at all .. I have to find an elegant way of doing this tbh :/
cosmicsmash_content = struct.unpack('L', cosmicsmash.read(4))[0]
#cosmicsmash.seek(4,1)
zerogunner2_content = struct.unpack('L', zerogunner2.read(4))[0]
#zerogunner2.seek(4,1)
if cosmicsmash_content != zerogunner2_content:
print("[ - ] Address %#x :: CS -> 0x%.8x == ZG -> 0x%.8x" % (x, cosmicsmash_content, zerogunner2_content))
x += 4
print("[ + ] DONE")
So we can see at 0x360 to 0x390 some RAM addresses actually come up, so let’s actually reach that part and see how it looks on each binary:
nahual@Neuromancer ~/projects/NAOMI_local $ hexdump -C -s 0x360 -n 0x100 -v ZeroGunner2.bin
00000360 00 00 00 00 00 00 02 0c 00 00 0e 00 00 00 0e 00 |................|
00000370 00 00 10 0c 00 00 10 00 00 00 1e 00 00 00 20 0c |.............. .|
00000380 00 00 10 00 00 00 2e 00 00 00 30 0c 60 54 00 00 |..........0.`T..|
00000390 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003c0 00 00 00 00 00 00 02 0c 00 00 0e 00 00 00 0e 00 |................|
000003d0 00 00 10 0c 00 00 10 00 00 00 1e 00 00 00 20 0c |.............. .|
000003e0 00 00 10 00 00 00 2e 00 00 00 30 0c 60 54 00 00 |..........0.`T..|
000003f0 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 10 02 0c 04 10 02 0c ff 02 00 01 00 ff ff ff |................|
00000430 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000440 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000450 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000460
nahual@Neuromancer ~/projects/NAOMI_local $ hexdump -C -s 0x360 -n 0x100 -v CosmicSmash.bin
00000360 00 00 02 00 00 00 02 0c 00 00 10 00 00 00 15 00 |................|
00000370 00 00 40 0c 00 00 02 00 00 00 19 00 00 00 48 0c |..@...........H.|
00000380 00 00 02 00 ff ff ff ff 00 00 00 00 00 00 00 00 |................|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003c0 00 00 12 00 00 00 02 0c 00 00 03 00 ff ff ff ff |................|
000003d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000003f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000400 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000410 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000420 00 00 02 0c 00 00 02 0c ff 00 00 01 00 ff ff ff |................|
00000430 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000440 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000450 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
00000460
nahual@Neuromancer ~/projects/NAOMI_local $
Address starting on 0x3c0 contains also another set of addresses, ZeroGunner contain almost the same, CosmicSmash doesn’t so more analysis will be done with other games (I will gather a few more games and analyze, otehr exercises are left to the reader)
import binascii
import struct
with open("CosmicSmash.bin", "rb") as cosmicsmash:
with open("ZeroGunner2.bin", "rb") as zerogunner2:
print("[ + ] Comparing CosmicSmash.bin to ZeroGunner2.bin from 0x360 to 0x500")
x = 0x130
while x < 0x260:
cosmicsmash.seek(x)
zerogunner2.seek(x)
# This is not pythonic at all .. I have to find an elegant way of doing this tbh :/
cosmicsmash_content = struct.unpack('L', cosmicsmash.read(4))[0]
#cosmicsmash.seek(4,1)
zerogunner2_content = struct.unpack('L', zerogunner2.read(4))[0]
#zerogunner2.seek(4,1)
if cosmicsmash_content != zerogunner2_content:
print("[ - ] Address %#x :: CS -> 0x%.8x == ZG -> 0x%.8x" % (x, cosmicsmash_content, zerogunner2_content))
x += 4
print("[ + ] DONE")
#!/usr/bin/env python
#
import binascii
import struct
with open("CosmicSmash.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
bin_offset = struct.unpack('i', binary_file.read(4))
binary_file.seek(4, 1)
bin_size = struct.unpack('i', binary_file.read(4))
binary_file.seek(4, 1)
bin_ram = struct.unpack('i', binary_file.read(4))
# bin_length = binary_file.read(4)
# print(bin_offset[0])
print("[ + ] Binary game start offset from ROM: %#x RAM Address is %#x Game binary size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
print("[ - ] Moving to offset %#x" % bin_offset[0])
binary_file.seek(bin_offset[0])
print("[ - ] Reading %d (%#x) bytes" % (bin_size[0], bin_size[0]))
game_data = binary_file.read(bin_size[0])
# game_data = binary_file.read(2424832)
# print binascii.hexlify(game_data)
game_file = open("CosmicSmashMainLoopGame.bin", "wb")
game_file.write(game_data)
game_file.close()
print("[ - ] Getting Entry point from 0x420")
binary_file.seek(0)
binary_file.seek(0x420)
game_entry_point_addr = struct.unpack('i', binary_file.read(4))
print("[ + ] Game entry address is %d (%#x)" % (game_entry_point_addr[0], game_entry_point_addr[0]))
import binascii
import struct
with open("FW_Netdimm_402.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
bin_offset = struct.unpack('i', binary_file.read(4))
bin_size = struct.unpack('i', binary_file.read(4))
bin_ram = struct.unpack('i', binary_file.read(4))
# bin_length = binary_file.read(4)
# print(bin_offset[0])
print("[ + ] Binary game start offset from ROM: %#x RAM Address is %#x Game binary size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
print("[ - ] Moving to offset %#x" % (bin_offset[0] + 0x500))
binary_file.seek(bin_offset[0])
print("[ - ] Reading %d (%#x) bytes" % (bin_size[0], bin_size[0]))
game_data = binary_file.read(bin_size[0])
# game_data = binary_file.read(2424832)
# print binascii.hexlify(game_data)
game_file = open("CosmicSmashMainLoopGame.bin", "wb")
game_file.write(game_data)
game_file.close()
print("[ - ] Getting Entry point from 0x420")
binary_file.seek(0)
binary_file.seek(0x420)
game_entry_point_addr = struct.unpack('i', binary_file.read(4))
print("[ + ] Game entry address is %d (%#x)" % (game_entry_point_addr[0], game_entry_point_addr[0]))
#!/usr/bin/env python
#
# Running on the NetDimm
import binascii
import struct
with open("BIOS/KF Private/317hackedupdatergd.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
bin_offset = struct.unpack('i', binary_file.read(4))
bin_size = struct.unpack('i', binary_file.read(4))
bin_ram = struct.unpack('i', binary_file.read(4))
# bin_length = binary_file.read(4)
# print(bin_offset[0])
print("[ + ] Binary game start offset from ROM: %#x RAM Address is %#x Game binary size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
print("[ - ] Moving to offset %#x" % bin_offset[0])
binary_file.seek(bin_offset[0])
print("[ - ] Reading %d (%#x) bytes" % (bin_size[0], bin_size[0]))
game_data = binary_file.read(bin_size[0])
# game_data = binary_file.read(2424832)
# print binascii.hexlify(game_data)
game_file = open("CosmicSmashMainLoopGame.bin", "wb")
game_file.write(game_data)
game_file.close()
print("[ - ] Getting Entry point from 0x420")
binary_file.seek(0)
binary_file.seek(0x420)
game_entry_point_addr = struct.unpack('i', binary_file.read(4))
print("[ + ] Game entry address is %d (%#x)" % (game_entry_point_addr[0], game_entry_point_addr[0]))
Let’s check the next data:
00000360 00 00 02 00 00 00 02 0c 00 00 10 00 00 00 15 00 |................|
00000370 00 00 40 0c 00 00 02 00 00 00 19 00 00 00 48 0c |..@...........H.|
00000380 00 00 02 00 ff ff ff ff 00 00 00 00 00 00 00 00 |................|
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
We see the 0x00020000 <-> 0x0c200000 <-> 0x00010000 (Our main loop data) but also see the same kind of data structure right after, so let’s make a quick loop, on a quick glance we can see that the 0xffffffff is on the offset so we should be able to read until 0xffffffff
import binascii
import struct
print("[ + ] CosmisSmash")
with open("CosmicSmash.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
while (1):
bin_offset = struct.unpack('i', binary_file.read(4))
bin_ram = struct.unpack('i', binary_file.read(4))
bin_size = struct.unpack('i', binary_file.read(4))
if bin_size[0] == 0 or bin_offset[0] < 0:
break
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
binary_file.seek(0x420)
entry_addie = struct.unpack('i', binary_file.read(4))
print("Entry address is %#x" % entry_addie)
print("[ + ] ZeroGunner2")
with open("ZeroGunner2.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
while (1):
bin_offset = struct.unpack('i', binary_file.read(4))
bin_ram = struct.unpack('i', binary_file.read(4))
bin_size = struct.unpack('i', binary_file.read(4))
if bin_size[0] == 0 or bin_offset[0] < 0:
break
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
binary_file.seek(0x420)
entry_addie = struct.unpack('i', binary_file.read(4))
print("Entry address is %#x" % entry_addie)
print("[ + ] MarvelVsCapcom2.bin")
with open("MarvelVsCapcom2.bin", "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(0x360)
while (1):
bin_offset = struct.unpack('i', binary_file.read(4))
bin_ram = struct.unpack('i', binary_file.read(4))
bin_size = struct.unpack('i', binary_file.read(4))
if bin_size[0] == 0 or bin_offset[0] < 0:
break
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (bin_offset[0],bin_ram[0], bin_size[0]))
binary_file.seek(0x420)
entry_addie = struct.unpack('i', binary_file.read(4))
print("Entry address is %#x" % entry_addie)
import binascii
import struct
from pprint import pformat
class gameEntries:
"""contains the address entries for the game memory segments and entry point for the game,
Exceptions are caught with the "I did everything right, and did nothing wrong" license ala DSP
"""
def __init__(self):
"""Init the variable to be able to read all the entries into a dictionary to then just parse the dictionary"""
self.entries = []
self.PC = None
self.PC2 = None #Feeling like I don't know the purpose, might delete later *insert meme pose*
def readGameLoops(self, filename=None, loop_address=0x360):
"""Reads the entries and returns an array with the dictionary of them"""
with open(filename, "rb") as binary_file:
binary_file.seek(0)
binary_file.seek(loop_address)
while (1):
# More .. "pythonic" (Aka looks cool is kinda unreadable)
(bin_offset, bin_ram, bin_size) = struct.unpack('iii', binary_file.read(12))
if bin_size == 0 or bin_offset < 0:
break
self.entries.append({'ROM_Offset':bin_offset, 'RAM_Address':bin_ram, 'Segment_Size':bin_size})
#print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (bin_offset,bin_ram, bin_size))
"""We get the entry address (there are 2 pointers, Maybe PC and another? will read both)"""
if self.PC == None:
binary_file.seek(0x420)
(self.PC, self.PC2) = struct.unpack('ii', binary_file.read(8))
return self.entries
def getEntryPoint(self):
"""Returns current value of self.PC if not set it will return None"""
return self.PC
def getEntryPoint2(self):
return self.PC2
def cleanEntries(self):
"""Clean the entries array to allow to have either another binary or have another offset"""
del self.entries[:]
self.PC = self.PC2 = None
return True
def __repr__(self):
return pformat(self.entries, indent=4, width=1)
def _AddSegment(name, base_address, data):
"""Add a segment to the IDB with some basic options set for convenience."""
s = idaapi.segment_t()
s.startEA = base_address
s.endEA = base_address + len(data)
s.bitness = 1 # 32-bit
s.align = idaapi.saRelByte
s.comb = idaapi.scPub
s.sel = idaapi.setup_selector(0)
idaapi.add_segm_ex(s, name, None, idaapi.ADDSEG_NOSREG | idaapi.ADDSEG_OR_DIE)
idaapi.mem2base(data, base_address)
## UGH debug :P
print("[ + ] Analyzing ZeroGunner2.bin first")
og = gameEntries()
entries = og.readGameLoops('ZeroGunner2.bin')
for entry in entries:
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (entry['ROM_Offset'], entry['RAM_Address'], entry['Segment_Size']))
print("-- Entry point is %#x" % og.getEntryPoint())
print("-- Entry point 2 is %#x" % og.getEntryPoint2())
og.cleanEntries()
entries = og.readGameLoops(filename='ZeroGunner2.bin', loop_address=0x3c0)
for entry in entries:
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (entry['ROM_Offset'], entry['RAM_Address'], entry['Segment_Size']))
og.cleanEntries()
print('-----------------')
print("[ + ] Analyzing CosmicSmash.bin now")
entries = og.readGameLoops('CosmicSmash.bin')
for entry in entries:
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (entry['ROM_Offset'], entry['RAM_Address'], entry['Segment_Size']))
print("-- Entry point is %#x" % og.getEntryPoint())
print("-- Entry point 2 is %#x" % og.getEntryPoint2())
og.cleanEntries()
entries = og.readGameLoops(filename='CosmicSmash.bin', loop_address=0x3c0)
for entry in entries:
print("offset from ROM: %#x RAM Address is %#x Loop/Segment size: %#x" % (entry['ROM_Offset'], entry['RAM_Address'], entry['Segment_Size']))
import binascii
import struct
with open("BIOS/KF Private/317hackedupdatergd.bin", "rb") as cosmicsmash:
with open("BIOS/KF Private/FW_Dimm317.bin", "rb") as zerogunner2:
print("[ + ] Comparing CosmicSmash.bin to ZeroGunner2.bin from 0x130 to 0x500")
x = 0x360
while x < 0x500:
cosmicsmash.seek(x)
zerogunner2.seek(x)
# This is not pythonic at all .. I have to find an elegant way of doing this tbh :/
cosmicsmash_content = struct.unpack('L', cosmicsmash.read(4))[0]
#cosmicsmash.seek(4,1)
zerogunner2_content = struct.unpack('L', zerogunner2.read(4))[0]
#zerogunner2.seek(4,1)
#print("[ - ] Address %#x :: CS -> 0x%.8x == ZG -> 0x%.8x" % (x, cosmicsmash_content, zerogunner2_content))
x += 4
print("[ + ] DONE")
og.cleanEntries()
og.readGameLoops('BIOS/KF Private/317hackedupdatergd.bin')
print("-- Entry point is %#x" % og.getEntryPoint())
og.readGameLoops(filename='BIOS/KF Private/317hackedupdatergd.bin', loop_address=0x3c0)
THIS IS A FILE WHICH CHANGES EVERYDAY, FEEL FREE TO GIT PULL CONSTANTLY¶