7

Im reading the book "Hacking: The Art of Exploitation" and I came across a part of the book where it shows a supposed protection against buffer overflow vulnerabilities by rearranging variables in memory. I tried this and the program is still vulnerable to buffer overflows. Here is the code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int check_authentication(char *password) {
    //*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*
    char password_buffer[16];
    int auth_flag = 0;
    //*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

    strcpy(password_buffer, password);

    if (strcmp(password_buffer, "password1") == 0)
        auth_flag = 1;
    if (strcmp(password_buffer, "password2") == 0)
        auth_flag = 0;

    return auth_flag;
}


int main(int argc, char *argv[]) {
    if (check_authentication(argv[1])) {
        printf("ACCESS GRANTED");
    } else {
        printf("ACCESS DENIED");
    }
}

This program, in short, checks an authentication key provided by the second command line argument and tells the user whether or not access was granted. The main point of focus is on the variables outlined by the comments with the asterisks. Theoredically, this arrangement of variables should prevent a buffer overflow attack as a result of the FILO data structure, meaning the password buffer should be located AFTER the auth_flag variable and therefore should prevent the password buffer from being an execution point for a buffer overflow. Here is an examination of the execution flow in GDB:

(gdb) break 9
 Breakpoint 1 at 0x1188: file auth_overflow2.c, line 9.
(gdb) break 16
 Breakpoint 2 at 0x11d7: file auth_overflow2.c, line 16.

The first step in examining the program is to set breakpoints at line 9 (where the strcpy function is executed, before password authentication) and at line 16 (where the auth_flag is returned and the password authentication has then occurred). Then, we can run the program and examine the memory at breakpoint 1.

(gdb) run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 Starting program: /home/user/Hacking/a.out aaaaaaaaaaaaaaaaaaaaaaaaaaaa

 Breakpoint 1, check_authentication (password=0x7fffffffe484 'a' <repeats 29 times>) at auth_overflow2.c:9
9       strcpy(password_buffer, password);
(gdb) x/s password_buffer
 0x7fffffffe060:    ""
(gdb) x/x &auth_flag
 0x7fffffffe07c:    0x00
(gdb)x/16xw &auth_flag
 0x7fffffffe07c:    0x00000000  0xffffe0a0  0x00007fff  0x55555229
 0x7fffffffe08c:    0x00005555  0xffffe188  0x00007fff  0x00000000
 0x7fffffffe09c:    0x00000002  0x55555270  0x00005555  0xf7e0609b
 0x7fffffffe0ac:    0x00007fff  0x00000000  0x00000000  0xffffe188

As we can see from the output above, the auth_flag is at a proper 0 and the password buffer is empty. The last command in the output above also demonstrates that the auth_flag variable is properly located BEFORE the password buffer, so in theory a buffer overflow shouldn't effect the auth_flag variable in any way. Now lets continue the program and examine the memory after the authentication check.

(gdb) cont
 Continuing.

 Breakpoint 2, check_authentication (password=0x7fffffffe484 'a' <repeats 29 times>) at auth_overflow2.c:16
 16     return auth_flag;
(gdb) x/s password_buffer
 0x7fffffffe060:    'a' <repeats 29 times>
(gdb) x/x &auth_flag
 0x7fffffffe07c:    0x61
(gdb) x/16xw &auth_flag
 0x7fffffffe07c:    0x00000061  0xffffe0a0  0x00007fff  0x55555229
 0x7fffffffe08c:    0x00005555  0xffffe188  0x00007fff  0x00000000
 0x7fffffffe09c:    0x00000002  0x55555270  0x00005555  0xf7e0609b
 0x7fffffffe0ac:    0x00007fff  0x00000000  0x00000000  0xffffe188

As you can see from the output above, the auth_flag variable was somehow changed to a non-zero value even though the password buffer is located after the auth_flag variable.

So, with all of this information in mind, how is it possible that a buffer overflow flows BACKWARD in memory instead of forward?

  • The `password_buffer` array begins at `0x7fffffffe060` while `auth_flag` is begins at `0x7fffffffe07c`. So `auth_flag` is stored *after* `password_buffer` in memory, and can thus be overwritten by a `strcpy` call. – Some programmer dude Jul 22 '19 at 06:01
  • Thank you, this helps to clarify things, but how can I allocate the auth_flag variable before the password buffer in this situation? It seems that no matter how I order the variables, they end up in the same order. –  Jul 22 '19 at 06:04
  • 1
    Put its definition *before* the `password_array` definition? Though there's really no requirement or specification that this will help, a compiler can put local variables wherever it feels like and even rearrange them, or use optimizations that means they won't even be stored at all. Variables placement in memory is an implementation detail and depends on the compiler (including its version) and the targeted platform (the target ABI). – Some programmer dude Jul 22 '19 at 06:09
  • Thank you for clearing this up, this really helps. –  Jul 22 '19 at 06:12
  • 1
    To be sure : that specific example from the book might avoid a buffer overflow attack on some implementations (with some compiler flags), but definitely not all (as you have found out). In fact I'd even go as far as to say that there would be very few implementations that would *not* be at risk with this code (even if only for the possibility of a *stack* overflow). Don't ever allow a buffer to overflow ! – Sander De Dycker Jul 22 '19 at 06:26
  • strcpy is not considered 'safe' as it allows for the somewhat arbitrary reassignment of memory. Instead, you could (should maybe) use strcmp_s which mandates the definition of how big the thing you're copying is. This means you can never copy more than x bytes, avoiding buffer overruns. See. https://stackoverflow.com/questions/32136185/difference-between-strcpy-and-strcpy-s – Ben Jul 22 '19 at 09:40

1 Answers1

1

Some compilers rearrange on-stack arrays so that they are not adjacent to the return address if there are other variables or spill slots in the stack frame, in the hope that limited overflows of a few bytes will no longer be able to reach the return address and thus redirect execution. If these variables are equally critical for security as the return address, this heuristics goes wrong, and in your hypothetical example, this appears to be the case.

Florian Weimer
  • 32,022
  • 3
  • 48
  • 92