A Software Attack on Kabuki
As explained on part one, Kabuki presumably uses an internal memory where it keeps the keys used for program and data code decryption. We know this from the fact that whenever the cpu loses power input on pin 28 those memory contents are lost and the cpu stops being able to run games, forever.
But let's look for a minute at what's going on here...
If Kabuki uses an internal memory for storage then this storage must be of CMOS type, thanks to one of the manufacturer product catalogs (VTI) we already know that the Kabuki Z80 core is a CMOS part.
In theory bits of CMOS memory or registers must default to an specific value after a power loss has occurred. Normally this should be 0, but let's play and get wild by assuming internal inverters are at work and the memory output default value could also be 1.
In either case, and unless the designers of Kabuki randomized ram/register contents as part of a paranoid approach, then we could be in safe grounds when assuming that Kabuki's ram must get back a known state after a power loss has occurred.
Could we try running code encrypted with keys whose bit values are all 0's or 1's? Could recovering a dead Kabuki game be so simple just requiring the burning a new set of roms encrypted with those default values?
Let's look into this.
Planning the Attack
First of all, if we want to run code encrypted with different keys then we must know how to encrypt new valid code. Thanks to Icer Addis and the Mame team we know how to decode code for games using Kabuki but as expected, not a word about encoding code as it has never been a need to emulate existing games.
Secondly, we will need a piece of Z80 code that allows us to validate the experiment. Creating a "Hello World" piece of code that works on Capcom hardware is a project on its own.
Writing a Hello World
|Pang rom "Hello World" code running under Mame|
Digging around Pang and using an standard disassembler I produced the following code that creates a functional Hello World test, the unencrypted code works perfectly under Mame by replacing rom number 6:
Encrypting Kabuki compatible code
Now the harder part...
We need a set of tools to allow us encrypt our program, I won't get into much detail but writing the encoder required hard work (thanks to ASR and DR for the help) and I ended creating the following programs:
Kabuki Encode - Encrypts a file with a set of keys. Generates separated encrypted opcodes and data as required by Kabuki.
Kabuki Decode - Decrypts a Kabuki encrypted file using a set of keys provided.
Masker - This tool mixes encrypted opcodes and data generated by Kabuki Encode, takes a mask file as input.
Use this link to download a zip file including source code, binaries compiled for Mac OS, and also the Hello World program shown above.
All three programs present a help output when ran with no arguments:
Kabuki Encode by Eduardo Cruz http://arcadehacker.blogspot.com
usage: ./kabuki_encode <inputfile_opcode> <inputfile_data> <outputfile_opcodes> <outputfile_data> <baseaddress> <lenght> <swapkey1> <swapkey2> <addresskey> <xorkey> Optional: <numberofbanks>
./kabuki_encode helloworld.bin helloworld.bin helloworld.bin_opcodes helloworld.bin_data 0x0000 0x30 0x00000000 0x00000000 0x0000 0x00 0
Wrote 48 (0x30) bytes
As you can observe in the example above all keys are intentionally set to 0, this is because we want to test if Kabuki's default keys after losing power default to 0.
Kabuki Encode has created for us two different files "helloworld.bin_opcodes" and "helloworld.bin_data". Kabuki handles opcodes and data bytes differently and this requires different encryption for both as explained on my previous post.
Next is merging the resulting opcode and data files.
This is when the tool Masker becomes handy, it helps us merge the files generated by Kabuki Encode into a single file, but before being able to use it we need to create a mask file that will tell Masker how the final resulting file should be combined.
|Mask file for Hello World|
Notice the file is full of FF and AA bytes, FF tells Masker to place on that position a byte from the opcode encrypted file, and AA tells it to place a byte from the data file instead. A mask file needs to be created manually and this requires that you know your program, you need to differentiate opcodes from data bytes (arguments of opcodes are data bytes too).
Let's run Masker:
./masker helloworld.bin.mask helloworld.bin_opcodes helloworld.bin_data helloworld.bin.final
Wrote 48 (0x30) bytes
Finally we have our final encrypted code "helloworld.bin.final". This is now ready to burn into a 27256 type eprom and off to test it on a real Pang board.
Hello World, the results
With a great amount of excitement I tested this new code encrypted with encryption keys set to 0 but I was soon faced with a reality check, it didn't work... and the same went for code tested with all encryption bits set to 1. A total fail.
Back to the drawing board
What could be going on here? Our encrypted code is totally valid and Mame is able to run it. Could there be differences in the real hardware vs the Mame emulation? We are clearly missing something here...
Time to go back to hardware and plug the logic analyzer, we need to understand exactly what's going on with Kabuki. This time we will use the original roms of the game Pang to understand the running differences of the real hardware in encrypted (pin 28 high) and non encrypted mode (pin 28 low).
Using the method described before and with the help of our new tools I created a Pang romset encrypted with all bits set to 0 and ran it.
|Pang code running encrypted on the left and unencrypted on the right|
The Z80 code shown above represents a timeline of execution, the program starts at 0x58 and first takes the order to call/jump to a different code location (0x13A), before doing so it stores the address of the byte that it was supposed to execute next if there wasn't such call/jump order (0x5B).
After that it continues at the call address 0x13A and soon encounters an order to return back at 0x13E. The program recovers the address where it should execute next from the stack: 0x5B.
In the case of Kabuki running unencrypted (right side) the address recovered, 0x5B, is interpreted correctly as 0x5B. Kabuki running encrypted mode (left side) also reads 0x5B but it interprets wrongly as 0xAD therefore returning code execution to a wrong place.
Bingo! As identified with the help of a logic analyzer we can see that our suicided Kabuki (left side) is trying to decode memory ram reads and not just rom reads... What this means is that any bytes stored in memory during execution are handled as encrypted bytes when read back. This causes all programs that use memory to malfunction.
The good: This finding explains why our Hello World didn't work and confirms that indeed Kabuki defaults to 0 all encryption keys when suicided.
The bad: We have hit ourselves with a massive stop wall... There's a new element at play not taken into account before, Kabuki must feature an additional configuration setting inside its memory that decides when to decode and not to decode byte reads, this setting probably sets which address spaces must be ignored eg: do not decode anything from 0xC000 to 0xFFFF (Pang's ram space).
When Kabuki suicides besides losing the encryption keys it also loses this address decoding setting and leaves the cpu practically unusable. With this discovery our dream to recover dead Kabuki games with new rom code is vanished, but we have just lost a battle, not the entire war.
This is all for this week, thanks for reading and stay tuned for my next post in the Kabuki series: A better Pang desuicide.