Note

First pwn challenge of this CTF. It’s pretty easy because it only consists in overwriting a variable on the stack to make a condition valid and print the flag.

Description

If I gaslight you enough, you won't be able to get my flag! :)

Note: The attached binary is the exact same as the one executing on the remote server.

Dockerfile, gatekeep.c, gatekeep

File information

checksec gatekeep && file gatekeep

[*] '/home/conflict/ctfs/lactf2023/gatekeep/gatekeep'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
gatekeep: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=80c46fdc485592ada59a02cc96b63fc03e9c6434, for GNU/Linux 3.2.0, not stripped

So, we’re going to work on a 64 bits non-stripped dynamically linked executable, with PIE and NX enabled, but they are irrelevant to achieve our goal.

For the source code, there is no need to decompile the file or to struggle with some pseudo-code because we have the .c file.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void print_flag() {
    char flag[256];

    FILE* flagfile = fopen("flag.txt", "r");
    
    if (flagfile == NULL) {
        puts("Cannot read flag.txt.");
    } else {
        fgets(flag, 256, flagfile);
        flag[strcspn(flag, "\n")] = '\0';
        puts(flag);
    }
}

int check(){
    char input[15];
    char pass[10];
    int access = 0;

    // If my password is random, I can gatekeep my flag! :)
    int data = open("/dev/urandom", O_RDONLY);
    if (data < 0)
    {
        printf("Can't access /dev/urandom.\n");
        exit(1);
    }
    else
    {
        ssize_t result = read(data, pass, sizeof pass);
        if (result < 0)
        {
            printf("Data not received from /dev/urandom\n");
            exit(1);
        }
    }
    close(data);
    
    printf("Password:\n");
    gets(input);

    if(strcmp(input, pass)) {
        printf("I swore that was the right password ...\n");
    }
    else {
        access = 1;
    }

    if(access) {
        printf("Guess I couldn't gaslight you!\n");
        print_flag();
    }
}

int main(){
    setbuf(stdout, NULL);
    printf("If I gaslight you enough, you won't be able to guess my password! :)\n");
    check();
    return 0;
}

Exploitation

Here, we can see a print_flag() function that will read and print out our flag, so that’s the function we want to call

Then we have the check() function, it generates a random password then calls gets() to ask us the password. As a reminder, this function is dangerous and should never be used because it doesn’t allow you to check the length of the user input.

It then compares our input and the password, and if they match, it sets the access variable to 1.

Finally, if access is not null, it prints the flag.

Since we know we can overflow the buffer thanks to gets(), we will be able to overwrite variables on the stack, including access. The goal here is going to be to change its value to anything that is not null.

To find out how many characters we will enter, we have two options, but the easiest one would be to read the source code and figure out how many bytes the previous variables take.

    char input[15];
    char pass[10];
    int access = 0;

Here we can see two variables initialised before the access (I know there is also a flag[256] buffer but it is only initialised when print_flag() is called). They take 25 bytes in total so we can assume that if we enter 30 bytes we should overwrite access.

For the exploit, there is no need to use pwntools, we’re just going to connect via netcat and send the payload manually.

python3 -c "print('A'*30)"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

2023-02-14-184628_717x712_scrot

flag -> lactf{sCr3am1nG_cRy1Ng_tHr0w1ng_uP}