Score! You found a treasure chest! Now if only you could figure out how to unlock it… maybe there’s a magic word?
Initial Analysis
The challenge provides a Linux binary that prompts for a “magic word.” If the correct word is entered, it displays a treasure chest (ASCII art) and presumably the flag. The goal is to reverse-engineer the “magic word” verification logic.
Using IDA Pro to decompile the main function, the following program flow was identified:
- Input Collection: The program reads up to 256 characters from
stdinusingfgets. - Padding & Allocation:
- It calculates the input length (
v8). - It determines a padding value (
v7 = v8 % 8). - It allocates memory for the input plus padding and copies the string into it.
- Crucially, it appends
v7null bytes.
- It calculates the input length (
- Encryption: It calls
sub_4012A9, which iterates through the input in 8-byte blocks and encrypts them usingsub_4011C6. - Verification: The program checks if the final processed length
v8is 34 (0x22) and if the resulting ciphertext matches a hardcoded byte array atunk_404080.
__int64 __fastcall main(int a1, char **a2, char **a3){ char s[256]; // [rsp+10h] [rbp-130h] BYREF void *v5[3]; // [rsp+110h] [rbp-30h] BYREF void *dest; // [rsp+128h] [rbp-18h] int v7; // [rsp+134h] [rbp-Ch] signed int v8; // [rsp+138h] [rbp-8h] int i; // [rsp+13Ch] [rbp-4h]
qmemcpy(v5, "tiny_encrypt_key", 16); puts("Try to open the chest!"); printf("Maybe try saying the magic word: "); fgets(s, 256, stdin); s[strcspn(s, "\n")] = 0; printf("input: %s\n", s); v8 = strlen(s); v7 = v8 % 8; dest = malloc(v8 + v8 % 8); memcpy(dest, s, v8); memset((char *)dest + v8, 0, v7);70 collapsed lines
v8 += v7; sub_4012A9(dest, (unsigned int)v8, v5); printf("result: 0x"); for ( i = 0; i < v8; ++i ) printf("%02X", *((unsigned __int8 *)dest + i)); putchar(10); if ( v8 == 34 && !memcmp(dest, &unk_404080, 0x22u) ) { puts("Congrats! Here's your treasure: "); puts("*******************************************************************************"); puts(" | | | |"); puts(" _________|________________.=\"\"_;=.______________|_____________________|_______"); puts("| | ,-\"_,=\"\" `\"=.| |"); puts("|___________________|__\"=._o`\"-._ `\"=.______________|___________________"); puts(" | `\"=._o`\"=._ _`\"=._ |"); puts(" _________|_____________________:=._o \"=._.\"_.-=\"'\"=.__________________|_______"); puts("| | __.--\" , ; `\"=._o.\" ,-\"\"\"-._ \". |"); puts("|___________________|_._\" ,. .` ` `` , `\"-._\"-._ \". '__|___________________"); puts(" | |o`\"=._` , \"` `; .\". , \"-._\"-._; ; |"); puts(" _________|___________| ;`-.o`\"=._; .\" ` '`.\"` . \"-._ /________________|_______"); puts("| | |o; `\"-.o`\"=._`` '` \" ,__.--o; |"); puts("|___________________|_| ; (#) `-.o `\"=.`_.--\"_o.-; ;___|___________________"); puts("____/______/______/___|o;._ \" `\".o|o_.--\" ;o;____/______/______/____"); puts("/______/______/______/_\"=._o--._ ; | ; ; ;/______/______/______/_"); puts("____/______/______/______/__\"=._o--._ ;o|o; _._;o;____/______/______/____"); puts("/______/______/______/______/____\"=._o._; | ;_.--\"o.--\"_/______/______/______/_"); puts("____/______/______/______/______/_____\"=.o|o_.--\"\"___/______/______/______/____"); puts("/______/______/______/______/______/______/______/______/______/______/________"); puts("*******************************************************************************"); free(dest); return 0; } else { puts("Hmmmm.... didn't open..."); free(dest); return 0; }}
__int64 __fastcall sub_4012A9(__int64 a1, unsigned int a2, __int64 a3){ __int64 result; // rax __int64 v5; // [rsp+1Ch] [rbp-Ch] BYREF unsigned int i; // [rsp+24h] [rbp-4h]
for ( i = 0; ; ++i ) { result = a2 >> 2; if ( i >= (unsigned int)result ) break; v5 = *(_QWORD *)((int)(8 * i) + a1); sub_4011C6(&v5, a3); *(_QWORD *)(a1 + (int)(8 * i)) = v5; } return result;}
t64 __fastcall sub_4011C6(unsigned int *a1, _DWORD *a2){ unsigned int v3; // [rsp+1Ch] [rbp-14h] unsigned int v4; // [rsp+20h] [rbp-10h] int i; // [rsp+28h] [rbp-8h] int v6; // [rsp+2Ch] [rbp-4h]
v4 = *a1; v3 = a1[1]; v6 = 0; for ( i = 0; i <= 31; ++i ) { v6 -= 1640531527; v4 += ((v3 >> 5) + a2[1]) ^ (v3 + v6) ^ (16 * v3 + *a2); v3 += ((v4 >> 5) + a2[3]) ^ (v4 + v6) ^ (16 * v4 + a2[2]); } *a1 = v4; a1[1] = v3; return v3;}Reverse Engineering the Cipher
The function sub_4011C6 implements a variation of the Tiny Encryption Algorithm (TEA).
- Key: The key is the hardcoded string
"tiny_encrypt_key". - Constants: It uses the standard TEA delta
0x9E3779B9. - Structure: It performs 32 rounds of Feistel-like transformations.
- Implementation Detail: Unlike standard TEA, it updates the “sum” (v6) at the start of the loop and uses key indices
[0, 1]for the first half and[2, 3]for the second half of the block update.
Extraction & Decoding
To solve the challenge, we extract the target ciphertext from 0x404080 and the 16-byte key. Since TEA is a symmetric block cipher, we can implement a decryption routine.
Target Ciphertext (34 bytes):
38 75 5B CB 44 D2 BE 5D 96 9C 56 43 EA 98 06 75 4A 48 13 E6 D4 E8 8E 4F 72 70 8B FF DC 99 F8 76 C5 C9
Decryption Script (Python):
def tea_decrypt(v_bytes, k_bytes): v0 = int.from_bytes(v_bytes[0:4], 'little') v1 = int.from_bytes(v_bytes[4:8], 'little') k = [int.from_bytes(k_bytes[i:i+4], 'little') for i in range(0, 16, 4)]
delta = 0x9E3779B9 sum_val = (delta * 32) & 0xFFFFFFFF
for _ in range(32): v1 = (v1 - (((v0 >> 5) + k[3]) ^ (v0 + sum_val) ^ ((v0 << 4) + k[2]))) & 0xFFFFFFFF v0 = (v0 - (((v1 >> 5) + k[1]) ^ (v1 + sum_val) ^ ((v1 << 4) + k[0]))) & 0xFFFFFFFF sum_val = (sum_val - delta) & 0xFFFFFFFF
return v0.to_bytes(4, 'little') + v1.to_bytes(4, 'little')
# Data from unk_404080ciphertext = bytes.fromhex("38755BCB44D2BE5D969C5643EA9806754A4813E6D4E88E4F72708BFFDC99F876")key = b"tiny_encrypt_key"
flag = b""for i in range(0, len(ciphertext), 8): flag += tea_decrypt(ciphertext[i:i+8], key)
print(flag.decode().strip('\x00'))Conclusion
Running the decryption yields the magic word/flag. The check for length 34 and the specific padding logic suggests the original flag was 29 characters long, which when padded with 5 null bytes (29 % 8 = 5), results in the 34-byte block compared by the binary.
Flag
RS{oh_its_a_TEAreasure_chest}