unlink学习

unlink原理

unlink这种利用方式源于glibc堆管理的一种特性,即当free一个chunk时,若该chunk处于双向链表中,则会检测其相邻块是否空闲,若处于空闲状态,则会将该空闲块从双向链表中取出,然后合并,这个取出操作就是unlink。unlink其实就是删除双向链表中的目标结点,这个删除过程本质上是对其前后结点的指针重新赋值,若我们对其指针进行巧妙的设置,则可能控制任意内存地址空间,我们会有任意地址改写能力。

合并

向前合并:
查看下一个块是不是空闲的 ,下一个块是空闲的,如果下下个块(距离当前空闲块)的PREV_INUSE§位没有设置。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。
如果是空闲的,合并它。
现在将合并后的块添加到 unsorted bin 中。

向后合并:
查看前一个块是不是空闲的 –如果当前空闲块的PREV_INUSE§位没有设置, 则前一个块是空闲的。
如果空闲,合并它

基本过程如下:
在这里插入图片描述

源码

利用unlink的时候,有一个检查机制,这就要看看unlink的源码

#!c
1413    /* Take a chunk off a bin list */
1414    #define unlink(AV, P, BK, FD) {                                            
1415        FD = P->fd;                                                                      
1416        BK = P->bk;                                                                      
1417        if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
1418          malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
1419        else {                                                                      
1420            FD->bk = BK;                                                              
1421            BK->fd = FD;                                                              
1422            if (!in_smallbin_range (P->size)                                      
1423                && __builtin_expect (P->fd_nextsize != NULL, 0)) {                      
1424                if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)              
1425                    || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
1426                  malloc_printerr (check_action,                                      
1427                                   "corrupted double-linked list (not small)",    
1428                                   P, AV);                                              
1429                if (FD->fd_nextsize == NULL) {                                      
1430                    if (P->fd_nextsize == P)                                      
1431                      FD->fd_nextsize = FD->bk_nextsize = FD;                      
1432                    else {                                                              
1433                        FD->fd_nextsize = P->fd_nextsize;                              
1434                        FD->bk_nextsize = P->bk_nextsize;                              
1435                        P->fd_nextsize->bk_nextsize = FD;                              
1436                        P->bk_nextsize->fd_nextsize = FD;                              
1437                      }                                                              
1438                  } else {                                                              
1439                    P->fd_nextsize->bk_nextsize = P->bk_nextsize;                      
1440                    P->bk_nextsize->fd_nextsize = P->fd_nextsize;                      
1441                  }                                                                      
1442              }                                                                      
1443          }                                                                              
1444    }
1445    
1446    /*

检查机制

 if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      
        malloc_printerr (check_action, "corrupted double-linked list", P, AV);  

在unlink之前会检查一下当前结点的前一个结点的后一个节点和当前结点的后一个节点的前一个节点是不是同一个结点,要想成功unlink,必须绕过这个检测,一种思路是将chunk->fd设置为(&chunk_addr - 0x18),因为:

chunk->fd->bk == *(chunk->fd + 0x18) == *(&chunk_addr - 0x18 + 0x18) == chunk_addr

这样就满足了检测条件,同理需要将chunk->bk设置为(&chunk_addr-0x10)。检测之后进行unlink操作

chunk->bk->fd = chunk->fd ==> *(chunk->bk + 0x10) = chunk->fd
==> *(&chunk_addr) = (&chunk_addr - 0x18)
 
chunk->fd->bk = chunk->bk ==> *(chunk->fd + 0x18) = chunk->bk
==> *(&chunk_addr) = (&chunk_addr - 0x10)

由于进行了两次赋值操作,所以只有第二次的有效,这样好像也看不出什么东西。这里我们假设有一个字符串数组strings,其第一个元素为strings[0],是char类型,假设我们已经构造好一个chunk,它满足chunk->fd == &strings[0] - 0x10,chunk->bk == &strings[0] - 0x18,unlink进行strings[0] = &strings[0] - 0x10,若我们本来可以对strings[0]的内容进行修改,那么unlink后我们实际上修改的是&strings[0] - 0x10处,进而覆盖到(&string[0]),若向*(&strings[0])写入目标地址,再进行strings[0]的写操作就能写入数据到目标地址内存中。

当然,上述利用存在一些细节问题,如string[0]和chunk的地址并非一致,这导致无法绕过检测,所以必须伪造chunk写入string[0]中才行,在具体利用的例子中可以看到如何伪造chunk。

伪造chunk

伪造chunk首先我们要知道chunk在free前和free后是不同的状态,这也是chunk的神奇之处。
在这里插入图片描述
在这里插入图片描述
正常情况下unlink之后会把P从双链表中解链,如果我们伪造了一个chunk,将fd=mem-0x18,bk=mem-0x10就可以绕过这个检查,还有一个要注意的是伪造chunk的fake_size位的最后一个标志为P的设置,size的最后一个标志位P表示了前一个chunk的使用状态,P=1表示前一个chunk处于使用状态,P=0表示前一个chunk处于空闲状态,我们通过控制P位就可以控制合并,当我们将FD=mem-0x18,BK=mem-0x10时,就表示我们已经拥有了任意地址写的能力,例如我们用合适的payload将[email protected]写入。p就变成了[email protected],那么再改一次p,把[email protected]改为shellcode的地址或者说system的地址都可以。之后再调用free功能,就可以任意命令执行。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值