Buffer Overflows

DISCLAIMER: Written for educational purposes only. Use at your sole discretion. The author is not in any way liable for the (mis)use or accuracy of the information presented here.

Smash that stack!

An introduction to the inner workings of buffer overflows is given. C and C++ languages do not enforce bounds checking, leaving it as a task for the programmer. If the programmer neglects to do so, for whatever reason, and if the program happens to receive user input, then it is possible to craft that input in order to fiddle with the program’s execution. This fiddling ranges from a simple segmentation fault, to arbitrary code execution. Here the focus will be the latter. Note that this kind of problem, buffer overflows, affects a very wide range of programs (a quick google search should prove that), written by an equally wide range of programmers, from novice to experts. It is a pervasive effect of the flexibility provided by both C and C++. And the more complex the software is, the worse. My point is, do not underestimate the easiness of letting a problem like this slip in your own code.

The gory details

It all started with a lecture from a former teacher, about a specific aspect of software security[1], viz. buffer overflows (also called buffer overruns). I had tried to run the code provided in [1] by myself (in Linux), but I’ll I got was pretty dull “stack smashing detected (core dumped)” error message (I used GCC-4.1, which includes SSP by default). (Although the screenshots shown in [1] were taken in Windows, the code was run from an Unix emulator.) Before continuing, you might want to at least take a quick glance at [1], it’ll help to better understand some of the examples given below. As I could not do anything fun with the code (or so I tought at the time), I stopped tinkering with it and started looking for something else to do… Meanwhile, I got my hands on this book, which despite being written by Microsoft employees (or, God forbid, because of it?), was a good book–or so I’d been told. So I started reading it, and as all good secure coding books, this one has a chapter devoted to buffer overflows (nicknamed there the “Public Enemy Number One”). Early in that chapter, was a C code snippet similar to the one given in [1]. More due to boredom that anything else, I typed that code and started playing with it– or so I planned. Funny thing, the said book, albeit so far being a good book, it was still written by MS folks, so surprise surprise, the code is Windows specific! In particular, it tried to print the current stack like this: printf(“Now the stack is this: \n%p\n%p\n%p\n%p\n%p\n\n”); This does not work on Linux; I assume it works in Windows, but I have not tested it… Anyhow, this reminded me of that code in the aforementioned presentation, and so I decided to try to better understand how it worked, even if I could not do the overflow part per se. I googled for x86 stack, and among the first set of results, were to entries to Wikipedia… each of which depicts the stack differently! Like there is nothing better than trying to understand things by oneself (may there be the time), I started tweaking said code. And the first thing I found out was interesting indeed! The SSP I talked earlier about, that actually kept the code from overflowing (so to speak…), well, I got rid of it by just add ing some variables! You know, that kind of local variables you define inside a function or method? All I had to do was add one and give it a value (not even using was necessary!), and the program started overflowing! I.e. the memory violations were no longer prevented. So long for SSP… Anyway, the next thing I did was dump there a bunch of variables, to try figuring out the structure of the stack; but we’re getting ahead of ourselves. First, let’s demonstrate how to execute the attack. In order to do this, I’ll first show the look of a typical program execution. This the final code form: echo2.c.

#include
#include 

void print_it(int param1, int param2)
{
    //int *ptr = malloc(10 * sizeof (int));
    unsigned int a=10;
    unsigned int c=12;
    unsigned int b=11;
    char s[4]; // 3 chars plus null
    char s1[4]; // 3 chars plus null

    printf("\nStack Before the call:\n");
    printf("    \t %08X \n", *((int*)(s-4)));
    printf("    \t %08X \n", *((int*)(s+0)));
    printf("    \t %08X \n", *((int*)(s+4)));
    printf("    \t %08X \n", *((int*)(s+8)));
    printf("    \t %08X \n", *((int*)(s+12)));
    printf("    \t %08X \n", *((int*)(s+16)));
    printf("    \t %08X \n", *((int*)(s+20)));
    printf("    \t %08X \n", *((int*)(s+24)));

    scanf("%s", s);
    printf("buffer s: %s\n", s);
    scanf("%s", s1);
    printf("buffer s1: %s\n", s1);

    printf("Stack after the call:\n");
    printf("    \t %08X \n", *((int*)(s-4)));
    printf("    \t %08X \n", *((int*)(s+0)));
    printf("    \t %08X \n", *((int*)(s+4)));
    printf("    \t %08X \n", *((int*)(s+8)));
    printf("    \t %08X \n", *((int*)(s+12)));
    printf("    \t %08X \n", *((int*)(s+16)));
    printf("    \t %08X \n", *((int*)(s+20)));
    printf("    \t %08X \n", *((int*)(s+24)));

    printf ("\nvalue of b: \t %X\n", b);
}

void hack(void)
{
    printf("\n*** I've been hacked! ***\n\n");
    system("ls -l");
    printf("Done!\n");
}

int main(void)
{
    printf("Address of print_it: \t %p\n", print_it);
    printf("Address of hack: \t %p\n", hack);
    //printf("size of unsigned int: %d\n", sizeof(unsigned int));
    print_it(13, 14);
    return 0;
}

Before depicting a typical execution (and the buffer overflow afterwords), the code shall be shortened, by comment ing lines 7-9, 11, 14, 20, 21, 25, 26, 29, 35, 36 and 38. These were added to attempt to uncover the x86 stack schema, more on that later on. OK, here’s the screenshot:

Before the attack

Before the attack

Now for a little explaining: as you should know if you read [1], print_it is the function that dumps all the output shown in the screenshot, while hack is the (in this case not very evil) function we want to execute when print_it returns (instead of just given control back to main). The stack is printed as follows: each row corresponds to 4 bytes (32 bits), also known as a word. The printf statements used print the contents as hexadecimal digits, so in the screenshot one byte (which in x86 is also the length of char datatype) corresponds to two characters. So the first row is the s buffer, the second row I’m not sure of what it is (frame pointer maybe?), the third row is the return address (the Holy Grail! ; remember we’re talking about x86: addresses have 32 bit length!), and the last two rows are the integer parameters given to print_it (integers also have 32 bits length in this platform). In hexadecimal, the code for the character A is 41, for B is 42, and for C is 43. On the other hand, the hexadecimal value for the decimal number 13 is D; idem for 14 and E. This explains the value of the last two rows, and also the values in the s buffer after the call (before the call the s buffer contains only garbage). Note that each row is filled from right to left! So in the stack after call, the rightmost number is 41 (A, the first input character), and similarly for the remaining characters. Note also that in C, strings (i.e. char arrays) are null terminated! If the input consists of 4 characters, than the next byte in the stack (i.e. the two rightmost characters in the next row, in this case D8, will be overwritten with 00 (two zeros). From what’s been said, the reader by now has likely been able to ascertain the way in which memory addresses grow: from right to left, and from the uppest row to the bottomest. If there are even more than 4 input characters, well we’ll get there in a while🙂

Lastly, note that before and after reading the input string, the return address remained unchanged! The purpose of the exploit is precisely to change it so it points to malicious code (the hack function in this example).

THE ATTACK: so, what we want to do, is too put the address of hack in the memory that contains the return address of the function print_it, so that when print_it returns, hack gets executed. To accomplish this, we just provide a string that is long enough to overwrite the s buffer, the row below it, and the return address. As the echo2 does not do any kind of bounds checking, the part of the string that is longer than the size of the buffer will happilly overwrite the memory adjacent to the s buffer. We build that string in such a way that the portion of the string that will overwrite the return address has the return address of the hack function. This is how we do it:

The Attack

The Attack

Here’s what happened: as the address of hack contains non printable characters, we used the echo command with the -e handle (allows interpretation of backslash escapes) to feed the fatal string to our echo2 program. That string consisted of 8 A’s, to overwrite the memory from the s buffer until the return address of print_it. And now comes the fun part: we use the \x escape to tell the echo command we’re entering hexadecimal values. Because rows are filled from right to left, we pairwise reverse the address of hack, and feed it to echo2. print_it executes, returns, … and presto! we’ve been hacked! Which in this case amounts to execute the ls command, but as stated in [1], IT COULD BE A LOT WORSE!. Of course the program ended up segfaulting, but if instead of ls, the hack function executed something like “/bin/bash”, after the segmentation fault, the attacker would still have a fully fledged shell to work with… As part of the aftermath, note that the null byte was appended to the string: it overwrote the value of the first argument of print_it (13, or 0D in hexadecimal). This example was unrealistically simple. Real life stuff implies a much greater knowledge of the execution stack inner works, subject of the remainder of this writing, to be done someday…