Protostar Stack0 中的填充和对齐

Padding and alignment in Protostar Stack0

今天早上我 运行 通过 stack0 protostar 练习。该示例是使用 gets 的简单堆栈溢出。我在 x86-64 上使用 gcc 编译了代码,启用了堆栈保护器和 ASLR。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    volatile int modified;
    char buffer[64];

    modified = 0;
    gets(buffer);

    if(modified != 0) {
        printf("you have changed the 'modified' variable\n");
    } else {
        printf("Try again?\n");
    }
}

由于一个int是4个字节,并且在buffer之前被压入堆栈,64个字节的char,我认为我需要将buffer溢出1个字节才能改变modified的值。

但是,这没有用。因此,我接下来认为编译器在 modified 和 buffer 之间插入了 4 个字节的填充,这样两者都从字边界开始(8 个字节的 x86-64)。但是,这也没有用。

最后,我 运行 GDB 中的可执行文件,发现事实上,GCC 在修改和缓冲区之间插入 12 个字节的对齐:因此我不得不将 77 个字节写入缓冲区,以便在修改 (第 77 个字节是修改后的第一个字节。

(gdb) p &modified
 = (volatile int *) 0x7fffffffdadc

(gdb) p &buffer[63]
 = 0x7fffffffdacf ""

因此,0x7fffffffdadc - 0x7fffffffdacf 是 13 个字节。

0000000000400504 <main>:
400504: 55                      push   %rbp
400505: 48 89 e5                mov    %rsp,%rbp
400508: 48 83 ec 60             sub    [=12=]x60,%rsp
40050c: 89 7d ac                mov    %edi,-0x54(%rbp)
40050f: 48 89 75 a0             mov    %rsi,-0x60(%rbp)
400513: c7 45 fc 00 00 00 00    movl   [=12=]x0,-0x4(%rbp)
40051a: 48 8d 45 b0             lea    -0x50(%rbp),%rax
40051e: 48 89 c7                mov    %rax,%rdi
400521: e8 ea fe ff ff          callq  400410 <gets@plt>

现在,查看上面的目标代码,我们可以看到 GCC 在堆栈上分配了 96 个字节 sub [=13=]x60, %rsp 用于缓冲区和修改。

然后,它将目标索引和源索引从%rbp 移动到地址-0x54 和-0x60。这些寄存器的作用是什么?

接下来,我可以看到 modified 被设置为 0 movl [=14=]x0,-0x4(%rbp),它距离基指针 4 个字节。因此缓冲区末尾和修改后的第一个字节之间有12个字节。

因此,我的问题是:为什么 GCC 在修改和缓冲区之间插入 12 字节对齐,以使修改后的 16 字节对齐,而不是仅 8 字节对齐,这将使其与 x86-64 上的字边界对齐?

堆栈对齐由平台 ABI 管理,而不仅仅是特定类型对齐。在您的情况下,堆栈对齐为 16 字节,这对于 x86_64 平台很常见。

您可以 运行 搜索 x86_16 ABI 问题,例如:x86_64 align stack and recover without saving registers