即使没有对齐,GCC 分配的堆栈空间也比本地人需要的多。空间有什么用?

时间:2021-04-23 09:28:06

标签: c assembly gcc x86 stack

我有这样的代码:

#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 this 来编译它。 然后我用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

2 个答案:

答案 0 :(得分:1)

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

使用 -fno-stack-protector -fno-pie 来简化 asm。它们在 Godbolt 上默认关闭,并且较新的 GCC 不会浪费从 EBP+8 复制到另一个本地的指令,因此 https://godbolt.org/z/7bMzxGKsd 说明当您使用一个较新的海湾合作委员会。我还使用 -fverbose-asm 用 var 名称注释 asm。 32 位 PIE 代码很烂(x86-64 64 位模式中的 PC 相对寻址是新的),所以很难阅读,这就是为什么我提到 -fno-pie 即使它不影响堆栈使用。 (除了调用 thunk 将当前 EIP 放入整数寄存器的片刻。)


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

此外,dizimov %gs:0x14,%eax 显然是用某种形式的 call 0x1320 <__stack_chk_fail_local> 编译的,所以我查看并发现它将堆栈 cookie 存储到 -fstack-protector。 (就在保存的 EBX 的正下方,它本身就在保存的 EBP 之下,EBP 帧指针在费心设置之后指向它。)


评论提到了 EBX,但是保存的 EBP 和 EBX 的空间由 mov %eax,-0x8(%ebp)push %ebp 分配,它们本身修改了 ESP,而不是 push %ebx 的一部分。

请注意,GCC 确实有时会分配比本地+对齐(Why does GCC allocate more space than necessary on the stack, beyond what's needed for alignment?)所需的更多的堆栈空间,但这不是它在这里所做的:它使用堆栈空间的每个字节它保留。没有用,但您要求它不要优化,因此它编写了愚蠢的代码。 :P

答案 1 :(得分:0)

查看您的反汇编代码,它似乎正在检查 strcpy 函数中的数据溢出。在 <+28> 和 <+34> 处,它向 gs:0x14 末尾的地址写入一个幻数 buff_array。然后在 strcpy 返回后,在 <+74>-<+84>,它检查这个幻数没有被覆盖。如果有,它就知道复制了 100 多个字节,并用 __stack_chk_fail_local 发出错误信号。