Introduction:
Pico CTF’s buffer overflow 0 is an introduction to the concept of buffer overflow vulnerabilities. Buffer overflow vulnerabilities occur when the attacker is able to write a larger amount of data to a buffer of specified size and can then over write the $eip, or the saved return address on the stack, changing how the code would execute.
The Challenge:
The challenge description outlines that our goal is simply to overflow the buffer and provides us with the source code to look at, as well as the executable, and the location of the live service to connect to.
The Code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define FLAGSIZE_MAX 64
char flag[FLAGSIZE_MAX];
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
int main(int argc, char **argv){
FILE *f = fopen("flag.txt","r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(flag,FLAGSIZE_MAX,f);
signal(SIGSEGV, sigsegv_handler); // Set up signal handler
gid_t gid = getegid();
setresgid(gid, gid, gid);
printf("Input: ");
fflush(stdout);
char buf1[100];
gets(buf1);
vuln(buf1);
printf("The program will exit now\n");
return 0;
}
Looking at the code, the first thing it does in main() is it tries to open a flag.txt file to be read, then reads the content of the flag into the flag buffer. After that, it sets up a signal handler to handle if/when a SIGSEGV signal from the kernel, SIGSEGV is the signal name for a segfault. after that it gets the effective gid (or group id) of the process that had called the executable using the getegid() and uses setresgid() to then set the real, effective and saved group ID to what was stored in gid. Now that all the housekeeping is done functionality that the end-user will experience goes into motion by setting up the buffer to read user input at 100 bytes, printing the input prompt, and reading user input into buffer buf1.
The Vulnerability:
During the setup of the function, the program specifies a buffer buf1 to hold 100 bytes of input supplied by the user and then writes what the user supplies to said buffer using gets(). It is important when looking at code to understand from at least a basic point of view what each line does, and in the case of gets() if we look at the man page we can see:
gets – get a string from standard input
Shortly followed by:
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead.
Great! We now know that gets() will read as much as we put into standard input into the buffer, and after reading our input into the buffer, the buffer is passed into the vuln() function.
vuln():
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
Vuln appears to be a simple function that simply takes a string of input, creates a buffer buf2 of size 16 then attempts to use strcpy() to copy the supplied string into buf2. But wait, we know that the supplied buffer can hold up to 100 bytes, and we are trying to copy that into a buffer that only holds 16 bytes? Won’t that go over? Exactly.
Attack Concept:
Essentially what is happening here is the code in vuln is allowing up to fill the contents of buf2 with what we wrote into buf1. In the above illustration, we can see that when the contents of buf1 are written into buf2 it overflows and clobbers the Base Pointer $ebp and even clobbers the saved return address $eip. When this happens, the function vuln() will try to return after copying over the buffer contents, but when it looks for the return address it sees a portion of what was written from buf1 to buf2, will jump to that location in memory, and segfault. Remember that line of code that set up what to do when the process receives a SIGSEGV or a segfault?
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
Well lucky us! When we get a segfault, the program is set up to print out the flag! Now, all we need to do is exploit the vulnerability and get our flag.
Execution:
We can get the source code and the executable using wget:
Then, once we have the code, we can use what we learned about buffer overflows to get our flag.
First, we create a flag for our local instance.
Then, we can try out our exploit. First with 16, then 17, then for good measure, 20.
And just like that, we have got the flag! (I changed the flag.txt contents to look more like a flag) Now all that’s needed is to take what we learned locally and apply that to the remote instance. Happy Hacking!