Unlink

题外:学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,这是再向其中写入一些数据便可达到一些利用。

原文地址:https://www.cnblogs.com/countfatcode/p/11668332.html