作者:wzt
原文链接:https://mp.weixin.qq.com/s/20ACZFyQiUWZf5cIm_ZW-w

1.1 简介

Freebsd的内核内存分配器叫做UMA(Universal Memory Allocator),这篇文章只关心它的安全特性,对于常规功能实现请读者朋友参考网络上的其他文章。它的安全功能特性相比XNU、NT、LINUX都少了很多,并且还存在一些不安全的构架设计,下面将会详细分析。

1.2 架构设计缺点

UMA的总体架构也是基于solaris slab, 我们直接看最底层的slab结构,一个slab大小为PAGE_SIZE,slab的管理体结构依据slab里的每个item大小而决定,对于小块item,slab管理结构体放在slab里,并且是放到PAGE_SIZE的最后。对于大块item,管理结构体则单独分配一个内存,不包含在slab里。

对于小块item, slab这种设计属于严重的安全错误设计,slab header放在所有item的最后,如果最后一个item发生溢出,就可以直接覆盖slab header里的数据结构。

struct uma_slab {
        uma_keg_t       us_keg;                 /* Keg we live in */
...
}

Slab header结构为struct Uma_slab,它的第一个成员是us_keg。

struct uma_keg {
 LIST_HEAD(,uma_zone)    uk_zones;       /* Keg's zones */
...
}

Uk_zones结构为:

struct uma_zone {
        uma_ctor        uz_ctor;        /* Constructor for each allocation */
        uma_dtor        uz_dtor;
}

结构体成员uz_ctor和uz_dtor为每个zone在创建和销毁时调用的析构函数指针,exploit程序一般都会替换这两个函数指针,使其指向shellcode地址。Slab header放在最后,使堆溢出攻击相对linux变得更加简单, 因为linux的slab header就是放在最前面的。我们在设计内存分配器时就要避免这个糟糕的设计,同时管理结构体中函数指针的定义一定要做到最少,防止被exploit程序滥用。

1.3 安全特性缺失

1.3.1 溢出检测

能检测到溢出情况的发生是每个内存分配器的基础安全能力,业界的通用算法是在内存区块的前后加入redzone,在初始化时填充一个固定值,在内存释放时检测redzone里的固定值是否有改变来判断是否有溢出行为的发生。

UMA的redzone结构为:

在data的前面分别为struct stack 保存的是当前栈信息,size为data的大小,0x42则为redzone的固定值,一共16字节。在data的最后同样为16字节的固定值。

由于设置redzone会占用更多的内存,同时会使初始化和释放逻辑变得复杂,从而影响效率,所以检测溢出的发生都是作为debug选项来开启的。几乎所有主流的os内核内存分配器在设置redzone时都使用了固定值,笔者认为这也是一个不安全的设计,exploit编写者可以精心构造内存结构,使其shellcode地址指向0x42424242,就可以绕过检测。

1.3.2 UAF检测

对于UAF(Use After Free)的检测,freebsd没有实现这个功能,一般算法是在slab item释放时对data区域填充固定的值,在分配时先检测固定值有没有被污染,以此来判断有无UAF的发生。

1.3.3 双向安全链表

双向链表的删除操作时,要先检测前后节点是否为合法地址, freebsd同样没有设计这个功能。

1.3.4 item地址随机化

Slab里保存的item为了初始化简单, 每个item都是顺序链接的, 这给exploit程序的利用提供了极大的方便。Linux内核使用了洗牌算法将item的链接顺序打乱来规避这种攻击。Freebsd没有提供这个功能。

同样为了检测是否有内存破坏的现象, 通常会在一些管理结构体中加入一个随机化的cookie值,内存释放时判断cookie是否有被污染。freebsd没有提供这个功能。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1451/