FreeSentry (FreeSentry: Protecting Against Use-After-Free Vulnerabilities Due to Dangling Pointers)来自于NDSS 15!

简介

Use-after-free出现的根本原因是已分配的内存释放之后指针并没有因为内存释放而变为NULL,而是继续指向已释放内存,这样就会出现二次释放或利用这个指针来访问可以已经分配新内容的同一片地址空间。
Use-After-Free漏洞缺少防护措施,而且很难通过人工代码检查来发现。Use-After-Free不像错过对其他漏洞的检查,比如说缓冲区溢出或整形错误,程序员无法加上简单的检查来确定指针不是旧的。即使在释放内存之后将一个指针设成NULL也会导致为找到,因为它有可能已经被拷贝给另一个指针了。所以唯一一个可能的方法就是把和这个内存区域“有关联”的所有指针均无效化才可以。另外这里还涉及一个问题,那就是如果我千方百计的利用各种方法来隐藏我得到的指针,比如利用指针运算跳出这个内存区域的范围、转换类型(这几个问题,作者都考虑到了,但是不没有都解决,详见第二大部分)。
FreeSentry这个缓解措施重要采取插入动态运行时检查用来无效化指针,当涉及到的内存释放之后。如果一个无效的指针被访问到了,程序会随后崩溃,组织攻击者利用漏洞攻击。
FreeSentry通过跟踪指向对象的指针并在对象释放的时候无效这些指针来提供保护。我们通过对源码插入一些运行时代码以跟踪指针并在对象释放的时候无效这些指针来达到这个目标。 一个Use-After-Free很简单的例子就是:

p = (struct A *) malloc(16);
free(p);
q = (struct B *)malloc(16);
//其中前提是p和q指向同一块地址空间,这样就可以使用旧指针来访问和修改新数据了!
p -> integer1 = value;

这是一段简单的Use-After-Free漏洞代码序列。下面我们来解释一下这个代码序列,其中结构体A是一个包含四个integer的结构体,而结构体B是一个包含两个函数指针和一个8字节的char数组。依靠内存重新分配,我们可以得知p和q之间是相等关系,还是有一定的差值。通过指针运算,我们将p移动到q的位置,然后我们就可以把它们当成integer读或写这些指针,可能允许我们赋值这些integer来指向注入的代码。

1     char * retptr(){
2            char p, *q;
3            q = &p;
4            return q;
5     }
6
7     int main(){
8            char *a, *b;
9            a = malloc(16);
10           b = a+5;
11           free(a);
12           b[2] = c;
13
14           b = retptr();
15           *b = 'c';
16    }

这个例子中有两处Use-after-free漏洞,分别是12和15行由于第11和4行的内存释放。第12行涉及到动态分配的内存,而第15上涉及到指向栈分配内存空间的指针,而栈内存空间由于函数返回而被自动释放。我们在这里列出来这个代码主要是为了后面的代码插桩给你源代码或者错误例子!

实现

我觉得这个作者在写这篇文章的时候,文章组织不好,我感觉他的实现部分完全在第三部分APPROACH中,在PROTOTYPE IMPLEMENTATION这个部分基本没有具体的实验思路和细节部分了。废话不多说,马上来讲实现方法.
FreeSentry提供的保护方法的背后的核心思想是将objects和它们的指针联系到一起。当为object分配的内存被释放,那些指向这个object会被置为无效。 为了做到这个,当一个指针被创建或者修改去指向一个新的object,这个指针的地址会被注册为指向我们的object。当object被释放之后,free函数会查询所有的指向被object占据的内存区域的指针。如果这些指针始终指向我们的object,这些指针会被无效。 但是这里面其实有一个问题,后续的地方可以看得见。就是在指针运算、指针出界和类型转化这些方面,这句话就有问题了。 以下是插桩之后的代码:

1    char * retptr(){
2        labelstack();
3        char p, *q;
4        q = &p;
5        regptr(&q);
6        invalidatestack();
7        return q;
8    }
9
10    int main(){
11        char *a, *b;
12        a = malloc(16);
13        regptr(&a);
14        b = a+5;
15        regptr(&b);
16        free(a);
17        b[2] = c;
18
19        b = regptr();
20        regptr(&b);
21        *b = 'c';
22    }

其中,regptr用于注册指针,free函数会执行原来的free函数,并且会释放那些利用regptr函数注册的指针。

  • A.支持数据结构

只需要将这幅图中的各种原理讲一下就可以了,只要将object lookup table 和 pointer lookup table 讲懂了就行了

  • B. 释放内存

这里面的关键是malloc函数、free函数的功能,这里面需要的是除了原本函数功能之外的功能。

  • C. 重新分配内存

这里面的关键是realloc函数的功能,这里面主要是靠上面的那幅图来解释。

  • D. 栈保护

这里面的关键是alloca函数的功能

  • E. 指针运算和出界指针

感觉这个地方可能有些问题,因为这里边的逻辑让我感觉有点牵强。

  • F. 被赋值为其他类型的指针

limitation

  • G. 未保护的代码

  • H. C++

问题讨论