我正在关注 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) 运行 $(python -c 'print "\x41"* 506')
结果是段错误,表明返回地址被两个 41 覆盖了一半。
当我尝试复制它时,我需要将 506 更改为 522 以产生相同的结果。所以我的问题是:
为什么506运行时只重写了两个字节而不是三个字节 是吗?
为什么我需要写入 522 个字节来覆盖返回中的 2 个字节 地址?我认为这与他可能使用 32 位有关 64 位 Kali,但我真的不明白这有什么区别 数学上相加。
当我执行
disassemble main
时,我看到在函数序言之后是指令sub rsp,0x210
,所以看起来缓冲区分配给了 528 字节。为什么 特别是这个数字(他改为 subs 0x1f4,正好是 500)以及它与上面需要大于 520 字节才能开始重写指令指针的内容有何关系?在写入 [500,520] 字节的范围内发生了什么 超过缓冲区大小,但尚未覆盖 指令指针?
最佳答案
这个问题的变体大约每个月都会被问到。
事情很简单:在缓冲区的边界上写入会导致未定义的行为,这可能或可能不会涉及段错误并且覆盖内存中的任何特定结构。
您所做的假设是每个人都使用强制性内存布局,但事实并非如此,对于地址空间随机化或编译器优化等技术更是如此。
见鬼,为什么 main
函数要存储传统的返回地址?它可能很好地内嵌在系统/编译器/二进制格式特定的启动代码中。
如果编译器很聪明,它甚至会注意到 argv[1]
仅由 strcpy
访问,后者将其复制到缓冲区——然后,什么都不考虑将在 main
之后访问位于 argv[1]
的地址空间,将简单地不为缓冲区分配任何内容 并简单地使用 &( argv[1])
代替。由于它无处使用,您的 main()
将是空的,但对于 return 0
,一个 const 表达式,因此对 main 的调用可以替换为写入 0到 eax
或您的平台用于返回值的任何内容。
不想告诉你这个,但是:除了指出实际上可能存在缓冲区溢出之外,它只给出了在具有特定编译器版本的特定机器上工作的东西,用特定的编译器编译一段特定的代码特定架构的 libc。结果不能一概而论。
关于c - 与缓冲区溢出数学的基本混淆,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41214322/