我跟随Youtube Computerphile缓冲区溢出教程,了解它是如何工作的。该教程在Kali中说它,并且我运行Kali 64位来测试它(我认为他运行的是32位)。
他写了一个像这样的简单程序:
#include <stdio.h>
#include <string.h>
int main(int argc, char** argv) {
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
然后在GDB中启动程序后,他运行:
(gdb) run $(python -c 'print "\x41" * 506')
结果是一个seg错误,表明返回地址被两个41&#s覆盖了一半。
当我尝试复制它时,我需要将506更改为522以产生相同的结果。所以我的问题是:
为什么506只运行时只重写两个字节而不是三个字节 它
为什么我需要写入522个字节来覆盖返回中的2个字节 地址?我认为这可能与他有关,可能使用32位代替 64位卡利,但我真的不明白这种差异 数学上加起来。
当我disassemble main
时,我看到在函数序言是指令sub
rsp, 0x210
之后,看起来缓冲区被分配给528个字节。为什么
特别是这个数字(他的代替子0x1f4恰好是500)以及如何与上面的内容相关,开始重写指令指针需要大于520个字节?
在写入[500,520]字节的范围内发生了什么 超过缓冲区大小,但尚未写入顶部 指令指针?
答案 0 :(得分:8)
每个月左右都会询问此问题的变体。
事情很简单:在缓冲区的边界上写入会导致未定义的行为,可能或可能不会涉及分段错误并覆盖内存中的任何特定结构。
您所做的假设是,每个人都使用强制性内存布局,而这种情况根本不是真的,更不用说地址空间随机化或编译器优化等技术。
天啊,为什么main
函数存储传统的返回地址?它可能非常好地在系统/编译器/二进制格式特定的启动代码中。
如果编译器很聪明,它甚至会注意到argv[1]
仅由strcpy
访问,它将其复制到缓冲区 - 然后,考虑到什么都不会访问{的地址空间在argv[1]
之后{1}}只会不为缓冲区分配任何内容,而只需使用main
。由于那是无处使用的,你的&(argv[1])
将为空,但对于main()
,一个const表达式,因此对main的调用可以替换为将0写入return 0
或任何你的平台用于返回值。
讨厌告诉你这个,但是:除了指出事实上可能存在缓冲区溢出之外,它只提供一些适用于特定机器的东西,它具有特定的编译器版本,编译特定的代码片段用于特定体系结构的libc。结果不能一概而论。