pwnable.kr - fd

Posted on Apr 12, 2023

Write-Up

The fd challenge is the first of the Toddler’s Bottle challenges in pwnable.kr. The description of the challenge is the following:

Mommy! what is a file descriptor in Linux?

ssh [email protected] -p2222 (pw:guest)

If we log into the server, we’ll be in the /home/fd directory with the following:

fd@pwnable:~$ ls -lah
total 40K
drwxr-x---   5 root   fd   4.0K Oct 26  2016 .
drwxr-xr-x 116 root   root 4.0K Nov 11  2021 ..
d---------   2 root   root 4.0K Jun 12  2014 .bash_history
-r-sr-x---   1 fd_pwn fd   7.2K Jun 11  2014 fd
-rw-r--r--   1 root   root  418 Jun 11  2014 fd.c
-r--r-----   1 fd_pwn root   50 Jun 11  2014 flag
-rw-------   1 root   root  128 Oct 26  2016 .gdb_history
dr-xr-xr-x   2 root   root 4.0K Dec 19  2016 .irssi
drwxr-xr-x   2 root   root 4.0K Oct 23  2016 .pwntools-cache

It is obvious that we want to access the flag file, but we can only read it if we’re fd_pwn (file owner) or root (file group).

If we look at the fd executable, we notice it is owned by fd_pwn, belongs to the fd group and has the setuid flag set (s in the permissions column), which means that when we run it, it will have the same permissions as fd_pwn, even though we (fd) are the ones running it.

Let’s now look at the contents of fd.c, which we can assume is the source for fd, and our possible entrypoint to access the flag file:

 1#include <stdio.h>
 2#include <stdlib.h>
 3#include <string.h>
 4char buf[32];
 5int main(int argc, char* argv[], char* envp[]){
 6    if(argc<2){
 7        printf("pass argv[1] a number\n");
 8        return 0;
 9    }
10    int fd = atoi( argv[1] ) - 0x1234;
11    int len = 0;
12    len = read(fd, buf, 32);
13    if(!strcmp("LETMEWIN\n", buf)){
14        printf("good job :)\n");
15        system("/bin/cat flag");
16        exit(0);
17    }
18    printf("learn about Linux file IO\n");
19    return 0;
20}

It should immediately jump to our attention that on line 15 we are cat ing the contents of the flag file. If we backtrace from there, we see a strcmp on line 13 for the string LETMEWIN\n and the contents of buf, which is a 32B char array filled on line 12.

The read that we see on line 12 will read up to 32 bytes from fd into the buf array. This fd variable is the file descriptor that is defined on line 10, which takes the first argument when calling the executable (argv[1]), converts it to an int (atoi) and subtracts 0x1234 (4660).

With basic knowledge about file descriptors, it should be obvious that we want to make the first argument of read to be 0 (stdin), so that we can send the LETMEWIN\n string. To do this we simply set the first argument as 4660, the program will then block on the read, and we send it the LETMEWIN<enter> string:

fd@pwnable:~$ ./fd 4660
LETMEWIN
good job :)
<flag content>