Home > Software design >  Is possible to have a Stack Frame reversed?
Is possible to have a Stack Frame reversed?

Time:07-05

I am trying to understand and learn the stack-based Buffer Overflow Vulnerability, so I wrote the following simple program:

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

int auth(char *password){
    char buffer[16];
    int auth_flag = 0;

    strcpy(buffer, password);

    if(strcmp(buffer, "get_access") == 0)
        auth_flag = 1;

    return auth_flag;
}

int main(int argc, char *argv[]){
    if(auth(*  argv))
        printf("\n=~=~=~=~=~=~=~=\nAccess Granted\n=~=~=~=~=~=~=~=\n\n");
    else
        printf("\n=x=x=x=x=x=x=x=\nAccess Denied\n=x=x=x=x=x=x=x=\n\n");
}

What I don't still get is why the variable auth_flag is after buffer. Shouldn't be the contrary?

Here is my GDB session to clearly understand what I am saying:

(gdb) break 8
 Breakpoint 1 at 0x1168: file overflow.c, line 8.
(gdb) break 13
Breakpoint 2 at 0x1199: file overflow.c, line 13.
(gdb) run $(perl -e 'print "A" x30')
Starting program: /home/computer/Desktop/overflow $(perl -e 'print "A" x30')

Breakpoint 1, auth (password=0x7fffffffe369 'A' <repeats 30 times>) at overflow.c:8

 8      strcpy(buffer, password);
(gdb) x/32xw $rsp
0x7fffffffdeb0: 0x00f0b5ff  0x00000000  0xffffe369  0x00007fff
0x7fffffffdec0: 0xffffdee7  0x00007fff  0x55555235  0x00005555
0x7fffffffded0: 0x00000000  0x00000000  0x00000000  0x00000000
0x7fffffffdee0: 0xffffdf00  0x00007fff  0x555551c1  0x00005555
0x7fffffffdef0: 0xffffe000  0x00007fff  0x00000000  0x00000002
0x7fffffffdf00: 0x555551f0  0x00005555  0xf7e11d0a  0x00007fff
0x7fffffffdf10: 0xffffdff8  0x00007fff  0x00000000  0x00000002
0x7fffffffdf20: 0x5555519e  0x00005555  0xf7e117cf  0x00007fff
(gdb) x/x &auth_flag
0x7fffffffdedc: 0x00000000
(gdb) x/4w buffer
0x7fffffffdec0: 0xffffdee7  0x00007fff  0x55555235  0x00005555
(gdb) print 0x7fffffffdec0 - 0x7fffffffdedc
$1 = -28
(gdb) c
Continuing.

 Breakpoint 2, auth (password=0x7fffffffe369 'A' <repeats 30 times>) at overflow.c:13
13      return auth_flag;
(gdb) x/32xw $rsp
0x7fffffffdeb0: 0x00f0b5ff  0x00000000  0xffffe369  0x00007fff
0x7fffffffdec0: 0x41414141  0x41414141  0x41414141  0x41414141
0x7fffffffded0: 0x41414141  0x41414141  0x41414141  0x00004141
0x7fffffffdee0: 0xffffdf00  0x00007fff  0x555551c1  0x00005555
0x7fffffffdef0: 0xffffe000  0x00007fff  0x00000000  0x00000002
0x7fffffffdf00: 0x555551f0  0x00005555  0xf7e11d0a  0x00007fff
0x7fffffffdf10: 0xffffdff8  0x00007fff  0x00000000  0x00000002
0x7fffffffdf20: 0x5555519e  0x00005555  0xf7e117cf  0x00007fff
(gdb) c
Continuing.

=~=~=~=~=~=~=~=
Access Granted
=~=~=~=~=~=~=~=

[Inferior 1 (process 4465) exited normally]
(gdb) 

As it can be seen print 0x7fffffffdec0 - 0x7fffffffdedc returns a negative value so auth_flag is after buffer. On top of that 0xffffe369 (which is the function argument) is even on top of buffer. It's like the whole stack frame is reversed. Can someone help understand why please?

(The Access should not be granted since the stack is a FILO structure, hence in this form the variable auth_flag should never be overwritten. Instead I was expecting a Segmentation fault error...)

CodePudding user response:

In common implementations, stacks grow from higher to lower addresses. There is no absolute technical need for this; it is merely convention based on history. I imagine that, in the early days of computing, when a general program stack was being created, the program code and some of its data were already in the low-addressed portion of memory, so the stack had to be put somewhere else. If you picked a specific address to start it at, say 200016, and let it grew upward, then that would bound how big the rest of the program (code and data, such as a general memory pool for malloc-like uses) could be—it could not grow beyond 200016 bytes unless you did some extra work to move the stack. Instead, the stack would start at a high address and work downward. Then the program and its other data could grow upward and the stack could grow downward, and the only hard limit would be when they met (thus using all available memory), not an arbitrarily chosen location.

This explains why the value of password is greater than the address of buffer; password points to part of a parameter (argv) in the calling routine, whereas buffer is data in the called routine. The stack grew downward from the calling routine to the called routine.

As for the relative locations of buffer and auth_flag, these are not controlled by the stack direction. The compiler does not necessarily assign locations for local variables based on the order they appear in the source code. It may analyze the entire routine, make its own notes about what variables there are, and organize their locations for efficiency or other purposes. When there are differently sized objects needed different alignments, a compiler will typically attempt to pack them efficiently. This is like bagging groceries at a grocery store. You may traverse the store aisles and put things into your shopping cart in one order, but then they are bagged at check-out in a different order, often trying to arrange them efficiently and safely. So buffer and auth_flag will both be in the stack frame of the auth routine, but they will be in whatever order the compiler chooses for them.

It would be technologically possible to have stacks grow from lower addresses to higher addresses, and this could help mitigate buffer overflow exploits, as exceeding a buffer’s high bound would be more likely to run into unused or unimportant memory rather than into sensitive data such as return addresses. However, a great deal of software would have to be changed, and some hardware might have to be changed to, as built-in push and pop instructions are designed for stacks growing downward.

CodePudding user response:

I think the compiler may layout the local variables on the stack at its own discretion. Or place some of them in registers without stack allocation. The memory layout depends heavily on compiler type, its version and enabled optimizations.

The compiler may postpone the initialization of auth_flag

int auth(char *password){
    char buffer[16];

    strcpy(buffer, password);

    int auth_flag = 0;
    if(strcmp(buffer, "get_access") == 0)
        auth_flag = 1;
    return auth_flag;
}

...and optimize it further...

int auth(char *password){
    char buffer[16];

    strcpy(buffer, password);
    int auth_flag;
    if(strcmp(buffer, "get_access") == 0)
        auth_flag = 1;
    else
        auth_flag = 0;
    return auth_flag;
}

...and finally remove auth_flag completely

int auth(char *password){
    char buffer[16];

    strcpy(buffer, password);
    if(strcmp(buffer, "get_access") == 0)
        return 1;
    else
        return 0;
}
  • Related