The video game Skeleton Warriors for Sega Saturn uses a custom hash function to check for cheat codes entered at the pause screen. The function is equivalent to the Python code below.
After computation, the hash results are compared to a list of values associated with cheat codes. For example:
- Entering X, X, X, X, Y, Y, Y, Y on the controller produces the hash
00001555dddc7332, which is associated with a "show coordinates" effect. - Entering C, Right, A, Z, Y, B, A, Left, Left produces the hash
000dd8d3a498d510, which is associated with the "bouncing" effect.
These effects have been for decades, but there's one effect that's gone unreported. Its hash is 8f9d0b0ccffa2be3. I know what it does — it makes the game show hit boxes — but not what the button sequence is.
The hash doesn't look at all secure, but I've not been able to reverse this particular value yet. I tried brute force enumeration of all the input sequences up to 9 characters, but no luck there.
The codes tend to spell out words with the restricted alphabet (notice CRAZYBALL above — another is LUCCY LUCCY), and I was able to recover all of the already-known ones with a dictionary attack, but again, not the unknown one.
Is there a way to exploit the weakness of the algorithm to recover the plaintext? Is this algorithm an implementation of something that's already been studied?
I suspect that the target is 18 characters long, because the game converts the button presses to ASCII and stores them in memory as you enter them. There are 19 bytes allocated to that buffer.
letter_map = {
"l": 1, "r": 2, "u": 3, "d": 4,
"a": 5, "b": 6, "c": 7, "x": 8,
"y": 9, "z": 10,
}
def process_sequence(code_buttons):
buffer_01 = 0
buffer_02 = 0
for button in code_buttons:
index = letter_map[button]
for __ in range(4):
if (index & 1) != 0:
buffer_02 = buffer_02 ^ 0xA001
x = ((buffer_01 & 0x80000000) == 0) ^ 1
y = ((buffer_02 & 0x80000000) == 0) ^ 1
buffer_01 = (buffer_01 * 2 + y) & 0xFFFFFFFF
buffer_02 = (buffer_02 * 2 + x) & 0xFFFFFFFF
index = index >> 1
return (buffer_01 << 32) | buffer_02
Edit: The comments give answers that satisfy my question, but not the game! It also computes a third buffer_ value - I didn't think it mattered, because it doesn't for the other codes. It must apply to this one because of its length.
The third hash component is 08B25A87.
def cheat_code_helper(index_, cheat_buffer_03):
iVar1 = (cheat_buffer_03 & 0xFFFF) * (index_ & 0xFFFF)
if (((index_ >> 0x10) << 0x10) | (cheat_buffer_03 >> 0x10)) == 0:
return iVar1
return (
iVar1
+ (
(index_ & 0xFFFF) * (cheat_buffer_03 >> 0x10)
+ (cheat_buffer_03 & 0xFFFF) * (index_ >> 0x10)
)
* 0x10000
)
def process_sequence(code_buttons):
buffer_01 = 0
buffer_02 = 0
buffer_03 = 1
for button in code_buttons:
index = letter_map[button]
buffer_03 = (cheat_code_helper(index + 1, buffer_03) + buffer_02) & 0xFFFFFFFF
for __ in range(4):
if (index & 1) != 0:
buffer_02 = buffer_02 ^ 0xA001
x = ((buffer_01 & 0x80000000) == 0) ^ 1
y = ((buffer_02 & 0x80000000) == 0) ^ 1
buffer_01 = (buffer_01 * 2 + y) & 0xFFFFFFFF
buffer_02 = (buffer_02 * 2 + x) & 0xFFFFFFFF
index = index >> 1
return (buffer_01, buffer_02, buffer_03)
Another edit: The ASCII buffer is 19 bytes rather than 18.
And another: I fixed the Python implementation of the helper. The initial condition for buffer_03 is 1, not 0!