"not_the_same_3dsctf_2016"是一道经典的CTF(Capture The Flag)二进制利用挑战题,源自2016年3DSCTF赛事。这道题之所以在安全圈内持续被讨论,是因为它巧妙地结合了栈溢出漏洞利用和非常规ROP(Return-Oriented Programming)技巧,需要选手突破常规思维模式才能完成攻击链的构建。
题目运行在x86架构的Linux环境,提供了一个32位的可执行文件。当用checksec工具检查时,会发现程序开启了NX(No-eXecute)保护但未启用ASLR(Address Space Layout Randomization),这意味着我们需要在不执行栈上代码的前提下,通过现有代码片段组合实现攻击目标。
关键提示:题目名称中的"not_the_same"暗示着解题方法与此前常见的3DSCTF题目存在显著差异,这往往是CTF命题者留下的重要线索。
使用IDA Pro反编译工具分析二进制文件,可以快速定位到main函数中存在危险的gets()函数调用:
c复制int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[50]; // [esp+12h] [ebp-36h] BYREF
setvbuf(stdout, 0, 2, 0);
printf("Enter your name: ");
gets(s);
return printf("Hello, %s!\n", s);
}
这里gets()读取用户输入到长度仅为50字节的栈缓冲区s中,但未做任何长度限制,导致经典的栈溢出漏洞。通过计算可知,从缓冲区起始到返回地址的偏移量为:
程序的安全防护特点决定了我们的利用方式:
特别值得注意的是,程序编译时使用了非标准的链接选项,移除了许多常规的gadget(如pop-pop-ret序列),这正是题目"not_the_same"的核心难点所在。
由于常规ROP gadget匮乏,我们需要采用特殊方法寻找可用指令片段:
bash复制ROPgadget --binary not_the_same_3dsctf_2016 | less
经过分析,我们发现以下几个关键gadget:
0x0806f3f0 : ret - 简单的返回指令0x080483b8 : pop ebx ; ret - 唯一可用的pop指令0x0804eef0 : mov dword ptr [edx], eax ; ret - 内存写操作通过组合有限的gadget,我们可以构建任意内存写入能力:
mov [edx], eax完成写入这种技术被称为"面向返回的编程增强版"(ROP+),需要精心设计每个内存单元的写入顺序。
由于程序调用了printf等库函数,我们可以分步覆写GOT表项:
具体步骤需要精确计算每个写入操作的地址和值,通常需要10-15个ROP链节点。
完整的ROP攻击链包含以下阶段:
Python exploit代码框架如下:
python复制from pwn import *
context(arch='i386', os='linux')
elf = ELF('./not_the_same_3dsctf_2016')
rop = ROP(elf)
# 自定义ROP链构建
rop.raw(0x080483b8) # pop ebx
rop.raw(0x0804a040) # .data地址
rop.raw(0x0806f3f0) # ret对齐
# 继续构建后续链...
每个内存写入操作需要精确计算:
使用pwntools的调试功能可以大幅提高开发效率:
python复制io = process('./not_the_same_3dsctf_2016')
gdb.attach(io, '''
break *0x080485AA
continue
''')
调试时重点关注:
当.data段空间不足时,可以瞄准.init_array段:
在无法直接控制栈指针的情况下:
当关键指令缺失时,可以尝试:
栈对齐问题:x86架构要求栈16字节对齐,缺少ret指令会导致崩溃
内存权限错误:尝试写入只读区域
gadget副作用:某些指令会破坏关键寄存器
这道题的精妙之处在于它迫使攻击者跳出常规ROP的舒适区,去探索更底层的指令组合可能性。在实际漏洞利用中,这种灵活思维往往比工具的使用更重要。我建议在掌握基本解法后,尝试用不同方法实现攻击链,这对理解计算机体系结构也大有裨益。