c语言 - GCC 分配的堆栈空间比本地人需要的要多,即使没有对齐。它使用空间做什么?



我有这样的代码:

#include <stdio.h>
#include <string.h>
void overflow_me(char* dizi){
char buff_array[100];
strcpy(buff_array,dizi);
printf("Hosgeldin %s",buff_array);
}

int main(int argc, char *argv[]){
overflow_me(argv[1]);
return 0;
}

我通过使用gcc -g -o overflow overflow.c -m32 -mpreferred-stack-boundary=2编译它。 然后我用 gdb 打开溢出文件并反汇编 overflow_me 函数。

endbr32
0x00001211 <+4>:     push   %ebp
0x00001212 <+5>:     mov    %esp,%ebp
0x00001214 <+7>:     push   %ebx
0x00001215 <+8>:     sub    $0x6c,%esp

我想知道为什么堆栈分配 108 个字节。我预计这将是0x64而不是0x6c。

整体拆卸功能:

0x0000120d <+0>:     endbr32
0x00001211 <+4>:     push   %ebp
0x00001212 <+5>:     mov    %esp,%ebp
0x00001214 <+7>:     push   %ebx
0x00001215 <+8>:     sub    $0x6c,%esp
0x00001218 <+11>:    call   0x1110 <__x86.get_pc_thunk.bx>
0x0000121d <+16>:    add    $0x2db3,%ebx
0x00001223 <+22>:    mov    0x8(%ebp),%eax
0x00001226 <+25>:    mov    %eax,-0x70(%ebp)
0x00001229 <+28>:    mov    %gs:0x14,%eax
0x0000122f <+34>:    mov    %eax,-0x8(%ebp)
0x00001232 <+37>:    xor    %eax,%eax
0x00001234 <+39>:    pushl  -0x70(%ebp)
0x00001237 <+42>:    lea    -0x6c(%ebp),%eax
0x0000123a <+45>:    push   %eax
0x0000123b <+46>:    call   0x10b0 <strcpy@plt>
0x00001240 <+51>:    add    $0x8,%esp
0x00001243 <+54>:    lea    -0x6c(%ebp),%eax
0x00001246 <+57>:    push   %eax
0x00001247 <+58>:    lea    -0x1fc8(%ebx),%eax
0x0000124d <+64>:    push   %eax
0x0000124e <+65>:    call   0x1090 <printf@plt>
0x00001253 <+70>:    add    $0x8,%esp
0x00001256 <+73>:    nop
0x00001257 <+74>:    mov    -0x8(%ebp),%eax
0x0000125a <+77>:    xor    %gs:0x14,%eax
0x00001261 <+84>:    je     0x1268 <overflow_me+91>
0x00001263 <+86>:    call   0x1320 <__stack_chk_fail_local>
0x00001268 <+91>:    mov    -0x4(%ebp),%ebx
0x0000126b <+94>:    leave
0x0000126c <+95>:    ret

看起来额外的空间用于堆栈cookie(默认情况下-fstack-protector=strong打开,-fpie也使代码复杂化),以及无缘无故地将堆栈参数从EBP上方复制到下方。

使用-fno-stack-protector -fno-pie简化 asm。 它们在 Godbolt 上默认处于关闭状态,较新的 GCC 不会浪费从 EBP+8 复制到另一个本地的指令,因此 https://godbolt.org/z/7bMzxGKsd 说明当您使用较新的 GCC 进行不同编译时,您确实只保留了 100 字节的堆栈空间。 我还使用-fverbose-asm用 var 名称注释 asm。 32 位 PIE 代码很糟糕(PC 相对寻址在 x86-64 64 位模式下是新的),所以很难阅读,这就是为什么我提到-fno-pie,即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器时的片刻。


我通过使用 |n|> 0x6c 查找n(%ebp)找到了发生了什么,所以我发现了mov %eax,-0x70(%ebp)(前面是来自8(%ebp)的负载,即 arg)和pushl -0x70(%ebp),这也清楚地表明这是dizi被推送为 printf 参数的副本。

另外,mov %gs:0x14,%eaxcall 0x1320 <__stack_chk_fail_local>很明显这是用某种形式的-fstack-protector编译的,所以我看了看,发现它存储了堆栈cookie到mov %eax,-0x8(%ebp)。 (在保存的 EBX 正下方,它本身位于保存的 EBP 下方,EBP 帧指针在费心设置后指向该 EBP。


Comments 提到了 EBX,但保存的 EBP 和 EBX 的空间由push %ebppush %ebx分配,它们本身会修改 ESP,而不是sub $0x6c,%esp的一部分。

请注意,GCC有时会分配比局部变量 + 对齐所需的更多的堆栈空间(为什么 GCC 在堆栈上分配的空间超过了对齐所需的空间?)但这不是它在这里所做的:它正在使用它保留的堆栈空间的每个字节。 没有用,但是您要求它不要优化,因此它制作了愚蠢的代码。:P

查看反汇编的代码,它似乎正在检查strcpy函数中的数据溢出。在 <+28> 和 <+34> 时,它会将一个幻数gs:0x14到刚过buff_array末尾的地址。然后在strcpy返回后,在 <+74>-<+84>,它会检查这个幻数是否未被覆盖。如果有,它知道复制了 100 多个字节,并表示__stack_chk_fail_local错误。

相关内容

最新更新