UAF漏洞初探

UAF原理

上代码!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main(){
char *p1;
p1=(char *)malloc(sizeof(char)*10);
memcpy(p1,"hello",10);
printf("p1 addr:%x,%s\n",p1,p1);
free(p1);
char *p2;
p2=(char *)malloc(sizeof(char)*10);
memcpy(p2,"world",10);
printf("p2 addr:%x,%s\n",p2,p1);
return 0;
}

如上代码所示,
指针p1申请内存,打印其地址和字符串,然后释放p1
然后指针p2申请同样大小的内存,打印p2的地址和值
GCC编译,运行结果如下:
1.png
p1与p2地址相同,p1指针释放后,p2申请相同大小的内存,操作系统会将之前给p1分配的地址分配给p2,修改p2的值,p1也被修改了。

结论:
1.在free掉一块内存后,紧接着申请大小相同的内存,操作系统会将刚刚free掉的内存再次分配。
2.通过p2能够操作p1,如果之后p1继续被使用(use after free),则可以通过p2修改程序功能等目的。

根本原因:说来话长,可以尝试理解dlmalloc

dlmalloc详解

c语言程序会向栈和堆申请内存,小块内存向栈申请,函数调用结束后程序会自动释放内存。大块内存向堆申请,需要自己释放,否则会造成内存泄露。

堆的申请涉及到动态内存管理,包括内核层面和用户层面。linux内核向应用程序提供了接口(系统调用brk和mmap),当应用程序需要申请内存时向内核提出请求。

内存分配器避免了应用程序直接调用brk和mmap,应用程序在申请内存时,直接向内存分配器提交申请。有了内存分配器,我们可以避免brk和mmap的系统调用而直接通过malloc和free两个接口函数管理内存。申请内存时,内存分配器会一次向内核申请大量内存,然后分批交给应用程序,从而提高了效率。释放内存时,应用程序也是将内存释放给内存分配器,内存分配器在合适的时候再将内存释放回内核。

dlmalloc就是一种内存分配器,用于Android系统。而linux中采用的是ptmalloc内存分配器,它在dlmalloc的基础上进行了改进,以更好适应多线程。dlmalloc采用两种方式申请内存,如果应用程序单次申请的内存量小于256kb,dlmalloc调用brk()扩展进程堆空间,但是dlmalloc向内存申请的内存量大于应用程序申请的内存量,申请到内存后dlmalloc将内存分成两块,一块返回给应用程序,另一块作为空闲内存先保留起来。下次应用程序申请内存时dlmalloc就不需要向内核申请内存了,从而加快分配效率。当应用程序调用free释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放回内核,二是供应用程序下次申请内存使用。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放回内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内存内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。

brk()系统调用可以扩充或收缩堆的大小,mmap()向内核申请内存,munmap则释放内存。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!