今天早上,我在protostar上进行stack0练习。示例是使用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个字节,并且在缓冲区之前被压入堆栈,64个字节的char,我以为我需要溢出缓冲区1个字节才能更改修改后的值。
然而,这不起作用。因此,我接下来认为编译器在修改后的缓冲区和缓冲区之间插入了4个字节的填充,这样两者都从字边界开始(x86-64的8个字节)。但是,这也不起作用。
最后,我在GDB中运行了可执行文件,发现事实上,GCC在修改和缓冲区之间插入了12个字节的对齐:因此我必须将77个字节写入缓冲区以便在修改后溢出(第77个字节是第一个修改后的字节。)
(gdb) p &modified
$3 = (volatile int *) 0x7fffffffdadc
(gdb) p &buffer[63]
$12 = 0x7fffffffdacf ""
因此,0x7fffffffdadc - 0x7fffffffdacf是13个字节。
0000000000400504 <main>:
400504: 55 push %rbp
400505: 48 89 e5 mov %rsp,%rbp
400508: 48 83 ec 60 sub $0x60,%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 $0x0,-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在堆栈sub $0x60, %rsp
上为缓冲区和修改后的内容分配了96个字节。
然后,它将目标索引和源索引从%rbp移动到地址-0x54和-0x60。这是什么目的以及这些寄存器的作用是什么?
接下来,我可以看到使用movl $0x0,-0x4(%rbp)
将modified设置为0,其中它是基指针的4个字节。因此,缓冲区末尾和修改后的第一个字节之间有12个字节。
因此,我的问题是:为什么GCC在修改的和缓冲区之间插入12个字节的对齐,以使修改的16字节对齐,而不是仅仅8字节对齐,这将使它与x86-64上的字边界对齐?
答案 0 :(得分:0)
堆栈对齐由平台ABI控制,而不仅仅是特定的类型对齐。在您的情况下,堆栈对齐是16个字节,这对于x86_64平台来说很常见。
您可以搜索x86_16 ABI问题,例如:DateTime ones