Meow Meow - SMP CTF 2024 - Selection Round
Greetings, Players
This guide provides a detailed explanation to help you solve the Reverse Engineering challenge titled Meow Meow.
We start with the provided meow.meow file. Using the file command in Linux reveals that it is an ELF 64-bit LSB PIE executable for x86-64 architecture.
If we attempt to execute the file using ./meow.meow, it prompts us to enter a license key, which corresponds to the flag we need to find.
Opening the binary in GDB and listing all user-defined functions using info functions reveals the following user defined functions:
0x00000000000011a9 confusedMeow
0x0000000000001236 meowCheck
0x0000000000001281 real_flag_check
0x0000000000001339 show_cat
0x000000000000136d main
Among these, the real_flag_check function at 0x0000000000001281 is responsible for verifying the license key.
Using GDB or a decompiler like Ghidra, we can analyze the real_flag_check function.As the function will be so messy, we can refine that with ChatGpt and make it more readable .Here's the decompiled version:
int real_flag_check(const char *input) {
extern char meowMeow[21];
extern unsigned char headKey[4];
if (input == NULL) return 0;
size_t input_length = strlen(input);
if (input_length != 21) return 0;
for (int i = 0; i < 21; i++) {
unsigned char expected_char = (meowMeow[i] - i - 7) ^ headKey[i % 4];
if ((unsigned char)input[i] != expected_char) return 0;
}
return 1;
}
This function validates the input by comparing each character against a generated value calculated using the formula: (meowMeow[i] - i - 7) ^ headKey[i % 4]
meowMeow is an array of 21 bytes, and headKey is an array of 4 bytes, both embedded in the binary.
On (GDB), we can easily find the addresses of those arrays with the -command(disassemble real_flag_check),
Dump of assembler code for function real_flag_check:
0x0000000000001281 <+0>: push rbp
0x0000000000001282 <+1>: mov rbp,rsp
0x0000000000001285 <+4>: sub rsp,0x20
0x0000000000001289 <+8>: mov QWORD PTR [rbp-0x18],rdi
0x000000000000128d <+12>: cmp QWORD PTR [rbp-0x18],0x0
0x0000000000001292 <+17>: jne 0x129e <real_flag_check+29>
0x0000000000001294 <+19>: mov eax,0x0
0x0000000000001299 <+24>: jmp 0x1337 <real_flag_check+182>
0x000000000000129e <+29>: mov rax,QWORD PTR [rbp-0x18]
0x00000000000012a2 <+33>: mov rdi,rax
0x00000000000012a5 <+36>: call 0x1050 <strlen@plt>
0x00000000000012aa <+41>: mov DWORD PTR [rbp-0x4],eax
0x00000000000012ad <+44>: mov eax,0x15
0x00000000000012b2 <+49>: cmp DWORD PTR [rbp-0x4],eax
0x00000000000012b5 <+52>: je 0x12be <real_flag_check+61>
0x00000000000012b7 <+54>: mov eax,0x0
0x00000000000012bc <+59>: jmp 0x1337 <real_flag_check+182>
0x00000000000012be <+61>: mov DWORD PTR [rbp-0x8],0x0
0x00000000000012c5 <+68>: jmp 0x1328 <real_flag_check+167>
0x00000000000012c7 <+70>: mov eax,DWORD PTR [rbp-0x8]
0x00000000000012ca <+73>: cdqe
0x00000000000012cc <+75>: lea rdx,[rip+0xd5d] # 0x2030 "meowMeow"
0x00000000000012d3 <+82>: movzx eax,BYTE PTR [rax+rdx*1]
0x00000000000012d7 <+86>: mov BYTE PTR [rbp-0x9],al
0x00000000000012da <+89>: mov eax,DWORD PTR [rbp-0x8]
0x00000000000012dd <+92>: sub BYTE PTR [rbp-0x9],al
0x00000000000012e0 <+95>: sub BYTE PTR [rbp-0x9],0x7
0x00000000000012e4 <+99>: mov eax,DWORD PTR [rbp-0x8]
0x00000000000012e7 <+102>: mov eax,edx
0x00000000000012e9 <+104>: sar eax,0x1f
0x00000000000012ec <+107>: shr eax,0x1e
0x00000000000012ef <+110>: add edx,eax
0x00000000000012f1 <+112>: and edx,0x3
0x00000000000012f4 <+115>: sub edx,eax
0x00000000000012f6 <+117>: mov eax,edx
0x00000000000012f8 <+119>: cdqe
0x00000000000012fa <+121>: lea rdx,[rip+0xd23] # 0x2024 "headKey"
0x0000000000001301 <+128>: movzx eax,BYTE PTR [rax+rdx1]*
0x0000000000001305 <+132>: xor BYTE PTR [rbp-0x9],al
0x0000000000001308 <+135>: mov eax,DWORD PTR [rbp-0x8]
0x000000000000130b <+138>: movsxd rdx,eax
0x000000000000130e <+141>: mov rax,QWORD PTR [rbp-0x18]
0x0000000000001312 <+145>: add rax,rdx
0x0000000000001315 <+148>: movzx eax,BYTE PTR [rax]
0x0000000000001318 <+151>: cmp BYTE PTR [rbp-0x9],al
0x000000000000131b <+154>: je 0x1324 <real_flag_check+163>
0x000000000000131d <+156>: mov eax,0x0
0x0000000000001322 <+161>: jmp 0x1337 <real_flag_check+182>
0x0000000000001324 <+163>: add DWORD PTR [rbp-0x8],0x1
0x0000000000001328 <+167>: mov eax,0x15
0x000000000000132d <+172>: cmp DWORD PTR [rbp-0x8],eax
0x0000000000001330 <+175>: jl 0x12c7 <real_flag_check+70>
0x0000000000001332 <+177>: mov eax,0x1
0x0000000000001337 <+182>: leave
0x0000000000001338 <+183>: ret
#0x2030 <meowMeow>, #0x2024 <headKey>
Now running the -command (x/21x 0x2030) on GDB, we can find all the hex values of those 21 element of “meowMeow” array,
pwndbg> x/21x 0x2030
0x2030 <meowMeow>: 0x19 0x20 0x85 0x02 0x37 0x3c 0x29 0x02
0x2038 <meowMeow+8>: 0x2d 0x48 0x30 0xc5 0x49 0x1e 0x73 0xc6
0x2040 <meowMeow+16>: 0x4e 0x22 0x60 0xd0 0x57
Now do same with headKey, -command (x/4x 0x2024)
pwndbg> x/4x 0x2024
0x2024 <headKey>: 0x41 0x55 0x2c 0x83
As we found all the values we need to calculate our flag with same operations
((meowMeow[i] - i - 7) ^ headKey[i % 4])
, we will write a cpp function to calculate that,
#include <iostream>
using namespace std;
int main() {
int meowMeow[21] = {0x19, 0x20, 0x85, 0x02, 0x37, 0x3C, 0x29, 0x02,
0x2D, 0x48, 0x30, 0xC5, 0x49, 0x1E, 0x73, 0xC6,
0x4E, 0x22, 0x60, 0xD0, 0x57};
int headKey[4] = {0x41, 0x55, 0x2C, 0x83};
for (int i = 0; i < 21; i++) {
int expected_char = (meowMeow[i] - i - 7) ^ headKey[i % 4];
cout << char(expected_char);
}
return 0;
}
Compile and run the program to retrieve the flag. The output will be in the format SMP{flag}.