Old Vault [RE]
Reverse engineering PS2 game program to find correct password.
Challenge Description
I found this old cd rom in the basement, my father said its a vault he couldn’t open and contains a treasure of some kind.
zip pass: wgmy2022
Solution
Unzipping the file gives us two other files: vault.bin
and vault.cue
. Because I’m using a mac, I followed this to convert the two files into an ISO file to mount it and see what’s inside.
bchunk -w vault.bin vault.cue vault
This should produce vault.iso
. After mounting it, there are two files PROGRAM.EXE
and SYSTEM.CNF
.
I ran a quick strings
command on the exe and found the keyword “Playstation” from one of the strings and immediately guessed that this is a PSX program. I prepped Ghidra by installing this plugin. It required Ghidra 10.1.5, so I also installed that specific version.
Opening the EXE now with Ghidra, I navigated to start
and started scanning through the functions it calls. They all did something, but doesn’t seem like much, except for the last call to a function at 0x80010020
, which has a few while (true)
loops (perhaps the game cycle?). I renamed this as main
.
The setup
function just has a lot of calls to SDK functions which were named clearly (with “init” or “setup”), hence named setup
. Similary, draw_main
had calls to DrawPrim
and some user-defined strings, which I later found was drawn onto the screen.
take_input
and proc_main
are the two functions that we really need to look into. First take_input
, which reads the input from PadRead(0)
, which upon googling, is available in the PS1 libraries. See here: https://www.retroreversing.com/ps1-libs.
For the value mapping of each key, I just googled the function name (PadRead
) with its library (LIBETC.h
) and found this file on github. Combining that with the value of _code
, we get the following mapping of _code
to pad key:
keymap = {
0: "LUp",
1: "LDown",
2: "LLeft",
3: "LRight",
4: "RUp",
5: "RRight",
6: "RDown",
7: "RLeft",
}
There are a bunch of code after what is shown in the screenshot, but after wasting 20 minutes on it, I figured it was only shifting the inputs after we input beyond the input size (of 8, we will see later).
Now we go to proc_main
, which supposedly checks the input and gives us the flag if we input correctly.
You can ignore most of the renamed functions and look at only the last one cmp
. It is quite easy to see that it does a per-byte comparison, and returns 0 if they are the same (similar to C
’s strcmp
). The third argument 0x14
indicates the length in bytes.
Looking into init
, we see an initialization of values into the array variable.
A wise man once told me one should google the magic numbers before doing anything else. So I googled 0x67452301
and found that it is used in SHA1, and, in fact, the other numbers all matched. So we get to just assume that shuffle
which is called after init
is just SHA1. You can go and see shuffle
, it would be a nightmare to understand it from just the decompilation. I ignored the third function and just assumed it is part of SHA1 (which it is, see here).
Looking back at proc_main
, we can quickly figure that it is doing the below comparison:
sha1(sha1(input) ^ xbox) == local_24;
From the first call to SHA1, we see that the input size is 8 (bytes). Which corresponds to the values from take_input
earlier. Each byte can only have 8 possible values, so there are 8^8 = 2^24 ≈ 1.6E7 combinations in total. We can bruteforce this.
I wrote a python script to find the correct key combination.
The output: RUp, RLeft, LUp, RDown, LRight, LUp, RDown, RLeft
As it was running, I installed PCSX2 on my Windows machine, which also required a PS2 bios to run. I don’t know why I installed a PS2 emulator instead of a PS1, but it worked out I guess. We need to configure the keymappings to allow for keyboard input.
Booting the ISO up, and pressing the keys accordingly.
Final Script
import hashlib
from tqdm import tqdm
keymap = {
0: "LUp",
1: "LDown",
2: "LLeft",
3: "LRight",
4: "RUp",
5: "RRight",
6: "RDown",
7: "RLeft",
}
xbox = [ 0x52, 0x07, 0xca, 0x72, 0xc8, 0xc3, 0xe6, 0x37, 0x96, 0x68, 0x7c, 0x4f, 0x03, 0x6e, 0xef, 0x2b, 0xab, 0x84, 0xfc, 0xc1, 0x13, 0x50, 0x97, 0x6a, 0x82, 0xc5, 0xb5, 0x64, 0xc0, 0x3f, 0x29, 0x47, 0x58, 0x6e, 0xeb, 0x2a, 0xa9, 0xcd, 0x99, 0x21, 0x13, 0x57, 0x03, 0x06, 0x71, 0x26, 0x94, 0x0b, 0xfc, 0x23, 0x25, 0xbb, 0x7a, 0x16, 0xf5, 0x58, 0x2b, 0x97, 0x4d, 0x8d, 0x57, 0xa3, 0xe7, 0x72, 0x0d, 0xea, 0xe8, 0x37, 0x38, 0x37, 0x41, 0xcf, 0xf6, 0x4a, 0xd9, 0xf4, 0xef, 0x03, 0xe9, 0x19, 0x4b, 0x6f, 0x3b, 0xae, 0xc9, 0xde, 0x0c, 0x23, 0xb4, 0x0b, 0x2d, 0x23, 0xb2, 0xfe, 0x7d, 0x40, 0xd3, 0x4f, 0x55, 0xe5, 0x0f, 0x7b, 0x1f, 0x9a, 0x45, 0x02, 0xe0, 0xcf, 0x70, 0x9f, 0xc9, 0xb7, 0xae, 0xb2, 0x21, 0x9c, 0x17, 0xbb, 0x71, 0xf1, 0x83, 0x87, 0x33, 0x36, 0x81, 0x76, 0x84, 0x3b, 0x8c, 0xd3, 0x35, 0x8b, 0xea, 0xa6, 0xc5, 0x68, 0x9b, 0x67, 0x5d, 0x5d, 0xc7, 0x13, 0x97, 0xa2, 0x9d, 0x3a, 0xd8, 0x67, 0xc8, 0xe7, 0x51, 0x9f, 0x66, 0x9a, 0x29, 0x04, 0x5f, 0xd3, 0xf9, 0xe9, 0xfb, 0xdf, 0x2b, 0xde, 0x59, 0x2e, 0xfc, 0x53, 0x44, 0xbb, 0x3d, 0xf3, 0x22, 0x0e, 0x4d, 0x90, 0x43, 0x1f, 0x25, 0xb5, 0xbf, 0xab, 0x0f, 0xca, 0xd5, 0x52, 0xd0, 0xff, 0x00, 0x4f, 0xd9, 0x87, 0x5e, 0xc2, 0x31, 0xac, 0x87, 0x0b, 0x41, 0xc1, 0x33, 0xf7, 0x23, 0x66, 0x11, 0xc6, 0x74, 0x6b, 0x1c, 0x83, 0x05, 0x5b, 0x9a, 0xf6, 0xd5, 0xb8, 0x0b, 0x37, 0x2d, 0x2d, 0x77, 0x43, 0x87, 0xd2, 0x2d, 0x8a, 0xc8, 0x97, 0x58, 0x97, 0x21, 0x6f, 0x16, 0x2a, 0x39, 0x54, 0xcf, 0xa3, 0xc9, 0xb9, 0x6b, 0x0f, 0xdb, 0x0e, 0xe9, 0x7e, 0xec, 0x83, 0xd4, 0x6b, 0x0d, 0xc3, 0xd2, 0x5e, 0x5d, 0xa0, 0x33, 0xef, 0x35, 0x85, 0x2f, 0x1b, 0x3f, 0xfa, 0x65, 0xa2, 0xc0, 0x2f, 0x90, 0xff, 0xa9, 0x57, 0xce, 0x12, 0x01, 0xfc, 0x77, 0xdb, 0x51, 0x91, 0xa3, 0x27, 0xd3, 0x96, 0xa1, 0x16, 0x64, 0x9b, 0xac, 0x33, 0x15, 0x2b, 0x0a, 0x06, 0x25, 0xc8, 0xfb, 0x87, 0x3d, 0xfd, 0xe7, 0xb3, 0xb7, 0x02, 0xbd, 0xda, 0xb8, 0xc7, 0xe8, 0x47, 0x31, 0x3f, 0x86, 0x7a, 0x09, 0x64, 0xbf, 0x73, 0xd9, 0x89, 0x9b, 0x7f, 0xcb, 0x3e, 0x79, 0xce, 0xdc, 0xb3, 0x64, 0x1b, 0x1d, 0x93, 0x42, 0xee, 0xad, 0xf0, 0xa3, 0xbf, 0x05, 0x55, 0x5f, 0x4b, 0x2f, 0x2a, 0xf5, 0xf2, 0xb0, 0x5f, 0x20, 0xaf, 0xb9, 0x27, 0x7e, 0xa2, 0x11, 0x0c, 0xe7, 0xab, 0x21, 0x61, 0xd3, 0x97, 0xc3, 0xc6, 0x31, 0x66, 0x54, 0xcb, 0x3c, 0xe3, 0xe5, 0xfb, 0xba, 0x56, 0x35, 0x18, 0x6b, 0x57, 0x0d, 0xcd, 0x17, 0xe3, 0xa7, 0x32, 0xcd, 0x2a, 0xa8, 0xf7, 0x78, 0xf7, 0x01, 0x0f, 0x36, 0x8a, 0x19, 0xb4, 0x2f, 0xc3, 0xa9, 0x59, 0x0b, 0xaf, 0xfb, 0x6e, 0x89, 0x1e, 0xcc, 0xe3, 0xf4, 0xcb, 0xed, 0x63, 0xf2, 0x3e, 0xbd, 0x00, 0x13, 0x0f, 0x15, 0x25, 0xcf, 0xbb, 0xdf, 0x5a, 0x05, 0x42, 0xa0, 0x8f, 0xb0, 0x5f, 0x89, 0xf7, 0xee, 0xf2, 0x61, 0x5c, 0x57, 0x7b, 0x31, 0x31, 0x43, 0xc7, 0xf3, 0xf6, 0x41, 0xb6, 0x44, 0xfb, 0xcc, 0x93, 0xf5, 0xcb, 0x2a, 0xe6, 0x05, 0x28, 0xdb, 0x27, 0x1d, 0x9d, 0x87, 0x53, 0x57, 0x62, 0x5d, 0x7a, 0x98, 0x27, 0x08, 0xa7, 0x11, 0xdf, 0xa6, 0xda, 0x69, 0xc4, 0x9f, 0x93, 0xb9, 0x29, 0xbb, 0x1f, 0xeb, 0x9e, 0x19, 0x6e, 0xbc, 0x13, 0x84, 0x7b, 0xfd, 0x33, 0x62, 0x4e, 0x8d, 0x50, 0x83, 0xdf, 0xe5, 0xf5, 0x7f, 0xeb, 0xcf, 0x8a, 0x95, 0x92, 0x90, 0xbf, 0x40, 0x0f, 0x99, 0xc7 ]
assert(len(xbox) == 0x200)
t = 0x8047435a4236b560d4f114390dabde1ef7aa5be7.to_bytes(0x14, 'little')
assert(t[0] == 0xe7)
for inp in tqdm(range(8**8)):
vals = [(inp // (8**i)) % 8 for i in range(8)]
h = hashlib.sha1(bytes(vals)).digest()
inter = [xbox[i] ^ h[i % len(h)] for i in range(0x200)]
h = hashlib.sha1(bytes(inter)).digest()
if h[:len(t)] == t:
print(", ".join(list(map(lambda x : keymap[x], vals))))
break
The solution script takes around 12 minutes on my laptop.
Flag: wgmy{6b5dde1c322f4e1600a101f23098363d}