8 - Buffers

Lecture Notes

This lecture will present an overview over issues involving Buffers overflow, underflow, or otherwise incorrectly used.

Download here

Practical tasks

This guide will complement the lecture slides and present code and descriptions to enable exploitation of buffer vulnerabilities.

Memory Structure

Different variable types will be allocated to different memory areas. This is intrinsic of each program and is broadly defined by how the program is compiled. When the program is loaded, the addresses may change, but they will still respect this notion of areas.

The following program mem.c will print the address of several variables that you may find in a program. Some variables are local, some are global, some are static, some are dynamic. Then you also have program arguments and functions. During the creation of a program the programmer will decide how to declare a variable, and this will have some impact on where the variable is placed in memory.

const char cntvar[]="constant";
static char bssvar[4];
...

int main(int argc, void** argv) {
        FILE* fd;
        char line[1024];
        unsigned int mask;
        unsigned int stack = (unsigned int) &argc;
        unsigned int heap = (unsigned int) malloc(sizeof(unsigned int));
        unsigned int bss = (unsigned int) bssvar;
        unsigned int cnst = (unsigned int) cntvar;
        unsigned int text = (unsigned int) &main;
        memset(&mask,0xff,sizeof(mask));
        mask ^= getpagesize() -1;
        printf("Internal Variables (Page = %u)\n", getpagesize());
        printf("&argc  = %08x -> stack = %08x\n", stack, stack & mask);
        printf("malloc = %08x -> heap  = %08x\n", heap, heap & mask);
        printf("bssvar = %08x -> bss   = %08x\n", bss, bss & mask);
        printf("cntvar = %08x -> const = %08x\n", cnst,cnst & mask);
        printf("&main  = %08x -> text  = %08x\n", text,text & mask);

You can compile the program with gcc -o mem mem.c.

Task:

  • Compile and run the program
  • Match the addresses printed with the different variable types
  • Change the location of a variable, or create others of the same type, and see how it affects the resulting address.

The program also allocates memory in the program stack, by calling a function recursively until all memory is exhausted.

void foo(int argc, unsigned int mask, unsigned int c, unsigned int m)
{
    char a[4096*0x100];
    unsigned int stack = (unsigned int) &argc;

    printf("foo [%03u]: &argc  = %08x -> stack = %08x\n",c,stack, stack & mask);
    if(c < m)
        foo(argc,mask,c+1, m);
}

Each new function will allocate a variable stack with the value of the argc argument (this could be avoided, and is here for clarification), and then allocates a variable named a of size 4096 * 0x100. 4096 (or 0x1000) is the standard page size, while 0x100 will set the number of pages. The larger this value, the quicker the program exhausts all memory.

A possible result would be:

foo [000]: &argc = bfeb8140 -> stack = bfeb8000
foo [001]: &argc = bfdb8110 -> stack = bfdb8000
foo [002]: &argc = bfcb80e0 -> stack = bfcb8000
foo [003]: &argc = bfbb80b0 -> stack = bfbb8000
foo [004]: &argc = bfab8080 -> stack = bfab8000
foo [005]: &argc = bf9b8050 -> stack = bf9b8000
foo [006]: &argc = bf8b8020 -> stack = bf8b8000
foo [007]: &argc = bf7b7ff0 -> stack = bf7b7000
foo [008]: &argc = bf6b7fc0 -> stack = bf6b7000

You should notice that stack allocation grows from higher address to lower addresses. Depending on your system configuration, addresses presented may be constant or slightly random.

Tasks:

  • Take notice of how the addresses in your system, and how memory usage evolves.
  • Run it multiple times
  • Also look at

Variable allocation

Program state is considered to be ephemeral and resides in memory areas specifically allocated for this purpose. Each function will allocate a new stack frame with local variables, and in some calling conventions, arguments to other functions called. Although when developing an application we use variables with specific names, when the code is compiled, variables are only memory spaces. If the language as weak, or no memory management features, access may be totally unconstrained and writing before or beyond the variable start may be a problem.

Considering the following program (also available here ), it declares two variables buffer and message. buffer is a char array with 5 bytes, while message is an array initialized to Hello World.

The for cycle present will write the value A to buffer, but instead of writing only 5 bytes, it will write 15 bytes. The question that arises is, where are these bytes going to? The program also prints the variable message before and after the the cycle, so it may help us finding this.

To check what happens, save to code to bo.c, compile the program with gcc -o bo bo.c, and execute it ./bo. What you will see is a basic overflow, but more on this later.

#include <stdio.h>

void main(int argc, char* argv[]){
    char message[] = "Hello World";
    
    char buffer[5];
    int i;

    printf("buffer=%s message=%s\n", buffer, message);
    
    for(i = 0; i < 15; i++) { 
        buffer[i] = 'A';
    }

    printf("buffer=%s message=%s\n", buffer, message);
}

Another file available will also print the value of several variables. Can be used to see how location declaration affects actual memory allocation.

Task:

  • Compile the program and execute it.
  • What can you conclude about memory structure of these variables
  • Instead of filling the buffer with A, fill it with a variable value (e.g. 'A' + i)
Previous
Next