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