首页 > 解决方案 > 为什么两个程序中的变量分配方式相同?

问题描述

我有以下代码来展示基于堆栈的缓冲区溢出。

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第二个示例中的地址。带断点的 GDBstrcpy如下所示:

(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。

标签: cmemorygdballocationbuffer-overflow

解决方案


如评论中所述,局部变量不会被压入堆栈并从堆栈中弹出。相反,在执行函数调用时,运行时会在堆栈上为局部变量分配一些空间。它被称为函数序言并且有一个已知的序列(在许多情况下 - 见评论)

push ebp
mov ebp, esp
sub esp, N

其中N是为局部变量保留的空间。

[rbp-4]出于某种原因,GCC 总是为局部变量分配内存位置auth_flag,这就是为什么你看不到任何区别(检查thisthis)。可能是编译器的设计方式......

另一方面,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 调试它。


推荐阅读