为什么变量在两个程序中的分配方式相同?
Why is the variable allocated the same way in both programs?
我有以下代码来展示基于堆栈的缓冲区溢出。
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;
}
此处当用户输入任何长度大于16的字符串时将允许访问。为了显示不溢出 auth_flag
的其他情况,我有以下代码:
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;
}
由于堆栈作为 LIFO,auth_flag
的地址应该低于第二个示例中的 password_buffer
。断点在 strcpy
的 GDB 如下所示:
(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
我预计 password_buffer
从 0x61ff10
开始,就在 auth_flag
之后。我哪里错了?
我在 windows 10 上使用 gcc(gcc 版本 9.2.0(MinGW.org GCC Build-20200227-1)和 gdb(GNU gdb (GDB) 7.6.1),没有修改到 SEHOP 或 ASLR。
如评论中所述,局部变量不会被压入堆栈或从堆栈中弹出。相反,当执行函数调用时,运行时会在堆栈上为局部变量分配一些 space。它被称为 Function Prologue 并且具有已知序列(在许多情况下 - 请参阅评论)
push ebp
mov ebp, esp
sub esp, N
其中 N
是为局部变量保留的 space。
出于某种原因,GCC 总是为 auth_flag
局部变量分配内存位置 [rbp-4]
,这就是为什么您没有看到任何差异(检查 this vs this)。可能是编译器的设计方式...
另一方面,clang 执行您希望编译器执行的操作,至少在为您的 auth_flag
局部变量分配堆栈位置时。编译器没有使用优化
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"
将上面的代码与下面的代码进行比较,其中 password_buffer
在 auth_flag
局部变量之前声明。
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"
上面代码片段中的 mov dword ptr [rbp - XXX], 0
行是声明和初始化本地 auth_flag
变量的地方。如您所见,局部变量在堆栈上的保留位置会根据您的缓冲区大小而变化。我认为用 clang 编译你的代码并用 lldb 调试它是值得的。
我有以下代码来展示基于堆栈的缓冲区溢出。
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;
}
此处当用户输入任何长度大于16的字符串时将允许访问。为了显示不溢出 auth_flag
的其他情况,我有以下代码:
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;
}
由于堆栈作为 LIFO,auth_flag
的地址应该低于第二个示例中的 password_buffer
。断点在 strcpy
的 GDB 如下所示:
(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
我预计 password_buffer
从 0x61ff10
开始,就在 auth_flag
之后。我哪里错了?
我在 windows 10 上使用 gcc(gcc 版本 9.2.0(MinGW.org GCC Build-20200227-1)和 gdb(GNU gdb (GDB) 7.6.1),没有修改到 SEHOP 或 ASLR。
如评论中所述,局部变量不会被压入堆栈或从堆栈中弹出。相反,当执行函数调用时,运行时会在堆栈上为局部变量分配一些 space。它被称为 Function Prologue 并且具有已知序列(在许多情况下 - 请参阅评论)
push ebp
mov ebp, esp
sub esp, N
其中 N
是为局部变量保留的 space。
出于某种原因,GCC 总是为 auth_flag
局部变量分配内存位置 [rbp-4]
,这就是为什么您没有看到任何差异(检查 this vs this)。可能是编译器的设计方式...
另一方面,clang 执行您希望编译器执行的操作,至少在为您的 auth_flag
局部变量分配堆栈位置时。编译器没有使用优化
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"
将上面的代码与下面的代码进行比较,其中 password_buffer
在 auth_flag
局部变量之前声明。
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"
上面代码片段中的 mov dword ptr [rbp - XXX], 0
行是声明和初始化本地 auth_flag
变量的地方。如您所见,局部变量在堆栈上的保留位置会根据您的缓冲区大小而变化。我认为用 clang 编译你的代码并用 lldb 调试它是值得的。