Search code examples
cmemorygdballocationbuffer-overflow

Why is the variable allocated the same way in both programs?


I have following code to showcase stack-based buffer overflow.

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

  strcpy(password_buffer, password);
  if(strcmp(password_buffer, "Admin") == 0)
    auth_flag = 1;
  return auth_flag;
}

Here when user inputs any string with length greater than 16 will allow access. To show other case of not overflow the auth_flag I have the following code:

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

  strcpy(password_buffer, password);
  if(strcmp(password_buffer, "Admin") == 0)
    auth_flag = 1;
  return auth_flag;
}

As the stack works as LIFO, auth_flag should have a lower address than password_buffer in the second example. GDB with break point at strcpy looks as follows:

(gdb) x/16xw password_buffer
0x61fefc:       0x696d6441      0x7659006e      0xc9da078f      0xfffffffe
0x61ff0c:       0x00000001      0x76596cad      0x00401990      0x0061ff38
0x61ff1c:       0x00401497      0x00ae1658      0x00000000      0x0028f000
0x61ff2c:       0x00400080      0x0061ff1c      0x0028f000      0x0061ff94
(gdb) x/x &auth_flag
0x61ff0c:       0x00000001

I expected the password_buffer to start from 0x61ff10, right after auth_flag. Where I am wrong?

I am using gcc (gcc version 9.2.0 (MinGW.org GCC Build-20200227-1) and gdb (GNU gdb (GDB) 7.6.1) on windows 10 with no modification to SEHOP or ASLR.


Solution

  • As stated in the comments, local variables are not pushed onto and popped from the stack. Instead, when the function call is executed, the runtime allocates some space on the stack for the local variables. It is called Function Prologue and has a known sequence (in many cases - see the comment)

    push ebp
    mov ebp, esp
    sub esp, N
    

    where N is the space reserved for the local variables.

    For some reason, GCC always allocates the memory location [rbp-4] for auth_flag local variable and that's why you do not see any difference (check this vs this). Could be how the compiler is designed...

    On the other hand, clang does what you expect the compiler to do, at least when allocating the place on the stack for your auth_flag local variable. No optimisations are used for the compiler

    check_authentication:                   # @check_authentication
            push    rbp
            mov     rbp, rsp
            sub     rsp, 48
            lea     rax, [rbp - 32]
            mov     qword ptr [rbp - 8], rdi
            mov     dword ptr [rbp - 12], 0
            mov     rsi, qword ptr [rbp - 8]
            mov     rdi, rax
            mov     qword ptr [rbp - 40], rax # 8-byte Spill
            call    strcpy
            mov     esi, offset .L.str
            mov     rdi, qword ptr [rbp - 40] # 8-byte Reload
            mov     qword ptr [rbp - 48], rax # 8-byte Spill
            call    strcmp
            cmp     eax, 0
            jne     .LBB0_2
            mov     dword ptr [rbp - 12], 1
    .LBB0_2:
            mov     eax, dword ptr [rbp - 12]
            add     rsp, 48
            pop     rbp
            ret
    .L.str:
            .asciz  "Admin"
    

    compare the above with the below code where password_buffer is declared before the auth_flag local variable.

    check_authentication:                   # @check_authentication
            push    rbp
            mov     rbp, rsp
            sub     rsp, 64
            lea     rax, [rbp - 32]
            mov     qword ptr [rbp - 8], rdi
            mov     dword ptr [rbp - 36], 0
            mov     rsi, qword ptr [rbp - 8]
            mov     rdi, rax
            mov     qword ptr [rbp - 48], rax # 8-byte Spill
            call    strcpy
            mov     esi, offset .L.str
            mov     rdi, qword ptr [rbp - 48] # 8-byte Reload
            mov     qword ptr [rbp - 56], rax # 8-byte Spill
            call    strcmp
            cmp     eax, 0
            jne     .LBB0_2
            mov     dword ptr [rbp - 36], 1
    .LBB0_2:
            mov     eax, dword ptr [rbp - 36]
            add     rsp, 64
            pop     rbp
            ret
    .L.str:
            .asciz  "Admin"
    

    The mov dword ptr [rbp - XXX], 0 line in above code snippets are where your local auth_flag variable is declared and initialised. As you can see, the reserved location on the stack for the local variable changes based on your buffer size. It is worth compiling your code with clang and debugging it with lldb I think.