CSC373 Oct30

slide version

single file version

Contents

  1. Structures
  2. Using typedef to name types
  3. Size of struct types
  4. Alignment
  5. Alignment and struct types
  6. Linux Example
  7. Windows Example
  8. Practice Problem
  9. Structure type
  10. Pointer to struct type
  11. Array Element Addresses
  12. Assembler for Array Access Formula
  13. Array Element Address with Expression Subscript
  14. Assembler for Array Access Formula with Expression Subscript
  15. Array Access Formula for a 2-dimensional Array
  16. Assembler Code for 2-Dimensional Array Access
  17. Code Example: Initialize a One Dimensional Array
  18. Optimized Version
  19. Buffer Overflow
  20. getbuf's char array is part of the problem
  21. getxs function
  22. Problem with gets (and getsx)
  23. Stack Layout
  24. Attack
  25. What code, where?
  26. How does the new code return to main?
  27. What does our code need to do?
  28. Summary of what needs to be done
  29. How?
  30. Prevention
  31. General Technique
  32. Operating System Protection
  33. Compiler Options for Stack Protection
  34. Linux Stack Position
  35. Linux Stack Position (cont.)
  36. Inspecting Linux Kernel Variables
  37. Using cat to Read/Change File Contents
  38. Reading/Changing the contents of a proc File
  39. Constructing Machine Code
  40. Example
  41. Use objdump

Structures[1] [top]

The following statement defines a type


struct S1 {
  char c1;
  int n;
  char c2;
  double x;
};

Variables can then be defined of this struct type:

  struct S1 s; /* Ugh! */      
    

and members can be referenced and assigned values:

 s.c1 = 'A';
 s.n = 42;
 s.c2 = s.c1;
 s.x = 2.7;
    

S1 is called the structure tag.

The "name" of the type is struct S1, not just S1.

So it is an error to define a variable like this:

 S1 s; /* ERROR */
    

Using typedef to name types[2] [top]

Examples of typedef declarations that give new names to existing type names or to type expressions:

 typedef int id; /* defines id as a type to be the same as int */
 id n;           /* declares n to be of type id (i.e. an int)  */
    

typedef struct {
  char c1;
  int n;
  char c2;
  double x;
} s1_t;

s1_t s;  /* Defines s to be of type s1_t (a struct) */

Size of struct types[3] [top]

What is the size of each of these struct types?


typedef struct {  
  char c1;        
  int n;          
  char c2;        
  double x;       
} s1_t;           
                  

typedef struct {
  double x;     
  int n;        
  char c1;      
  char c2;      
} s2_t;         

Alignment[4] [top]

Linux requires:

Windows requires:

Alignment and struct types[5] [top]

Consider this struct type:

typedef struct {
  char c1;
  int n;
  char c2;
  double x;
} s1_t;
    

It might be different in Windows than in Linux.

Linux Example[6] [top]


structure type: s1_t, total size = 20
Member  Address       Size Padding?           typedef struct {     
    c1  0xbfdbbe70      1                      char c1;           
     n  0xbfdbbe74      4                      int n;             
    c2  0xbfdbbe78      1                      char c2;           
     x  0xbfdbbe7c      8                      double x;          
                 sum = 14                    } s1_t;              
                                                               

structure type: s2_t, total size = 16
Member  Address       Size  Padding?          typedef struct { 
     x  0xbfdbbe78     8                       double x;        
     n  0xbfdbbe80     4                       int n;         
    c1  0xbfdbbe84     1                       char c1;       
    c2  0xbfdbbe85     1                       char c2;       
                sum = 14                     } s2_t;          

Windows Example[7] [top]


structure type: s1_t, total size = 24
Member  Address       Size Padding?          typedef struct {      
    c1  0x0028cd08      1 ?                   char c1;            
     n  0x0028cd0c      4 ?                   int n;              
    c2  0x0028cd10      1 ?                   char c2;            
     x  0x0028cd18      8 ?                   double x;   
                 sum = 14                   } s1_t;               

structure type: s2_t, total size = 16
Member  Address      Size Padding?          typedef struct { 
     x  0x0028cd18   8                        double x;      
     n  0x0028cd20   4                        int n;          
    c1  0x0028cd24   1                        char c1;        
    c2  0x0028cd25   1                        char c2;        
              sum = 14                      } s2_t;          

Practice Problem[8] [top]

Determine the offset of each field, the total size of the structure, and its alignment assuming Linux/IA32.

struct P1:

           i     c     j      d     Total     
offset
size  

struct alignment: 
    

Structure type[9] [top]

Declaring a variable of struct type allocates storage for the struct, but doesn't initialize the struct members.

typedef struct s1 {
  double x;
  int n;
  char c1;
  char c2;
} S1;

S1 rec;

rec.x = 3.5;
rec.n = 0;
rec.c1 = 'a';
rec.c2 = 'b';

    

Pointer to struct type[10] [top]

Declaring a pointer to a struct, does not allocate storage for the struct and does not initialize the pointer.

typedef struct s1 {
  double x;
  int n;
  char c1;
  char c2;
} S1;

S1 *p;

p = (S1 *) malloc(sizeof(S1));

p->x = 3.5;
p->n = 0;
p->c1 = 'a';
p->c2 = 'b';

/* or you could write */

(*p).x = 3.5;
(*p).n = 0;
(*p).c1 = 'a';
(*p).c2 = 'b';
      
    

Array Element Addresses[11] [top]

The assembler code to access an element of a one dimensional array needs to calculate the address of this element.

The compiler will know the beginning address of the array.

The calculation may depend on whether the subsript is a constant or an expression.

 int a[5];

 address(a[3]) = address(a[0]) + (number of elements that come before a[3]) * size(int)
    

But since array subscripts start at 0, the number of elements before a[3] is 3: a[0], a[1], and a[2]

So the array access formula for a[3] is

 int a[5];

 address(a[3]) = address(a[0]) + 3 * size(int) = address(a[0]) + 3 * 4
               = address(a[0]) + 12

    

Assembler for Array Access Formula[12] [top]

Assume the array address (i.e., address of a[0]) is

 -72(%ebp)
    

What assembler code would do this:

 a[3] = 100;
    

Answer: First the address of a[3] would be

  -60(%ebp)
    

So the compiler could generate

 movl $100, =60(%ebp)
    

Array Element Address with Expression Subscript [13] [top]

The array access formula with a variable expression subscript isn't much different:

 int a[5];
 int i = ...; // 

 address(a[i]) = address(a[0]) + (number of elements before a[i]) * sizeof(int)
 
               = address(a[0]) + i * 4
    

Assembler for Array Access Formula with Expression Subscript[14] [top]

Assume again that the array address (i.e., address of a[0]) is

 -72(%ebp)
    

and the variable i is stored in memory at:

 -4(%ebp)
    

What assembler code would do this:

 a[i] = 100;
    

Answer: First the address of a[i] would be

  -72(%ebp) + 4 * i
    

But the compiler could have generate code that does the multiplication during execution since the value of i is not a constant.

 movl -4(%ebp), %eax
 movl $100, -72(%ebp, %eax, 4)
    

Array Access Formula for a 2-dimensional Array[15] [top]

Two dimensional arrays in C are stored in row-major order:

 int a[4][5];      
    
Row 0 Row 1 Row 2 Row 3
                                       

The last element of row 0 is a[0][4].       

The next element in memory is the first element of row 1: a[1][0]       .

  address(a[i][j]) = (number of rows before row i) * sizeof(a row) +
                     (number of elements before a[i][j] in row i) * sizeof(element)      
    

So for this int array with 4 row and 5 columns

  address(a[i][j]) = i * (5 * size(int)) + j * sizeof(int)
                   = i * 20 + j * 4
    

Assembler Code for 2-Dimensional Array Access[16] [top]

 int a[4][5];      
    

Assume that the array address (i.e., address of a[0][0]) is

 -72(%ebp)
    

and the variables i and j are stored in memory at:

i:  -8(%ebp)
j:  -4(%ebp)
    

What assembler code would do this:

 a[i][j] = 100;
    

Answer: First the address of a[i][j] would be

  -72(%ebp) + 20 * i + 4 * j

or

  -72(%ebp) + 4 * (5 * i + j)
    

The second form is more convenient for using scaled index mode operands

 leal   -72(%ebp), %ecx          ; %ecx: address of a[0][0]
 movl   -8(%ebp), %eax           ; %eax: i
 imull  $5, %eax                 ; %eax: 5 * i
 addl   -4(%ebp), %eax           ; %eax: 5 * i + j
 movl   $100, (%ecx, %eax, 4)    ; a[i][j] = 100
    

where the scaled index address is


 (%ecx, %eax, 4): address(a[0][0]) + 4 * (5 * i + j)
    

Code Example: Initialize a One Dimensional Array[17] [top]


void init(int n, int a[n])
{
  int i;
  for(i = 0; i < n; i++) {
    a[i] = 5;
  }
}

Not optimized code generated. (doesn't use scaled index for the array element):


init:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $16, %esp
        movl    $0, -4(%ebp)    ; i: -4(%ebp), i = 0
        jmp     .L2             ; goto L2
.L3:
        movl    -4(%ebp), %eax  ; %eax:  i
        sall    $2, %eax        ; %eax:  i << 2 = 4 * i
        addl    12(%ebp), %eax  ; %eax:  4 * i + address(a[0])
        movl    $5, (%eax)      ; a[i] = 5
        addl    $1, -4(%ebp)    ; i++
.L2:
        movl    -4(%ebp), %eax  ;
        cmpl    8(%ebp), %eax   ; if i < n goto L3
        jl      .L3
        leave
        ret

Optimized Version[18] [top]


void init(int n, int a[n])
{
  int i;
  for(i = 0; i < n; i++) {
    a[i] = 5;
  }
}

Optimized code generated (uses scaled index for the array element):

( gcc -O2 -S init.c)

init:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %ecx   ; %ecx:  n
        movl    12(%ebp), %edx  ; %edx:  address(a[0])
        testl   %ecx, %ecx      ;
        jle     .L5             ; if (n <= 0) goto L5
        xorl    %eax, %eax      ; %eax: i = 0
.L4:
        movl    $5, (%edx,%eax,4) ; a[i] = 5
        addl    $1, %eax        ; i++
        cmpl    %ecx, %eax      ;
        jne     .L4             ; if (i != n) goto L4
.L5:
        popl    %ebp
        ret
      
    

Buffer Overflow[19] [top]

(See section 3.12 in 2nd ed. or section 3.13 in 1st ed.)

What does this function do?

It appears to

But there is a problem with the character array that holds the user input!

    1   #include <stdio.h>
    2   #include <stdlib.h>
    3   
    4   int getbuf()
    5   {
    6     char buf[12];
    7     getxs(buf);
    8     return 1;
    9   }
   10   
   11   int main()
   12   {
   13     int val;
   14     val = getbuf();
   15     printf("getbuf returned 0x%08x\n", val);
   16   
   17     return 0;
   18   }

getbuf's char array is part of the problem[20] [top]

    1   int getbuf()
    2   {
    3       char buf[12];
    4       getxs(buf);
    5       return 1;
    6   }

getxs function[21] [top]



  The C-library function to read an input line (discarding the
  newline) is  
  
              gets(char *str) 

  char line[50];

  gets(line); // Reads input line up to newline, stores in line
              with a null byte at the end 


  The function getsx is like gets, except that 

  Characters are typed as pairs of hex digits. 

  Non hex digit characters are ignored by getsx.
  
  getsx stops when a newline is encountered.

Problem with gets (and getsx)[22] [top]

The only parameter passed to gets (or getsx) is a character array to be filled from the input line.

What is actually pushed on the stack is the beginning address of the caller's character array.

No parameter is passed giving the size of this array.

So gets (and getsx) have no way of checking if there is enough room in the array for all the input characters.

Stack Layout[23] [top]

When getsx is called by getbuf, the stack frames look like this:

                 +-------------+
           esp ->|             |
                 |             |
                 |             |   getsx's stack frame
                 |             |
           ebp ->|getbuf's ebp |
                 +-------------+
                 |ret addr     |
                 +-------------+
                 |   parameter |   passed to getsx 
                 +-------------+
                 |             |
                 |             |
                 |             |   getbuf's stack frame
                 | main's ebp  |
                 +-------------+
                 |ret addr     |
                 +-------------+
                 |             |
                 |             |
                 |             |   main's stack frame
                 |             |
                 |             |
                 +-------------+


Attack[24] [top]

1. The getbuf function calls getsx to read input into the char
    array, buf of size 12.

2. But getbuf ignores what is copied into the buf array and
   simply returns the integer 1.

3. By typing in more than 12 characters the buf array will
   overflow, with the extra input being copied into memory
   after the array storage.

Goal: By cleverly choosing the input characters to
enter, get new code to execute so that getbuf returns the integer value

         0xdeadbeef

instead of 1.

What code, where?[25] [top]

1. The getsx function lets you type in characters such as 3f 1a 05 and
   converts each pair of characters to the a single 8 bit byte with
   the corresponding hex value; e.g., 0x3f, 0x1a, 0x05.

2. Devise an input string that is the byte encoding of a function that
   simply returns the value 0xdeadbeef.

3. Key: Pad this input string so that it overwrites the char
   array in getbuf and in fact overwrites the return address on the
   stack:

                 +-------------+
                 |             | <- input string will be stored in here
                 |             |
                 |             |   getbuf's stack frame
                 | main's ebp  |
                 +-------------+
                 |ret in main  |  Overwrite this address with a new one
                 +-------------+
                 |             |
                 |             |
                 |             |   main's stack frame
                 |             |
                 |             |
                 +-------------+

4. The new return address should simply be the address back in
   getbuf's stack frame where the input string is stored in the buf
   array. 

How does the new code return to main?[26] [top]


1. In overwriting the return address to main with a new return
   address, main's ebp value will also be overwritten since it is just
   above the return value on the stack:

                 +-------------+
                 |             | <- input string will be stored in here
                 |             |
                 |             |   getbuf's stack frame
                 | main's ebp  |
                 +-------------+
                 |ret in main  |  Overwrite this address with a new one
                 +-------------+
                 |             |
                 |             |
                 |             |   main's stack frame
                 |             |
                 |             |
                 +-------------+

2. So the input string must also have main's ebp and the new ret
   address as the last 8 bytes.

3. When getbuf returns it will pop its stack frame as usual. We
   haven't altered anything to affect that and the top of the stack
   will contain main's stack frame.

4. However, our code will be executing instead of main.

What does our code need to do?[27] [top]

1. Build a stack frame for itself, including the part that looks like
   main called it.

2. So, our code first needs to push the return address in main (as if the call
   instruction had executed).

3. Then it can proceed as normal as if it were written like this:


    1   int attack()
    2   {
    3     return 0xdeadbeef;
    4   }


Summary of what needs to be done[28] [top]

1. Determine the ebp value for main, the caller of getbuf.

2. Determine the beginning address of the character array buf
   in getbuf that will be loaded and that will overflow and overwrite
   getbuf's stack frame.

3. Determine the byte code for our attack function.

4. Build the input string as this byte code plus padding bytes if
   necessary plus main's ebp plus the beginning address of our code
   (i.e., the beginning address of the character array).

How?[29] [top]

1. Use gdb to determine ebp value and beginning address of character
   array. 

2. Use gcc -S to generate the assembly code for the attack
   function.

3. Copy the code and add the pushl instruction to push main's return
   address on the stack in a file named attack2.s. Then use gcc -c
   attack2.s to assemble this code in to object file attack2.o

4. Use objdump on the object file attack2.o to get the byte encoding
   of the function we want.

5. Append padding bytes (00 or 90) if necessary to the bytes from 4
   and then add main's ebp value and the beginning address of the char
   array buf where this code will be copied by getsx.

Prevention[30] [top]

Here are three attempts to prevent this buffer overflow problem:

General Technique[31] [top]

The gcc compiler generates code so that for each execution a random value is placed in the stack frame after the array storage.

A check can be made to see if this value has changed.

This so called canary word is inserted depending on various properties detected in the function, including if it declares an array of size 8 or more.

So this technique will check other functions, not just the "dangerous" C library function gets.

Operating System Protection[32] [top]

The operating system is responsible for providing the ability to execute programs.

Linux can put the stack at a different beginning location each time the program is run.

This makes it harder to determine where your input string will be stored on the stack since it will vary each time the program is run.

So it will be difficult to provide a "return" address in the input attack string that will cause it to jump to the attack code.

Compiler Options for Stack Protection[33] [top]

The gcc compiler by protects the stack if the option stack-protector is on.

This option can be turned on by using the option:

        gcc -fstack-protector prog.c -o prog
      

The compiler is typically installed with this option being the default.

It can be turned off by using the option:

        -fno-stack-protector

Example:
        gcc -fno-stack-protector prog.c -o prog

      

Linux Stack Position[34] [top]

Linux provides access to some operating system (kernel) data structures directly through "file-like" access.

The proc file system uses file operations to provide names and access to certain kernel data.

There is a "directory" named proc and its path is /proc.

The directory contains other directories and pseudo files.

A pseudo file has a name. You can list the pseudo file and list its contents, provided the permissions allow it.

However, listing such a file often shows that it contains 0 bytes, eventhough listing its contents shows some data.

Linux Stack Position (cont.)[35] [top]

Linux will place the stack at different random starting positions each time a program is run if the kernel variable

        randomize_va_space
      

has non-zero value.

How do you know its value?

How do you change its value?

Inspecting Linux Kernel Variables[36] [top]

The kernel variable, randomize_va_space is accessible through a pseudo file of the same name.

It is located in the proc file system at:

        /proc/sys/kernel/randomize_va_space
      

Listing this "file" shows:

Using cat to Read/Change File Contents[37] [top]

Read input from a file, say in.txt, and display on standard output:

        cat in.txt
      

Read input from keyboard (standard input) and display on standard output, type ctrl-d to terminate input.

        $ cat 
          hello, world typed input
          hello, world output from cat
          goodbye      typed input
          goodbye      output from cat
          ctrl-d       signal end of input to cat
      

Read input from key board (standard input) and redirect output to a file, say out.txt:

        $ cat >out.txt
          hello, world  typed input
          goodbye       typed input
          ctrl-d        signal end of input to cat; closes file out.txt
      

Note: Redirecting output to out.txt will first delete the contents of out.txt if it already exists.

Reading/Changing the contents of a proc File[38] [top]

Since we have read acces to randomize_va_space, we can use the Linux cat command to display a file (and a pseudo file):

        $ cat /proc/sys/kernel/randomize_va_space
      

The output goes to standard output. Its value is probably not 0. That means that the stack position will be randomized on each execution.

We could try to change its value by

        $ cat >/proc/sys/kernel/randomize_va_space
          0
          ctrl-d
      

But to do so you would need to "be" (i.e. login as ) the root user. Bummer!

Constructing Machine Code[39] [top]

There are several ways to determine the sequence of bytes that make up a program function.

Example 1
Example 2

Same as above, but write assembler instructions instead of a C function

Example[40] [top]

Write a file mycode.s

        pushl $0x08043dac
        pushl $0xfd400500
        movl  %esp, %ebp
        movl $5, %eax
        popl %ebp
        ret
      

Assemble mycode.s to produce mycode.o (use gcc):

        $ gcc -c mycode.s
      

Use objdump[41] [top]

Use objdump to produce an assembler listing, but which includes the machine byte representation of each instruction (redirect output to a different .s file):

        $ objdump -d mycode.o >mycode_obj.s
      

Here is mycoe_obj.s

   0:   68 ac 3d 04 08          push   $0x8043dac
   5:   68 00 05 40 fd          push   $0xfd400500
   a:   89 e5                   mov    %esp,%ebp
   c:   b8 05 00 00 00          mov    $0x5,%eax
  11:   5d                      pop    %ebp
  12:   c3                      ret

The machine program code is 18 (0x12) bytes long and (ignoring blanks) is:

68 ac 3d 04 08 68 00 05 40 fd 89 e5 b8 05 00 00 00 5d c3