Our sources told us that one of PALINDROME's lieutenants, XIPHEREHPIX, wrote a special computer program for certain members of PALINDROME. We have somehow managed to get a copy of the source code and the compiled binary. The intention of the program is unclear, but we think encrypted blob inside the program could contain a valuable secret.
We are given 2 files, the source code and the compiled binary.
The source code defines some helper functions:
getch() -> to get bytes from the terminal
input_password() -> to parse the bytes and output the password string
calculate_sha256() -> to calculate the sha256 of a given string
verify_password() -> to check if the password hash matches the hardcoded hash
The code also defines the function accumulate_xor, which xors each of the 4 items of 2 arrays, and a gcm_decrypt for decryption with AES GCM.
We can view the execution flow of the code in the main() function.
intmain(int argc,char**argv){char password[MAX_PASSWORD_SIZE +1] = { 0 };int password_length;unsignedchar key[32];printf("Hello PALINDROME member, please enter password sir:"); password_length =input_password(password);if (password_length <40) {printf("The password should be at least 40 characters as per PALINDROME's security policy.\n");exit(0); }if (!verify_password(password, password_length)) {initialise_key(key, password, password_length);show_welcome_msg(key); }else {printf("Failure! \n");exit(0); }}
We can see that the program asks for an over 40 byte long password, which it passes into the initialise_key function.
Upon inspection of this function, we see that the password is not actually used in creating the key. This means that we can skip the password verification altogether.
voidinitialise_key(unsignedchar*key,char*password,int password_length) {constchar*seed ="PALINDROME IS THE BEST!";int i, j;int counter =0;uint256_t*key256 = (uint256_t*)key;key256->a0 =0;key256->a1 =0;key256->a2 =0;key256->a3 =0;uint256_t arr[20] = { 0 };calculate_sha256((unsignedchar*) arr, (unsignedchar*) seed, strlen(seed));for (i =1; i <20; i++) {calculate_sha256((unsignedchar*)(arr+i), (unsignedchar*) (arr+i-1),32); }for (i =0; i < password_length; i++) {int ch = password[i];for (j =0; j <8; j++) { counter = counter %20;if (ch &0x1) {accumulate_xor(key256, arr+counter); } ch = ch >>1; counter++; } }}
The initialise_key function uses a hardcoded seed to generate the array of 20 SHA256 hashes. This means that the array of hashes used in the key generation is constant.
Moving on to the for loop, we can see that the password is only used for determining which of the arrays are XOR-ed to generate the key.
This means that the final key is generated solely from the same 20 SHA256 hashes, no matter what password is entered. Therefore we could bruteforce all combinations of all lengths of the SHA256 hashes until we find the flag.
import hashlibfrom Crypto.Cipher import AESimport binasciiimport itertoolsfrom Crypto.Util.number import long_to_bytesdefgcm_decrypt(ciphertext,key,iv): cipher = AES.new(key, AES.MODE_GCM, iv) plaintext = cipher.decrypt(ciphertext)return plaintextciphertext =b"\xad\xac\x81\x20\xc6\xd5\xb1\xb8\x3a\x2a\xa8\x54\xe6\x5f\x9a\xad\xa4\x39\x05\xd9\x21\xae\xab\x50\x98\xbd\xe4\xc8\xe8\x2a\x3c\x63\x82\xe3\x8e\x5d\x79\xf0\xc6\xf4\xf2\xe7"iv =b"PALINDROME ROCKS"header =b"welcome_message"tag =b"\xbd\xfc\xc0\xdb\xd9\x09\xed\x66\x37\x34\x75\x11\x75\xa2\x7a\xaf"seed ="PALINDROME IS THE BEST!"# generate the constant arrayarr = [Nonefor _ inrange(20)]arr[0]= hashlib.sha256(seed.encode()).digest()for i inrange(1, 20): arr[i]= hashlib.sha256(arr[i-1]).digest()# convert to int for XORarr = [int.from_bytes(x, "big")for x in arr]# bruteforce all combinationsdefbruteforce():for size inrange(1, len(arr) +1):for comb in itertools.combinations(arr, size): key =0for i in comb: key ^= i key =long_to_bytes(key) key = key.ljust(32, b"\x00") plaintext =gcm_decrypt(ciphertext, key, iv)ifb"TISC"in plaintext:print(f"{plaintext=}")print(f"{key=}")return