堆栈溢出的运行时探测(二)

 3. 技术

3.1 GNU Lib C(glibc)中的堆管理

        C语言没有提供如动态内存管理、字符串操作、输入输出等内置功能,而是把这些功能定义在一个标准库中,当用户使用的时候会被编译和链接。GNU C库就是这一一个库,定义了ISO C标准中的所有库函数,以及POSIX(可移植操作系统接口)和GNU系统的特定函数。

        C语言支持两种内存分配机制:静态和自动。当一个变量被声明为静态或者全局变量的时候采用静态内存分配,每个静态或者全局变量定义了一个固定大小的块空间。这个空间从程序开始就被分配,在运行期间也不释放。当变量是自动变量(如函数参数或者局部变量)时进行自动内存分配,当遇到包含声明的复合语句时会自动分配内存空间,当退出复合语句的时候内存就被释放。

        第三种内存分配方式是动态分配,这种方式不受C语言支持,但是通过glibc函数使用。动态内存分配是一种由程序决定哪些信息需要被存储的技术。当需要的内存数量或者内存使用周期未知的时候需要使用这种技术。使用到的两个基本函数:malloc,动态分配内存;free,释放内存。此外还有其他函数如calloc,realloc。

        GNU Lib C使用了Doug Lea的内存分配器dlmalloc来执行动态内存分配函数。dlmalloc利用了两个核心特征,边界标签(boundary tags)和分箱技术(binning),来代替用户程序管理内存分配请求和释放请求。

        内存管理是基于“块”的,即包含可用区域和内存管理信息的内存块。内存管理信息也叫边界标签(boundary tags),存储在每个块的开始位置,保持当前块和前面块的大小。这样便允许两个相邻的未使用块合并为一个大块,使得未使用的小块数量保持较小,减少碎片。

        当前不使用的块存储在bin中,根据大小分组。小于512字节的文件只保存一个尺寸,大于等于512字节的,其大小按几何级数增长。搜索可用块的时候是按照最小优先的策略进行的,根据需要的内存大小在合适的bin中开始寻找。对于未分配的块,管理信息包含了两个指针,用双向链表(自由链表)存储块和对应的bin。这些链表指针被称为forward (fd) 和back (bk)。

        对于32位的结构,管理信息通常包含4字节大小的信息域(块大小和前面块的大小)。当块未必分配时,也会包含两个4字节大小的指针用来操作bin中自由块的双链表。

3.2 堆溢出漏洞剖析

       使用前后指针连接bin中的可用块存在安全隐患:如果恶意用户可以使分配的内存块溢出,用户可以重写相邻的下一个内存块头指针。当溢出块未被使用时存储在bin的双向链表中,攻击者就可用控制块中前后指针的值。基于此,考虑到下面glibc使用的unlink宏:

#define unlink(P, BK, FD) { \
[1] FD = P->fd;  \
[2] BK = P->bk;  \
[3] FD->bk = BK;  \
[4] BK->fd = FD;  \
}

       如果试图溢出bin中自由表的块,unlink过程便会遭到恶意用户的破坏,在任意内存地址中写任意值。

在上面的unlink宏中,第一个参数P指向将要从双向链表中移除的块。攻击者必须在P→fd中存储指针地址(-12字节,下面会解释),在P→bk中存储需要的值。在第[1] 行和第[2] 行中,前指针(P →fd)和后指针 (P→bk) 的值分别被存储在临时变量FD和BK中。在第[3] 行中,FD被间接引用,FD中的地址增加了12字节(边界标签中bk的偏移量)。这种技术可以用来改变程序的GOT (Global Offset Table) 并且再直接访问攻击者代码的函数指针。

       上图中的堆溢出漏洞变种,操作块的大小域,而不是列表指针。攻击者可以给相邻块的大小域提供任意值,类似于操作列表指针。当大小域可以操作的时候,例如合并两个未使用块,堆管理过程可能被诱使其将攻击者控制下的内存区域作为下一个块。攻击者可以在那个位置设置一个伪块来进行攻击。如果攻击者由于某种原因未能写相邻块的列表指针但是能够操作其大小域,那么这次攻击也不一定成功。

3.3 堆完整性探测

       为了保护堆,我们的系统对glibc的对管理做了很多改进,包括每个块的结构和各自的管理方式。如下图:

        保护块管理信息的第一个元素是预先在块结构中插入一个标识符,还加了一个填充区域_pad0。标识符包含了块头种子和随机值的检校和,下面会具体描述。

        第二个元素是引入种子值的全局检校和,保留在一个静态变量中(_heap_magic),这个变量在程序启动时被赋予一个随机值,然后通过一个函数设置内存映像保护来防止被被改写。这与依赖重复函数设置内存映像保护的堆保护方案相反。我们只需要程序启动时一次调用,不用遭受其他方案在运行时产生的一些损失。

        最后一个元素是增大堆管理方案,用代码管理和检查每个块的标识符。新分配的块的标识符被初始化为保护内存位置和块大小域的检校和,有全局变量_heap_magic的种子值。注意检校和不包括分配块的列表指针,因为这些区域属于块中用户数据部分。新块才能使用这个方法。

        当一个块通过释放内存函数返回到堆管理,其标识符要受检查看是否和分配时的检校和一致。如果存储的值不匹配,就认为堆管理信息被破坏了。 这时会引发警报,程序会中断。否则,程序照常进行,块被插入到bin中,必要时与其他块进行合并。任何链表操作都在检查块的标识符之前进行。当块被插入到bin中后,其标识符会被更新。

        上面提到的元素通过修改块的头信息有效预防任意位置的内存写操作,无论通过溢出还是直接操作块的头信息。每个分配的块都通过随机种子的检校和进行保护,每个链表指针都要通过检查完整性进行保护。

        这种方法还有其他用处,比如可以探测出无意的堆溢出或者重复调用释放函数。此方法缺点就是不能定位指针破坏攻击,比如破坏应用函数的指针。也不能保证 用户数据的完整性,只能保证块的头信息的有效性。

        需要注意的是glibc的堆管理已经包含了保证堆管理完整性的功能。但是使用调试方案会增加资源耗费。这种方案对堆的自由链表和全局状态进行全面的检查,包括与对制作漏洞无关的检查。此外,不能保证所有的攻击都被探测到。不是所有的链表操作都会被检查,恶意值可能通过不是针对堆溢出的完整性检查。这样,我们只能说这种方法不适合保护我们提到的那些情况。

        上面的系统已经被应用到glibc2.3和glibc2.2.9x中,以及RedHat 8.0使用的glibc预发布版本。这项技术可以被灵活运用到其他堆设计中,下一步需要将此技术应用到除了glibc之外的其他开放式系统。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值