🇬🇧 LACTF 2023 - pwn/gatekeep
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
flag -> lactf{sCr3am1nG_cRy1Ng_tHr0w1ng_uP}