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.
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.