使用金丝雀值的堆栈损坏检测

Stack Corruption Detection using canary value

我正在阅读一本教科书,其中描述了能够检测堆栈何时损坏的防御措施。书上说:

最新版本的 gcc 在生成的代码中加入了一种称为堆栈保护器的机制来检测缓冲区溢出。这个想法是在任何本地缓冲区和堆栈状态的其余部分之间的堆栈帧中存储一个特殊的金丝雀值,如下图所示:

这个 canary 值,也称为 aguard 值,是在每次程序运行时随机生成的,因此攻击者无法轻松确定它是什么。在恢复寄存器状态和从函数 returning 之前,程序检查金丝雀是否已被此函数或它调用的某个操作更改。如果是这样,程序将因错误而中止。

我明白了,但我仍然认为这个设计存在缺陷。是的,攻击者可能无法确定 canary 的值是多少,但攻击者知道 canary 的大小(8 字节),因此攻击者可以操纵指针绕过 canary 所在的堆栈中的这 8 字节区域,然后覆盖return地址,所以金丝雀实际上什么都不保护,我的理解对吗?

如果攻击者有任意写,那么是的,金丝雀是没用的。事实上,任意写入可以通过多种方式使 Canary 无效,包括覆盖 __stack_chk_fail 的 GOT 条目(覆盖 Canary 时调用的函数),或者只是不覆盖 Canary。然而,任意写入并不总是可获得的。它通常仅在存在格式字符串漏洞时才会发生。当只有缓冲区溢出时,要写入金丝雀之后的地址,您必须写入该地址之前的地址,其中包括金丝雀。这意味着用缓冲区溢出覆盖 return 地址的唯一合理方法是猜测金丝雀或通过另一个漏洞泄漏它。

首先,重要的是要注意该图虽然正确,但与大多数传统堆栈图的方向相反。因此,用户输入将从 buf 开始并向上进行。

由于随机堆栈金丝雀位于缓冲区和return地址之间,而攻击的目标通常是覆盖return地址以重定向程序流,因此没有办法避免覆盖金丝雀值。这有效地阻止了任何类型的依赖于缓冲区溢出的攻击。此外,stack canary 总是以空字节结束,这也使得即使它的值已知也很难编写,因为许多字符串函数将在空字节处终止。

缓冲区溢出攻击之所以如此普遍,是因为程序员很容易不小心接收到超出缓冲区容量的输入。实际上,能够像您建议的那样操纵堆栈指针或跳过堆栈金丝雀比缓冲区溢出要困难得多(如果不是不可能的话),并且在许多情况下无论如何都需要控制 return 地址。

但是,防止一种类型的利用并不意味着其他技术不可行。正如 Aplet 提到的,还有其他技术可以绕过或取消堆栈金丝雀提供的保护。不可能阻止所有的利用技术,一般来说,应用的保护越多,性能损失就越大。 Stack Canary 在小的性能损失和检测大量攻击之间取得了很好的平衡。