Shattered-memories - LA CTF 2024

Posted on by NomanProdhan

Hello Reverse Engineers,

Welcome to another reverse engineering challenge write-up. Today, we're going to crack the ** Shattered-memories ** reverse engineering challenge from the LA CTF 2024.

For this challenge, we are provided with a Linux binary as the challenge file. When executed, the binary prompts us for a flag.

https://i.postimg.cc/NjrrkmZJ/shattered-memories-ss-1.png

As the binary was asking for a flag, I entered "KS HackZone" as a flag, but it returned an error message saying, "No, I definitely remember it being a different length..." This indicates that the binary checks the flag's length, and our input didn't match the expected number of characters. So, it's time to reverse this binary and uncover the actual flag.

To begin, I loaded the binary into Radare2, my favorite tool for working with binaries. 🤟

I decompiled the main function of the binary using the Radare2 Ghidra plugin and got the following pseudo C code:


ulong main(void)

{
    int32_t iVar1;
    int64_t iVar2;
    ulong uVar3;
    ulong s1;
    uchar auStack144 [8];
    uchar auStack136 [8];
    uchar auStack128 [8];
    uchar auStack120 [108];
    ulong var_4h;
    
    sym.imp.puts("What was the flag again?");
    sym.imp.fgets(&s1, 0x80, *0x4040);
    sym.strip_newline(&s1);
    iVar2 = sym.imp.strlen(&s1);
    if (iVar2 == 0x28) {
        var_4h._0_4_ = 0;
        iVar1 = sym.imp.strncmp(auStack144, "t_what_f", 8);
        var_4h._0_4_ = var_4h + (iVar1 == 0);
        iVar1 = sym.imp.strncmp(auStack120, "t_means}", 8);
        var_4h._0_4_ = var_4h + (iVar1 == 0);
        iVar1 = sym.imp.strncmp(auStack128, "nd_forge", 8);
        var_4h._0_4_ = var_4h + (iVar1 == 0);
        iVar1 = sym.imp.strncmp(&s1, "lactf{no", 8);
        var_4h._0_4_ = var_4h + (iVar1 == 0);
        iVar1 = sym.imp.strncmp(auStack136, "orgive_a", 8);
        var_4h._0_4_ = var_4h + (iVar1 == 0);
    // switch table (6 cases) at 0x21ac
        switch(var_4h) {
        case 0:
            sym.imp.puts("No, that definitely isn\'t it.");
            uVar3 = 1;
            break;
        case 1:
            sym.imp.puts("I\'m pretty sure that isn\'t it.");
            uVar3 = 1;
            break;
        case 2:
            sym.imp.puts("I don\'t think that\'s it...");
            uVar3 = 1;
            break;
        case 3:
            sym.imp.puts("I think it\'s something like that but not quite...");
            uVar3 = 1;
            break;
        case 4:
            sym.imp.puts("There\'s something so slightly off but I can\'t quite put my finger on it...");
            uVar3 = 1;
            break;
        case 5:
            sym.imp.puts("Yes! That\'s it! That\'s the flag! I remember now!");
            uVar3 = 0;
            break;
        default:
            uVar3 = 0;
        }
    }
    else {
        sym.imp.puts("No, I definitely remember it being a different length...");
        uVar3 = 1;
    }
    return uVar3;
}


As you can see, the actual flag is hardcoded into the binary, but it's in multiple parts. First, the binary checks the length of our input flag, ensuring it is 0x28 (40) characters long.

We know the flag format is lactf{something}, so "lactf{no" will be our starting and "t_means}" is the end of the flag. However, we need to determine the correct order of the remaining three parts.

We can easily find the flag by manually shuffling the three parts, but I have written the following simple Python script to automate this process and find the correct flag:


import subprocess
import itertools

start_part = "lactf{no"
end_part = "t_means}"
shuffle_parts = ["t_what_f", "nd_forge", "orgive_a"]
permutations = itertools.permutations(shuffle_parts)

def check_flag(flag):
    process = subprocess.Popen(['./shattered-memories'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    stdout, stderr = process.communicate(input=f"{flag}\n")
    
    if "Yes! That's it! That's the flag! I remember now!" in stdout:
        return True
    return False

for perm in permutations:
    middle_part = "".join(perm)
    flag = f"{start_part}{middle_part}{end_part}"
    if check_flag(flag):
        print(f"The correct flag is: {flag}")
        break

It was an easy reverse engineering challenge. I hope you enjoyed the write-up. I will be back with another write-up soon. Till then, happy hacking 🤟🤟