【问题标题】:Why is the variable allocated the same way in both programs?为什么两个程序中的变量分配方式相同?
【发布时间】:2020-05-11 09:50:45
【问题描述】:

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

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。

【问题讨论】:

  • 我不知道 GCC 如何将局部变量映射到堆栈位置的细节,但您所指的 LIFO 行为通常是指推送和弹出,而这不是局部变量的存储方式。在那里,编译器通常会计算在入口处保留的单个内存块。我知道的大多数编译器都按照声明的顺序密切分配本地变量,从堆栈上的较高地址到较低地址,但我认为c 的标准中没有指定一种或另一种方式。
  • 您的意思可能是“分配”而不是“分配”。您需要注意,局部变量不会单独或按给定顺序推送到堆栈上。相反,所需的内存量只是在函数入口的堆栈上分配。如何在该内存区域中对变量进行排序取决于编译器。
  • 如果你想保证一个东西在内存中物理上跟随另一个,把它们放在同一个包含结构中。如果它们在函数中独立声明,那么它们可以按任何物理顺序分配,并且它们之间可能会出现其他东西。事实上,如果编译器优化了你的代码,那么auth_flag 可能会保存在一个寄存器中,甚至没有内存地址。结构成员也可能发生同样的情况。
  • 如果堆栈中存在auth_flag,我确实会感到惊讶。根据架构/调用约定,它可能只是将结果放在eax/r0 寄存器或类似的东西中。正如您可以see here 一样,从源代码中删除标志会生成相同的指令。

标签: c memory gdb allocation buffer-overflow


【解决方案1】:

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

push ebp
mov ebp, esp
sub esp, N

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

出于某种原因,GCC 总是为auth_flag 局部变量分配内存位置[rbp-4],这就是为什么您看不到任何差异的原因(检查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_bufferauth_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"

上面代码 sn-ps 中的 mov dword ptr [rbp - XXX], 0 行是声明和初始化本地变量 auth_flag 的地方。如您所见,堆栈上为局部变量保留的位置会根据您的缓冲区大小而变化。我认为值得用 clang 编译你的代码并用 lldb 调试它。

【讨论】:

  • “有一个已知序列”不正确。您“知道”的序列只会由某些编译器发出,只会在某些平台上发出,并且只会在优化关闭的情况下发出。
  • 谢谢。刚刚在声明中添加了一条注释
猜你喜欢
  • 2019-11-19
  • 2020-07-31
  • 2017-09-16
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-04
  • 1970-01-01
相关资源
最近更新 更多