mirror of
https://github.com/holub/mame
synced 2025-05-20 20:58:51 +03:00
Updated Naomi's M2/M3 decryption. On the fly decryption of Virtua Striker III is now possible. [Andreas Naive]
This commit is contained in:
parent
2ae85ae98b
commit
c8bbb4ab25
@ -237,43 +237,43 @@ struct _naomibd_state
|
||||
static const naomibd_config_table naomibd_translate_tbl[] =
|
||||
{
|
||||
// games where on-the-fly decryption works (many of these are fully playable in MAME, just slow)
|
||||
{ "18wheelr", 0, 0x7cf54, { 0x1502, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "alpilota", 0, 0x70e41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "alpiltdx", 0, 0x70e41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "18wheelr", 0, 0x07cf54, { 0x1502, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "alpilota", 0, 0x070e41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "alpiltdx", 0, 0x070e41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "capsnk", 0, 0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "capsnka", 0, 0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "crackndj", 0, 0xc2347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "crzytaxi", 0, 0xd2f45, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "csmash", 1, 0x03347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "csmasho", 1, 0x03347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "cspike", 0, 0xe2010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "deathcox", 0, 0xb64d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "dybb99", 0, 0x48a01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "f355twin", 0, 0x6efd4, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "f355twn2", 0, 0x666c6, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "ggram2", 0, 0x74a61, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "gundmct", 0, 0xe8010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "gwing2", 0, 0xb25d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "hmgeo", 0, 0x38510, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "jambo", 0, 0xfab95, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "otrigger", 0, 0xfea94, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pjustic", 0, 0x725d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pstone", 0, 0xe69c1, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pstone2", 0, 0xb8dc0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "puyoda", 0, 0xacd40, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "ringout", 0, 0xb1e40, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "samba", 0, 0xa8b5d, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "slasho", 0, 0xa66ca, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "smlg99", 0, 0x48a01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "spawn", 0, 0x78d01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "sstrkfgt", 0, 0x32303, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "suchie3", 0, 0x368e1, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "toyfight", 0, 0x2ca85, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "vtennis", 0, 0x3eb15, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "vonot", 0, 0x10715, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "wwfroyal",0, 0x627c3, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "zerogu2", 0, 0x7c010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "zombrvn", 0, 0x12b41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "crackndj", 0, 0x1c2347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "crzytaxi", 0, 0x0d2f45, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "csmash", 1, 0x103347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "csmasho", 1, 0x103347, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "cspike", 0, 0x0e2010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "deathcox", 0, 0x0b64d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "dybb99", 0, 0x048a01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "f355twin", 0, 0x06efd4, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "f355twn2", 0, 0x1666c6, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "ggram2", 0, 0x074a61, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "gundmct", 0, 0x0e8010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "gwing2", 0, 0x0b25d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "hmgeo", 0, 0x038510, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "jambo", 0, 0x0fab95, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "otrigger", 0, 0x0fea94, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pjustic", 0, 0x0725d0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pstone", 0, 0x0e69c1, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "pstone2", 0, 0x0b8dc0, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "puyoda", 0, 0x0acd40, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "ringout", 0, 0x0b1e40, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "samba", 0, 0x0a8b5d, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "slasho", 0, 0x1a66ca, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "smlg99", 0, 0x048a01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "spawn", 0, 0x078d01, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "sstrkfgt", 0, 0x132303, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "suchie3", 0, 0x0368e1, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "toyfight", 0, 0x02ca85, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "vtennis", 0, 0x03eb15, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "vonot", 0, 0x010715, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "wwfroyal",0, 0x1627c3, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "zerogu2", 0, 0x07c010, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "zombrvn", 0, 0x012b41, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
|
||||
// games where the encryption is stacked with the ASIC's compression
|
||||
{ "doa2", 0, -1, { -1, 0x500, 0, -1, 0x20504, 0x20000, -1, 0x40508, 0x40000, -1, 0x6050c, 0x60000, -1, 0x80510, 0x80000, // 0x8ad01, has compression
|
||||
@ -290,7 +290,7 @@ static const naomibd_config_table naomibd_translate_tbl[] =
|
||||
0x2924, 0, 0x080000, 0x3222, 0, 0x090000, 0x7954, 0, 0x0a0000, 0x5acd, 0, 0x0b0000,
|
||||
0xdd19, 0, 0x0c0000, 0x2428, 0, 0x0d0000, 0x3329, 0, 0x0e0000, 0x2142, 0, 0x0f0000,
|
||||
0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
{ "sgtetris", 0, -1, { 0x1234, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } }, // 0x8ae51, uses compression
|
||||
{ "sgtetris", 0, -1, { 0x1234, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } }, // 0x08ae51, uses compression
|
||||
// { "virnbao", 0, 0x68b58, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } }, // note: "virnba" set doesn't have protection
|
||||
// { "vs2_2k", 0, 0x88b08, { 0, 0, 0, 0xffffffff, 0xffffffff, 0xffffffff } },
|
||||
};
|
||||
@ -1158,7 +1158,7 @@ internal block-cipher. So, at a given step, the internal block cipher will outpu
|
||||
given plaintext word, and the remaining 2 to the next plaintext word.
|
||||
|
||||
The underlying block cipher consists of two 4-round Feistel Networks (FN): the first one takes the counter (16 bits),
|
||||
the game-key (20 bits) and the sequence-key (16 bits) and output a middle result (16 bits) which will act as another key
|
||||
the game-key (>=22 bits) and the sequence-key (16 bits) and output a middle result (16 bits) which will act as another key
|
||||
for the second one. The second FN will take the encrypted word (16 bits), the game-key, the sequence-key and the result
|
||||
from the first FN and will output the decrypted word (16 bits).
|
||||
|
||||
@ -1169,10 +1169,11 @@ The underlying block cipher has the same structure than the one used by the CPS-
|
||||
indeed, some of the used sboxes are exactly the same and appear in the same FN/round in both systems (this is not evident,
|
||||
as you need to apply a bitswapping and some XORs to the input & output of the sboxes to get the same values due). However,
|
||||
the key scheduling used by this implementation is much weaker than the CPS-2's one. Many s-boxes inputs are XORed with any
|
||||
key bit and, indeed, the cart-specific key is just 20-bits long.
|
||||
key bit.
|
||||
|
||||
Due to the small key-length, no sophisticated attacks are needed to recover the keys; a brute-force attack knowing just
|
||||
one or two (encrypted word-decrypted word) pairs suffice.
|
||||
some (encrypted word-decrypted word) pairs suffice. However, due to the weak key scheduling, it should be noted that some
|
||||
related keys can produce the same output bytes for some (short) input sequences.
|
||||
|
||||
The only difference in the decryption process between M2 and M3 is the initialization of the counter. In M3, the counter is
|
||||
always set to 0 at the beginning of the decryption while, in M2, the bits #1-#16 of the ciphertext's address are used
|
||||
@ -1183,11 +1184,12 @@ you could apply a fixed bitswapping and XOR to the keys and the decryption would
|
||||
accordingly the s-boxes' definitions. So the order of the bits in the keys is arbitrary, and the s-boxes values have been
|
||||
chosen so as to make the key for CAPSNK equal to 0.
|
||||
|
||||
It can be observed that some sboxes have incomplete tables (a 255 value indicate an unknown value). In most of the cases,
|
||||
they are apparently unused by the cipher (due to the weak key scheduling mentioned above). As of february/2010, the only
|
||||
s-box which have a incomplete table which could be begin used is the 4th s-box of the 1st round of the 2nd FN. It's
|
||||
incomplete because we haven't located any game using that part of the s-box till now, but definitively it could be being
|
||||
used by some still-not-analyzed carts.
|
||||
It can be observed that some sboxes have incomplete tables (a 255 value indicate an unknown value). It's unclear whether or
|
||||
not they are used by the cipher, as the bits of the recovered keys as of november/2010 show small randomness and big correlations,
|
||||
making possible that some unseen bits could make the decryption need those incomplete parts. Even if not, as of november/2010, there are two
|
||||
s-boxes which have an incomplete table which definitely could be being used by some carts: the 1st s-box of the 1st round of the 1st FN and
|
||||
the 4th s-box of the 1st round of the 2nd FN. They are incomplete because we haven't located any game using that part of the s-box
|
||||
till now, but definitively they could be being used by some still-not-analyzed carts.
|
||||
|
||||
When bit #1 of the heading control bits is set to 1, an additional decompression step seems to be carried out. As of
|
||||
february/2010, Deunan Knute has put some work on analyzing the decompression algorithm, but probably much more work will
|
||||
@ -1218,8 +1220,9 @@ static const struct sbox fn1_sboxes[4][4] =
|
||||
{
|
||||
{
|
||||
0,3,2,2,1,3,1,2,3,2,1,2,1,2,3,1,3,2,2,0,2,1,3,0,0,3,2,3,2,1,2,0,
|
||||
// unused
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
2,3,1,1,2,2,1,1,1,0,2,3,3,0,2,1,
|
||||
// potentially used, but we haven't located any game using it
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
},
|
||||
{3,4,5,7,-1,-1},
|
||||
{0,4}
|
||||
@ -1265,7 +1268,7 @@ static const struct sbox fn1_sboxes[4][4] =
|
||||
{
|
||||
{
|
||||
2,0,1,0,0,3,2,0,3,3,1,2,1,3,0,2,0,2,0,0,0,2,3,1,3,1,1,2,3,0,3,0,
|
||||
// unused
|
||||
// unused?
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
},
|
||||
{0,1,3,4,6,-1},
|
||||
@ -1312,7 +1315,7 @@ static const struct sbox fn1_sboxes[4][4] =
|
||||
{
|
||||
{
|
||||
0,3,0,2,1,2,0,0,1,1,0,0,3,1,1,0,0,3,0,0,2,3,3,2,3,1,2,0,0,2,3,0,
|
||||
// unused
|
||||
// unused?
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
},
|
||||
{0,2,4,6,7,-1},
|
||||
@ -1392,8 +1395,7 @@ static const struct sbox fn2_sboxes[4][4] =
|
||||
{
|
||||
{
|
||||
0,2,2,1,0,1,2,1,2,0,1,2,3,3,0,1,3,1,1,2,1,2,1,3,3,2,3,3,2,1,0,1,
|
||||
// unused
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
0,1,0,2,0,1,1,3,2,0,3,2,1,1,1,3,2,3,0,2,3,0,2,2,1,3,0,1,1,2,2,2,
|
||||
},
|
||||
{0,2,3,4,7,-1},
|
||||
{3,4}
|
||||
@ -1469,7 +1471,7 @@ static const struct sbox fn2_sboxes[4][4] =
|
||||
{
|
||||
{
|
||||
2,2,3,2,0,3,2,3,1,1,2,0,2,3,1,3,0,0,0,3,2,0,1,0,1,3,2,3,3,3,1,0,
|
||||
// unused
|
||||
// unused?
|
||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
|
||||
},
|
||||
{1,2,4,7,-1,-1},
|
||||
@ -1524,20 +1526,21 @@ static const struct sbox fn2_sboxes[4][4] =
|
||||
},
|
||||
};
|
||||
|
||||
static const int fn1_game_key_scheduling[30][2] =
|
||||
static const int fn1_game_key_scheduling[34][2] =
|
||||
{
|
||||
{1,29}, {1,71}, {1,81}, {2,4}, {2,54}, {3,8}, {4,56}, {4,73},
|
||||
{5,11}, {6,51}, {7,92}, {8,89}, {9,9}, {9,10}, {9,39}, {9,41},
|
||||
{9,58}, {9,59}, {9,86}, {10,90}, {11,6}, {12,64}, {13,49}, {14,44},
|
||||
{15,40}, {16,69}, {17,15}, {18,23}, {18,43}, {19,82},
|
||||
{1,29}, {1,71}, {2,4}, {2,54}, {3,8}, {4,56}, {4,73}, {5,11},
|
||||
{6,51}, {7,92}, {8,89}, {9,9}, {9,10}, {9,39}, {9,41}, {9,58},
|
||||
{9,59}, {9,86}, {10,90}, {11,6}, {12,64}, {13,49}, {14,44}, {15,40},
|
||||
{16,69}, {17,15}, {18,23}, {18,43}, {19,82}, {20,81}, {21,5}, {21,32},
|
||||
{21,61}, {21,66}
|
||||
};
|
||||
|
||||
static const int fn2_game_key_scheduling[27][2] =
|
||||
static const int fn2_game_key_scheduling[32][2] =
|
||||
{
|
||||
{0,0}, {1,3}, {1,35}, {2,11}, {3,20}, {4,22}, {5,23}, {6,29},
|
||||
{7,38}, {8,39}, {9,47}, {9,55}, {9,86}, {9,87}, {9,90}, {10,50},
|
||||
{10,53}, {11,57}, {12,59}, {13,61}, {13,64}, {14,63}, {15,67}, {16,72},
|
||||
{17,83}, {18,88}, {19,94},
|
||||
{0,0}, {1,3}, {2,11}, {3,20}, {4,22}, {5,23}, {6,29}, {7,38},
|
||||
{8,39}, {9,47}, {9,55}, {9,86}, {9,87}, {9,90}, {10,50}, {10,53},
|
||||
{11,57}, {12,59}, {13,61}, {13,64}, {14,63}, {15,67}, {16,72}, {17,83},
|
||||
{18,88}, {19,94}, {20,35}, {21,6}, {21,11}, {21,17}, {21,85}, {21,92}
|
||||
};
|
||||
|
||||
static const int fn1_sequence_key_scheduling[20][2] =
|
||||
@ -1601,7 +1604,7 @@ static UINT16 block_decrypt(UINT32 game_key, UINT16 sequence_key, UINT16 counter
|
||||
memset(fn1_subkeys,0,sizeof(UINT32)*4);
|
||||
memset(fn2_subkeys,0,sizeof(UINT32)*4);
|
||||
|
||||
for (j=0; j<30; ++j)
|
||||
for (j=0; j<34; ++j)
|
||||
{
|
||||
if (BIT(game_key, fn1_game_key_scheduling[j][0])!=0)
|
||||
{
|
||||
@ -1611,7 +1614,7 @@ static UINT16 block_decrypt(UINT32 game_key, UINT16 sequence_key, UINT16 counter
|
||||
}
|
||||
}
|
||||
|
||||
for (j=0; j<27; ++j)
|
||||
for (j=0; j<32; ++j)
|
||||
{
|
||||
if (BIT(game_key, fn2_game_key_scheduling[j][0])!=0)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user