Use After Free
原理
简单的说,Use After Free 就是其字面所表达的意思,当一个内存块被释放之后再次被使用。但是其实这里有以下几种情况
内存块被释放后,其对应的指针被设置为 NULL , 然后再次使用,自然程序会崩溃。
内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
内存块被释放后,其对应的指针没有被设置为NULL,但是在它下一次使用之前,有代码对这块内存进行了修改,那么当程序再次使用这块内存时,就很有可能会出现奇怪的问题。
而我们一般所指的 Use After Free 漏洞主要是后两种。此外,我们一般称被释放后没有被设置为NULL的内存指针为dangling pointer。
这里给出一个简单的例子
#include <stdio.h>
#include <stdlib.h>
typedef struct name {
char *myname;
void (*func)(char *str);
} NAME;
void myprint(char *str) { printf("%s\n", str); }
void printmyname() { printf("call print my name\n"); }
int main() {
NAME *a;
a = (NAME *)malloc(sizeof(struct name));
a->func = myprint;
a->myname = "I can also use it";
a->func("this is my function");
// free without modify
free(a);
a->func("I can also use it");
// free with modify
a->func = printmyname;
a->func("this is my function");
// set NULL
a = NULL;
printf("this pogram will crash...\n");
a->func("can not be printed...");
}运行结果如下
例子
这里我们以 HITCON-training 中的 lab 10 hacknote 为例。
功能分析
我们可以简单分析下程序,可以看出在程序的开头有个menu函数,其中有
故而程序应该主要有3个功能。之后程序会根据用户的输入执行相应的功能。
add_note
根据程序,我们可以看出程序最多可以添加5个note。每个note有两个字段: void (*printnote)(); 与char *content;,其中printnote会被设置为一个函数,其函数功能为输出 content 具体的内容。
note的结构体定义如下:
add_note 函数代码如下:
print_note
print_note就是简单的根据给定的note的索引来输出对应索引的note的内容。
delete_note
delete_note 会根据给定的索引来释放对应的note。但是值得注意的是,在 删除的时候,只是单纯进行了free,而没有设置为NULL,那么显然,这里是存在Use After Free的情况的。
利用分析
我们可以看到 Use After Free 的情况确实可能会发生,那么怎么可以让它发生并且进行利用呢?需要同时注意的是,这个程序中还有一个magic函数,我们有没有可能来通过use after free 来使得这个程序执行magic函数呢?一个很直接的想法是修改note的printnote字段为magic函数的地址,从而实现在执行printnote 的时候执行magic函数。 那么该怎么执行呢?
我们可以简单来看一下每一个note生成的具体流程
程序申请8字节内存用来存放note中的printnote以及content指针。
程序根据输入的size来申请指定大小的内存,然后用来存储content。
那么,根据我们之前在堆的实现中所学到的,显然note是一个fastbin chunk(大小为16字节)。我们的目的是希望一个note的put字段为magic的函数地址,那么我们必须想办法让某个note的printnote指针被覆盖为magic地址。由于程序中只有唯一的地方对printnote进行赋值。所以我们必须利用写real content的时候来进行覆盖。具体采用的思路如下
申请note0,real content size为16(大小与note大小所在的bin不一样即可)
申请note1,real content size为16(大小与note大小所在的bin不一样即可)
释放note0
释放note1
此时,大小为16的fast bin chunk中链表为note1->note0
申请note2,并且设置real content的大小为8,那么根据堆的分配规则
note2其实会分配note1对应的内存块。
real content 对应的chunk其实是note0。
如果我们这时候向note2 real content的chunk部分写入magic的地址,那么由于我们没有note0为NULL。当我们再次尝试输出note0的时候,程序就会调用magic函数。
利用脚本
我们可以具体看一下执行的流程,首先先下断点
两处malloc下断点
两处free下断点
然后继续执行程序,可以看出申请note0时,所申请到的内存块地址为0x0804b008。(eax存储函数返回值)
申请note 0的content的地址为0x0804b018
类似的,我们可以得到note1的地址以及其content的地址分别为0x0804b040 和0x0804b050。
同时,我们还可以看到note0与note1对应的content确实是相应的内存块。
下面就是free的过程了。我们可以依次发现首先,note0的content被free
然后是note0本身
当delete结束后,我们观看一下bins,可以发现,确实其被存放在对应的fast bin中,
当我们将note1也全部删除完毕后,再次观看bins。可以看出,后删除的chunk块确实处于表头。
那么,此时即将要申请note2,我们可以看下note2都申请到了什么内存块,如下
申请note2对应的内存块为0x804b040,其实就是note1对应的内存地址。
申请note2的content的内存地址为0x804b008,就是note0对应的地址,即此时我们向note2的content写内容,就会将note0的put字段覆盖。
我们来具体检验一下,看一下覆盖前的情况,可以看到该内存块的printnote指针已经被置为NULL了,这是由fastbin的free机制决定的。
覆盖后,具体的值如下
可以看出,确实已经被覆盖为我们所想要的magic函数了。
最后执行的效果如下
同时,我们还可以借助gef的heap-analysis-helper 来看一下整体的堆的申请与释放的情况,如下
这里第一个输出了两次,应该是gef工具的问题。
题目
2016 HCTF fheap
Last updated