递归攻击——入门1
递归概要
递归可以分为两类,直接递归和间接递归。直接递归出现在程序调用自身的时候。直接递归的例子很多,比如数学算法,列表排序和二叉搜索树。
直接递归的例子如下所示,这个例子返回某个整数的阶乘(一系列递减整数的乘积)。
1 | uint32_t factorial(uint32_t number) |
间接递归是提程序调用另一个程序,并最终调用原始程序。
间接递归最实际的使用是在目录遍历程序中, 在这样的程序里,程序在树中导航,另一个程序处理文件。
如果这个文件是目录,就调用原始程序。
另一个例子是,判断某个数是偶数还是奇数,如下所示。
1 | int is_odd(int number) |
栈溢出导致拒绝服务
递归的问题通常出现在没有合理的估计递归调用的次数时。
当函数由 x86 处理器执行时,返回地址和函数参数被压入栈中。
在递归调用中,栈以指数级增长。如果递归层数太深,就会在分配的栈大小超出时发生栈溢出。
递归引发的栈溢出非常难以利用,通常会导致拒绝服务。
这是因为,随着递归调用的进行,栈持续增长,使得栈溢出超出了栈顶。
在栈上面(大多数时候)有一个防护页,用于预防栈破坏其他内存区域。
当然也有技术能够跳过这个防护页,溢出到堆中。
这一实现要求递归函数申请足够大的栈变量以及堆内存靠近防护页的另一端。
使用递归攻击未初始化的栈变量
递归在漏洞利用中的另一个作用是攻击未初始化的栈变量。
正如前面提到的,x86 调用约定中规定函数参数由调用者压入栈中。
如果攻击者能够控制递归中函数参数的值,他们就能通过初始化未初始化的栈变量来控制栈。
编译器假设当你申明一个变量时,你会初始化它(在使用之前)。
例子
1 |
|
总结
复杂的递归程序通常难以理解,因此会导致一些程序错误。
无限的递归调用可以用于破坏栈,在某些条件下,递归能被用于影响栈的布局以攻击未初始化的变量。
如果你对例子中的递归漏洞感兴趣,我推荐这个项目。
Project Zero: Exploiting Recursion in the Linux Kernel