Meow Meow - SMP CTF 2024 - Selection Round

Posted on by dibbajothy

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}.