金丝雀和其他挥发物不能防止溢出;他们只是试图应对已经发生的溢出的后果。金丝雀尝试检测覆盖堆栈帧中的返回地址的溢出情况。DEP 更进一步,它假设返回地址已被覆盖和跟踪,它限制了执行可以跳转的区域。ASLR 更进了一步:它“洗牌”允许执行的区域。
历史上,缓冲区溢出被利用来覆盖堆栈中的返回地址,从而使执行跳转到已经用于溢出缓冲区的数据。金丝雀尝试在跳转之前检测到这一点,并使用 DEP 使堆栈空间不可执行。DEP 还可以在堆中的缓冲区溢出时工作(金丝雀仅用于堆栈缓冲区溢出,但堆也可以包含缓冲区,以及要覆盖的敏感数据,例如指向函数的指针——尤其是在 OOP 的上下文中C++ 等语言)。为了绕过 DEP 和金丝雀,攻击者已经开始寻找允许覆盖函数指针的溢出,从而使执行跳转到标准库代码这必然是“那里”,也必然是可执行的。这就是发明 ASLR 的原因:让此类游戏变得更难。ASLR 仍然可以被幸运打败:因为 ASLR 必须保持页面对齐(x86 上为 4 kB),在不太大的地址空间内(通常在 32 位 x86 上小于 2 GB),没有那么多地方目标代码可能在哪里(最多一百万)。根据攻击上下文和攻击者的脚本可以尝试的频率,这可能太低而无法舒适。
这里的重要主题是金丝雀、DEP 和 ASLR 本身不会阻止溢出,而是针对传统上使用的通用溢出利用方法。在任何应用程序中,覆盖非指针数据的溢出都可能与远程shell 漏洞一样致命(例如,想象修改名为“ authenticated_user_name
”的字符串字段的溢出)。攻击者和防御者之间的武器竞赛变得过于专业化,在我看来,越来越没有抓住重点。一般来说,最好不要让溢出发生,即在目标缓冲区之外写入字节之前阻塞/杀死有问题的进程/线程。几乎所有体面的编程语言(Java、C#、VB.NET、Python、Ruby、Node.js、OCaml、PHP……选择范围都很大)都会发生这种情况。