RITSEC CTF 2026 - T-reasure Chest

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:

  1. Input Collection: The program reads up to 256 characters from stdin using fgets.
  2. 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 v7 null bytes.
  3. Encryption: It calls sub_4012A9, which iterates through the input in 8-byte blocks and encrypts them using sub_4011C6.
  4. Verification: The program checks if the final processed length v8 is 34 (0x22) and if the resulting ciphertext matches a hardcoded byte array at unk_404080.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
__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);
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):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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_404080
ciphertext = 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}