Overflow and stack protection

We have mentioned that program exploitation refers to techniques that allow us to “make a program do something unexpected and not planned”. How is this possible?

  • Programs are complex and it is hard to foresee all possible situations;
  • Programming errors can be exploited to obtain unexpected behaviour;
  • When a program is extended to incorporate new functionalities it might happen that these introduce new breaches, since new behaviours (initially unexpected) are introduced.

In general, code does not always do only what the programmer had in mind …

Example 1: off-by-one

in OpenSSH it was discovered a ‘off-by-one’ bug

instead of

This code checks that a channel id is in the expected range. The bug allowed for accessing a memory cell (through overflow that we study in this class), obtaining root privileges.

Example 2: path traversal

Microsoft IIS webserver checked the presence of ‘\’ in the file names to avoid ‘path traversals’ into disallowed directories. When the server was extended to support Unicode developers forgot to check for %5c, that corresponds to ‘\’. Translation from Unicode happened after the backslash check, so ‘path traversal’ attacks were possible.

Buffer Overflow

A buffer overflow occurs when a program overruns a buffer boundary and overwrites adjacent memory. This anomaly frequently happens in C programs. C language, in fact, leaves to the programmer the responsibility of preserving data integrity: there are no checks that variables are stored in the relative allocated memory. This produces very fast programs but run-time errors, such as overflows, are possible.

Example (Overwriting variables)

The following program allocates an integer variable value and two buffers of size 8, buffer1 and buffer2. It prints the variable location and content using %p, %s and %d (plus %08x for hexadecimal format). Then the argument from the command line is copied into buffer1 (line 18). This can clearly go beyond the boundaries of buffer1 in case the argument is bigger than 8. The idea is to observe what happens. To this purpose the program prints again the location and content of the three variables.

Try to execute the program passing a string of gradually increasing size. For example: ‘A’, ‘AA’, ‘AAA’, and so on.

  • Observe the overflow in the output:

    With 7 chars there’s no overflow: only buffer1 is modified:

    With 8 chars the zero bytes terminating the string overwrites buffer2 and string two disappears:

    With 8 more chars we fill buffer2. Notice that buffer1 seems to contain 16 chars even if it is just 8 bytes long (in fact half of it is stored over buffer2):

    Adding one more character reaches the bottom of the stack and the program is terminated (thanks to a protection that we will study in detail):

  • Try to compile the program with option -fno-stack-protector and observe the difference. This option disables protection against overflows on the stack (see below).

    We notice that this time the overflow affects value instead of buffer2: with 7 chars there is no overflow. With 8 chars the value becomes 0 because the string terminator overwrites the least significant byte of the integer value (little endianness). If we add 3 more chars we can see the 0x41 appearing in the integer on the right-hand part (again little endianness). When we add an extra char the zero byte touches the end of the stack and triggers a segmentation fault error.

To understand what happens in the above example it is good to recall how programs are mapped into memory:

Without stack protector, variables typically appears on the stack as they had been pushed, i.e., in reverse order: buffer2, buffer1 and value. Thus when an overflow on buffer1 occurs, this overwrites value but not buffer2.

Stack protector rearranges the position of variables. With -fstack-protector (which is usual enabled by default) the compiler put value before any array so that it cannot be overwritten by overflows. In this specific example it also swaps the relative position of the two buffers but this may vary depending on the compiler and on the alignment of variables on the stack. The following picture illustrates:

Now that you have a better understanding of the relative position of variables you can rerun the example to observe how values are overwritten.

A first exploit: changing program flow

Overwriting variables is, per se, critical. Sometimes it can cause interesting changes in the program flow. Next example shows how this could happen and how variable rearrangement may prevent attacks.

We compile the program with no stack protector (option -fno-stack-protector) and we experiment with inputs of increasing size:

What is happening here? As before, if we give a string longer than the buffer we have an overflow. Next variable in memory is overwritten. In particular auth_flag, becomes different from 0 (which in C means ‘true’). Function check(argv[1]) returns true and the user is authenticated.

Let us see with gdb what is going on on the stack:

Exercise

Set stack protector on and observe that the attack is prevented. Notice the special message given by the program when a long input is given. The overflow does not overwrites the auth_flag because of the rearrangement discussed above. Moreover the program detects the overflow. We will discuss how this is possible during the next class.

Leave a Reply

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