题外:学pwn快三个月了,从刚开始的师傅领进门到现在的个人修行,期间经历了太多的辛酸,在2019年10月12日晚9:40分,我终于做出了我的第一个堆题(原谅我这个菜鸡的不易),这道题是关于unlink的利用,特写此博客记录自己的所得。(笔者的环境是64位系统)
Unlink原理:
假设申请了两个大小分别为0x30和0x80的chunk,其中chunk1_head=0x4000h,chunk2_head=0x4040h,然后把返回的地址存在首地址为0x3000h的数组中,如果这时我们向chunk1中写入如下数据payload=p64(0)+p64(0x30)+p64(0x3040-0x18)+p64(0x0340-0x10)+'a'*16然后在free chunk2,便可以得到0x3028的地址,如下:
具体的原理是ptmalloc在free chunk2时会检查其前后的chunk是否为空,这里我们忽略其后的chunk,只看chunk1。由于我们覆盖chunk2的size为0x80,其最低位被覆盖为0,表示此chunk为空闲,那么ptmalloc会利用unlink把chunk1从bins中取出然后进行合并。unlink的源码如下:
/* Take a chunk off a bin list. */ static void unlink_chunk (mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size"); mchunkptr fd = p->fd; mchunkptr bk = p->bk; if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) malloc_printerr ("corrupted double-linked list"); fd->bk = bk; bk->fd = fd; if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL) { if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) malloc_printerr ("corrupted double-linked list (not small)"); if (fd->fd_nextsize == NULL) { if (p->fd_nextsize == p) fd->fd_nextsize = fd->bk_nextsize = fd; else { fd->fd_nextsize = p->fd_nextsize; fd->bk_nextsize = p->bk_nextsize; p->fd_nextsize->bk_nextsize = fd; p->bk_nextsize->fd_nextsize = fd; } } else { p->fd_nextsize->bk_nextsize = p->bk_nextsize; p->bk_nextsize->fd_nextsize = p->fd_nextsize; } } }
我们可以看出在unlink主要有两个检查:
- chunksize (p) != prev_size (next_chunk (p)
- __builtin_expect (fd->bk != p || bk->fd != p, 0)
- 注意这里的p指向的是chunk1。
要绕过第一个检查,我们必须使伪造的chunk大小等于其后一个chunk的prev_size的大小,即地址为0x4018h的内存块里的内容和地址为0x4040h的内存块里的内容相等。
在进行第二检查前,ptmalloc会先执行fd=p->fd,bk=p->bk,即取p前后chunk的地址,然后检查p前后两个chunk的bk和fd指针是否指向p,这里我们覆盖p->fd为0x3040-0x18,故fd指向地址为0x3028h的内存块,bk指向地址为0x3030h的内存块。
- fd->bk!=p,即判断 *(0x3028+0x18)是否等于p,
- bk->fd!=p,即判断 *(0x3030+0x10)是否等于p,
可知等于我们伪造的chunk的首地址。因此可以绕过第二个检查。(上图2地址为0x3040h的内存块里的内容在unlink前应是0x4010h,这里笔者偷个懒,直接把最终结果放上去了)
之后执行fd->bk=bk,即*(0x3028+0x18)=0x3030,执行bk->fd=fd,即*(0x3030+0x10)=0x3028,故最后地址为0x3040的内存块里的内容会被修改为0x3028,这是再向其中写入一些数据便可达到一些利用。