我发现这个参考绕过金丝雀:http : //sota.gen.nz/hawkes_openbsd.pdf
它建议逐个字节地暴力破解金丝雀。我不明白这是如何工作的:“技术是暴力破解金丝雀的每个字节以及时间分析”。如何逐字节进行时间分析?整个金丝雀不是一下子比较的吗?我认为这四个字节仅与一条指令进行比较。
我发现这个参考绕过金丝雀:http : //sota.gen.nz/hawkes_openbsd.pdf
它建议逐个字节地暴力破解金丝雀。我不明白这是如何工作的:“技术是暴力破解金丝雀的每个字节以及时间分析”。如何逐字节进行时间分析?整个金丝雀不是一下子比较的吗?我认为这四个字节仅与一条指令进行比较。
这个想法是控制攻击的长度,因此您只覆盖金丝雀的第一个字节。然后,您可以暴力破解该字节的 256 个可能值,并使用某种侧通道来确定哪个是正确的。一旦你得到了这个,你可以暴力破解第二个字节,并继续直到你得到整个金丝雀。
这个话题同样让我感到困惑。那个 Phrack 问题中只有一两句话甚至提到智能控制缓冲区溢出的长度(可能是因为这是一种典型的技术)。Paj28 似乎有正确的答案,但对于如何获得足够准确的侧通道以使其在实践中发挥作用仍然有点模糊。如果为了进行统计分析需要进行大量尝试,这显然会降低攻击的有效性(尤其是在远程情况下,每次尝试都会发生新的网络连接和 fork() 系统调用)。
常见的 SSP 方法使用单个金丝雀,该金丝雀从存储在受保护部分中的程序范围变量复制到堆栈。此初始化发生在可执行文件加载期间。此初始化的三个已知变体是(a)在 exec*() 系统调用期间由内核执行,(b)由静态链接可执行文件的一段启动代码执行,或(c)由插入到动态加载程序的代码片段动态链接的可执行文件的运行时映像。
最重要的是,这种逐字节的技巧可以被打败。考虑以下:
{01} 我们可以在堆栈帧中保留两个字长空间,而不是为一个 64 位字分配空间。一个这样的变量槽被放置在溢出不可能修改它的缓冲区之前。
{02} 在执行函数序言时,我们实际上不是将固定的金丝雀复制到这两个槽中,而是从两个值中派生出一个动态的金丝雀(它们本身可能在某种程度上是动态的)。我们的源值很可能取自预初始化的随机受保护内存块,类似于旧金丝雀的建立方式。
{03} 派生方法可以任意强,甚至是加密方法,但这种技术不需要如此。
{04} 一个简单的策略会使攻击者的生活变得非常困难。假设推导方法仅仅是诸如 A + B = C 之类的东西,其中 A 和 B 是伪随机 64 位值。C,校验值,同样是 64 位的。
{05} 现在,当攻击者去覆盖金丝雀时,他们只能到达 B。由于存在于较低的虚拟地址,A 在未触及的堆栈中存活。
{06} A + B != C,所以很明显,金丝雀检查失败了,正如我们所料。
{07} 但是,攻击从覆盖 B 的第一个字节中学到了什么?只有 A + B 自然不等于 C。由于攻击没有关于 A 和 C 实际是什么的信息,这会为 B 创建大量可能的值。
{08} 在这种方案下,天真地将单词值细分为字节并分别攻击它们是不可行的。由于模块化(包装)算术的性质,A[0] + B[0] = C[0] 不保证 A + B = C。来自低字节的位可以携带到更高的字节,而来自更高字节的位字节可以溢出到较低的字节。攻击者仍然有可能弄清楚 B 的第一个字节应该是什么,但是随着每个后续字节,它变得更加困难。
{09} 假设人们找到了一种可行的方法,可以在合理的时间内击败这三个 64 位字示例,则该概念可以任意扩展到比模算术更复杂的大量变量和函数推导方法。实际上不言而喻,有一种方案既足够快以供实际使用,又足够安全,可以阻止几乎所有可能的攻击。
{10}如果我们保留并确保一些内存区域用于存储金丝雀输入,则可以进一步改进该技术。为了检测缓冲区溢出,实际上只有一个金丝雀计算值需要出现在堆栈上。所有其他人都可以存在于其他地方,因此即使攻击者在不同的函数执行上下文期间设法溢出堆栈,也无法轻松读取它们。
我不太可能是第一个意识到堆栈粉碎保护方案可以进行重大改进以防御这种攻击和类似攻击的人。我只是将其作为一个简单的概念证明,“以防万一”。