【PWN笔记】Heap-Unlink原理

Unlink原理

unlink 是当我们去 free 一个 chunk 的时候,如果这个 chunk 的前一个或后一个是 free 的状态,glibc 会把它从链表里面取出来,与现在要 free 的这个合并再放进去,取出来的这个过程就是 unlink

unlink的代码

define unlink(AV, P, BK, FD) {                                            
    FD = P->fd;                                    
    BK = P->bk;                                    
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))           
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
    else {                                    
        FD->bk = BK;                               
        BK->fd = FD;                               
        if (!in_smallbin_range (P->size)                   
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {             
        if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)          
        || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
          malloc_printerr (check_action,                      
                   "corrupted double-linked list (not small)",    
                   P, AV);                        
            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的操作可以简单理解为将P的上一个chunk和P的下一个chunk连接,使P脱离双链表,简化为:

FD=P->fd;
BK=P->bk;
FD->bk=BK;
BK->fd=FD;

wiki的示意图

unlink_smallbin_intro

执行unlink时, 函数会检查当前空闲chunk的前一个chunk的bk指向是不是自己本身或者当前chunk的后一个chunk的fd指向是不是自己,如果不是则会抛出异常。

unlink是利用glibc malloc 的内存回收机制造成攻击的,核心就在于当两个free的堆块在物理上相邻时,会将他们合并,并将原来free的堆块在原来的链表中解链,加入新的链表中,但这样的合并是有条件的,向前或向后合并。但这里的前和后都是指在物理内存中的位置,而不是fd和bk链表所指向的堆块。
以当前的chunk为基准,将preivous free chunk合并到当前chunk称为向后合并,将后面的free chunk合并到当前chunk就称为向前合并。

向后合并

/* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      unlink(p, bck, fwd);
    }
#define chunk_at_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))

首先检测当前free chunk的PREV_INUSE(P)标志位,如果为0表示前一个chunk为free状态,但内存中第一个申请的chunk的前一个chunk一般都被认为在使用中,所以不会发生向后合并。
如果不是内存中的第一个chunk且它的前一个chunk标记为free状态时,发生向后合并:

  1. 修改chunk的size位大小为两个chunk size之和
  2. 将指针移动到前一个chunk处
  3. 调用unlink将前一个chunk从它所在的链表中移除。

向前合并

if (nextchunk != av->top) {
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

      /* consolidate forward */
      if (!nextinuse) {
    unlink(nextchunk, bck, fwd);
    size += nextsize;
      } else
    clear_inuse_bit_at_offset(nextchunk, 0);

#define clear_inuse_bit_at_offset(p, s)\
 (((mchunkptr)(((char*)(p)) + (s)))->size &= ~(PREV_INUSE))

向前合并不修正P的指针,只增加size大小。
在合并后glbc malloc会将合并后新的chunk加入unsorted bin中,加入第一个可用的chunk之前,更改自己的size字段将前一个chunk标记为已用,再将后一个chunk的previous size改为当前chunk的大小。

利用

  1. 检查 P 的 size 字段 和 P 的 next_chunk 中记录的 PREV_SIZE 是否一致,不一致则出现corrupted size vs. prev_size的错误

  2. 检查是否满足 P->fd->bk == P 和 P->bk->fd == P,否则出现corrupted double-linked list错误

    为了绕过这个检查,需要以下条件

  • 程序中存在一个全局指针变量 ptr

  • ptr 指向的对内存可由用户控制

    若具备以上条件,攻击者可在指针 ptr 指向的内存中伪造一个空闲 chunk P,根据 ptr 构造合适的地址覆盖 chunk P 的 fd 和 bk,使得 P->fd->bk == P && P->bk->fd == P 成立。具体如下(64 位):

    P->fd = ptr - 0x10P->bk = ptr - 0x18
  • 实际上就是当 P->fd 或 P->bk 所存地址解释为 chunk 结构时其对应的 fd 或 bk 都为 P 的地址

    在执行 unlink(P)时的指针操作如下:

    1)FD = P->fd = ptr - 0x10;2)BK = P->bk = ptr - 0x18;// FD->bk = ptr - 0x10 + 0x10 = ptr; BK->fd = ptr -0x18 + 0x18 = ptr// 由于 ptr 指向 P,可成功绕过指针校验3)FD->bk = BK,即 *ptr = ptr - 0x10;4)BK->fd = FD,即 *ptr = ptr - 0x18。

    由以上过程可知,借助指向 chunk P 的 ptr 指针可绕过 “corrupted double-linked list” 安全机制,并通过 unlink 攻击实现写内存,最终使得 ptr 指向 ptr - 0x18。

    unlink 后,对 ptr 指向的内存进行写入,如 ‘A’\*0x18 + free@got,使得 ptr 指向 free@got,再次对 ptr 指向的内存进行写入,可以把 free@got 修改为 system 的地址,之后调用 free 可任意命令执行。

参考链接

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇