Heap Unlink

unlink

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* Take a chunk off a bin list */
// unlink p
#define unlink(AV, P, BK, FD) {                                            
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
      malloc_printerr ("corrupted size vs. prev_size");               
    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 (chunksize_nomask (P))                              
            && __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适用于small bin,且在最新的libc2.27及以上中,加入了新的机制,该攻击不再那么适用。但是对于该技巧的学习,有助于更好的理解堆操作。

在旧的unlink中,并没有size和双向链表的检查。那么unlink操作就相当于执行了以下操作:

1
2
3
4
FD = P -> fd;
BK = P -> bk;
FD -> bk = BK;
BK -> fd = FD;

假设我们在P -> fd中写入目标地址:dest_addr - 0x18,在P -> bk中写入修改的地址(例如某函数的got表地址)expect_addr。以上函数相当于:

1
2
3
4
FD = dest_addr - 0x18;
BK = expect_addr;
*(dest_addr - 0x18 + 0x18) = expect_addr
*(expect_addr + 0x10) = dest_addr - 0x18

我们将expect_addr写入了dest_addr的位置。通过这一点我们可以向任意的位置写任意的值。

添加了以下检查机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
···
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
      malloc_printerr ("corrupted size vs. prev_size");               
    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;  
···

它要求FD->bk = BK->fd = P,即*(P -> fd+0x18)==*(P -> bk+0x10)==P,所以*(P -> fd)=P-0x18*(P -> bk)=P-0x10

最终实现:

1
*P=P-0x18

此时,再编辑P所指chunk为某got表,就可以对got进行编辑。

应用的场景,存在一个管理堆指针的数组,这个数组我们无法直接操作,但是其P的附近,所以我们可以通过unlink改变其中的值,再将P指向我们想写入的地址(got表),实现任意地址写。

另外,因为我们要修改chunk header,所以需要想办法溢出或UAF。