Ok, so let’s jump into challenge #1 called “fd.”

Head on over to https://pwnable.kr/. If it is your first time on the site, take some time to read through the homepage and get a feel for the layout of the site, as well as its mission, and philosophy. Once you have familiarized yourself, head up to the top left corner and click on “PLAY” to continue on to the challenges.

pwnable click-to-play
Image 1: A screenshot of the pwnable homepage with an arrow pointing to the “PLAY” LINK

On the challenges page, click on the first challenge in the “Toddler’s Bottle” section. It is called “fd,” and it looks like this:

fd challenge icon
Image 2: The “fd” challenge icon looks like a pink blob with a face and a bandage on the top of its “head.” It is surrounded by (top left) a tree stump, (top right) another pink blob, (bottom right) a red apple, and (bottom left) a purple mushroom with yellow spots.

Once you click on “fd” you will see some instructions. They say:

Mommy! what is a file descriptor in Linux?

* try to play the wargame your self but if you are ABSOLUTE beginner, follow this tutorial link:

ssh fd@pwnable.kr -p2222 (pw:guest)

It looks like we get a hint for the challenge — it has something to do with file descriptors — in addition to a instructions on how to SSH into the machine that hosts the challenge. File descriptors we can return to later. For now, let’s focus on getting into the challenge. If you are scratching your head wondering what SSH is, in a nutshell, it stands for Secure Shell, and it is a way to securely (think encryption) access resources on another computer over the open internet. For the purposes of this challenge, SSH allows you to access the computer system where the challenge is set up. For more detail on SSH, check out this video explanation from Wisc-Online: https://www.youtube.com/watch?v=v_cVEpESG3g

SSH clients typically come installed by default on Unix-like systems (Linux, Mac, etc.). If you are using Windows, you may need to download the SSH client PuTTY. I am going to be using Unix-like systems, but if you have any questions on how to set up PuTTY and follow along on Windows, check out this guide at ssh.com https://www.ssh.com/ssh/putty/windows/

Fire up your command line interface (Terminal, Konsole, etc.) and enter:

ssh fd@pwnable.kr -p 2222

If you see the following message, this is normal. Type yes to continue.

The authenticity of host '[pwnable.kr]:2222 ([]:2222)' can't be established.
ECDSA key fingerprint is SHA256:kWTx0QCL5U5VbUkQa1x5/dw8hJ6DS5CR0KilMRJnUYY.
Are you sure you want to continue connecting (yes/no)?

When prompted for a password, enter “guest” and now you’re in! If you’ve done it right, your screen should look something like this:

fd challenge command prompt
Image 3: Command prompt for fd challenge

All right, let’s take a look around and see what’s here. Let’s start by examining the files in our current directory. This system is telling us it uses the Linux distribution Ubuntu, so it’s a fair bet we can use Unix-like commands to navigate our way around the system. If you are new to Unix-like commands, it is a fundamental skill that is beyond the scope of this write-up. Check out this basic guide to Linux commands.

To see the files in our current directory, type ls. You should see three files in the output: fd, fd.c, and flag. Flag? That is what we are trying to “capture,” is it not? Maybe we should try to look at it and see what happens. We can use the “cat” command to print the contents of a file to “standard output” (i.e. the terminal screen). Try that now.

cat permission denied
Image 4: When we try to view the file “flag,” we are denied.

Uh-oh 🙁 Looks like it is not quite that easy. We get an error message telling us we don’t have permission to view that file. Shucks. Well, why don’t we take a look at what we do have permission to view? We can check that by running the command ls -l. (I won’t take time here to describe how file permissions work on Unix-like systems, but if you need a primer or refresher, check out this tutorial here.

ls -l command output
Image 5: Output from ls -l command

The output from ls -l shows us that:

  • The “flag” file belongs to owner “fd_pwn” and group “root,” and can only be read by users that are the owner or who are in the “root” group.
  • The “fd.c” file belongs to owner “root” and group “root.” The owner can write to the file, and all users can read it.
  • The “fd” file belongs to owner “fd_pwn” and group “fd.” It can be read by the owner and users that are part of the “fd” group. It can be executed by “fd” group members, and the “s” stands for setuid (set user id), which means that the program will run with the permissions of its owner (which, fingers crossed, are more permissions than we have).

We also need to know what our user id is, so let’s run the command id. The output shows us that we are uid (user id) “fd,” part of group “fd”. Right, so now we know why we couldn’t read “flag” — we aren’t the file owner and we are part of group “fd,” not group “root.” BUT we can read “fd.c” (all users can) and we can execute “fd” because we are part of the “fd” group.

Why don’t we give executing the file a go, just to see how it works? Type ./fd to run the file. It should give you a spit out a message that says pass argv[1] a number. Here, you have to know a little C programming, and I highly recommend Harvard’s free Computer Science 50 course, but just take my word for it that argv[1] is input for the program that you are running. It is the first uninterrupted set of characters after you run a program. In the following example, argv[1] is the word “leroy”

./CoolProgram leroy

At this point we don’t know why the program is requesting a number, or how it will process this number, so why don’t we give it a couple test numbers? Any number will do, so let’s start with 0, 1, and 2.


argv[1] input results
Image 6: We input 0, 1, and 2 for argv[1] and get a canned message

For each attempt, we get the output “learn about Linux file IO.” You can try a bunch more numbers if you want, but I think we are getting the message that random number guessing is not the most efficient way of getting to our goal of reading that “flag” file. Let’s try reading “fd.c” instead.

If we run the command cat fd.c, we see C programming source code for what we can infer is the program we have been running.

Here is the C code annotated with some of my comments. You typically annotate C code like so: /* Some insightful comments */. I’ll do my best to walk you through the code at a high level.

fd@ubuntu:~$ cat fd.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* We need to include these .h (header) files because they contain necessary definitions for the functions we call later. For example, we cannot print something to standard out (the terminal) without including stdio.h (standard input/output */
char buf[32];
/* Create an array of 32 bytes to hold some data */
int main(int argc, char* argv[], char* envp[]){
        printf("pass argv[1] a number\n");
        return 0;
        /* If there are fewer than 2 command line arguments (the program name andargv[1]), complain to the user and have them supply a number. */
    int fd = atoi( argv[1] ) - 0x1234;
    /* The number we supplied as argv[1] is interpreted by default as a string (text). We want the program to interpret it as an integer so we can perform some arithmetic, and atoi will do just that for us */
    int len = 0; /* Declare a variable len and initialize it to zero */
    len = read(fd, buf, 32); /* I’m a little confused by this.I’ll come back to it */
    if(!strcmp("LETMEWIN\n", buf)){ /* I’m a little confused by this. I’ll come back to it */
        printf("good job :)\n"); /* Tell the user “good job” */
        system("/bin/cat flag"); /* Print the “flag” file to the terminal */
    printf("learn about Linux file IO\n"); /* Tell the user to learn about Linux file input and output */
    return 0;

So there was a little bit in there I wasn’t clear on, but that is ok. Let’s start with something we might know a little better. Jump to the number 0x1234 in the code. You might recognize that as hexadecimal notation. It is just another way of representing a number, like binary notation. Watch this video from Khan Academy for a deeper dive.

What is the decimal equivalent for 0x1234? If you type “0x1234 to decimal” or “0x1234 to integer” in a search engine, you will likely get the result back pretty quickly, though there are also plenty of online hexadecimal to decimal converters. The answer is 4660.

Here’s a crazy idea — let’s type 4660 as our argv[1] number when we execute the program and see what happens.

4660 fd input
Image 7: Inputting 4660 as argv[1]. It looks like the program is hanging.

From the image, you can see it appears to hang and gives us a blinking cursor. Still not exactly what we are after, but certainly different from the canned message about learning about Linux IO we got earlier. Looks like a step in the right direction. If we press enter though, we do get that message. Just for fun, let’s try entering 4661 and see what happens. It hangs again! Ditto for 4662. But starting at 4663, it goes straight to the canned message. Hmm. On the other side, 4559 doesn’t do anything for us either. So there is something special about 4660 to 4662. What is it?

It may not be immediately apparent, so time for some Googling….err “research.” What does the read command in the source code do? If you remember, I said I would need to circle back later. It turns out, this is the description for the read command:

ssize_t read(int fd, void *buf, size_t count);

read() attempts to read up to “count” bytes from file descriptor fd into the buffer starting at buf.  [1] https://www.tutorialspoint.com/unix_system_calls/read.htm

In our case, read attempts to read up to 32 bytes from file descriptor fd into the buffer starting at buf. Well, that helps a little bit, but I am still not sure what to do. What the heck, for example, is a file descriptor? Remember that our hint at the beginning of the challenge was also about file descriptors. Let’s look up file descriptors too. In the “File descriptor” Wikipedia article, I immediately see that a file descriptor of zero is associated with standard input. You remember that standard output is printing content to the terminal screen. Standard input in the shell is usually keyboard input. I also see roles for file descriptors 1 and 2, standard output and standard error respectively. Now we know why the program seemed to hang for the specific range of 4660 to 4662. Those numbers minus 4660 result in specified file descriptors 0, 1, and 2, and the program is waiting for further input.

So putting that all together, the source code says to subtract 4660 from argv[1] and set that equal to fd. Again, if we supply 4660 as argv[1], then fd becomes 0. So when read is called, it attempts to read up to 32 bytes from standard input, which is our keyboard input, and save that in buf. So it is expecting us to type in something specific, but what?

Time to examine the last part of the source code. It looks like whatever we type in and gets saved in buf will be compared against the string (text) “LETMEWIN.” The pseduocode syntax reads, “if not strcmp LETMEWIN in buf, tell the user ‘good job’ and print out the password for them.” Or, said another way, if strcmp is false for LETMEWIN in buf, tell the user ‘good job’ and print out the password for them.”

If we look up the strcmp function, we find that it compares two strings to each other (in our case, the string “LETMEWIN” and whatever we type in that gets saved in buf), then returns zero if the two strings are equal, a value less than zero is string 1 is less than string 2, and a value greater than zero if string 1 is greater than string 2. [2]

So if we type the phrase “LETMEWIN,” strcmp compares that to “LETMEWIN” and returns 0 because they are identical. Since strcmp is false (aka 0, aka !strcmp), the boolean expression evaluates to true, which should allow us to see the contents of the “flag” file. Let’s give it a whirl.

We solved the challenge!
Image 8: We solved the challenge and the flag is outputted to the terminal.

It worked! The program told us good job” and showed us the contents of the file, which represents our flag. I won’t print the flag here, but you should feel great for solving this challenge. Way to go!

Please leave any questions/comments/frustrations/concerns below. I would love to hear from you and help you out if you hit a snag with any of the above.




Leave a Reply

Your email address will not be published. Required fields are marked *