本文简单介绍一些在Pwn中可能导致溢出的函数场景,部分函数给出了相关案例。

一、输入读取类函数

这类函数直接从输入流读取数据到缓冲区,是栈溢出最常见的触发点。

1. gets

gets(char *s)
  • 从stdin读取一行字符串到s,遇到换行停止。
  • 极度危险,无任何长度限制,输入多长就存多长
  • C11 标准已移除该函数,编译会报warning,但仍可用。
// 案例演示
#include <stdio.h>
#include <string.h>

void vulnerable() {
    char buf[32];  // 栈上的局部缓冲区,大小32字节
    gets(buf);     // 危险函数:无长度限制
    strcpy(buf, "hello"); // 若buf已被超长输入覆盖,此处会加剧溢出
}

int main() {
    vulnerable();
    return 0;
}

2. fgets

fgets(char *s, int size, FILE *stream)
  • 读取最多size-1个字符到s,自动加\0
  • 本身是安全函数,但误用会变危险
  • 若size被设置为变量,且变量被整数溢出篡改(如size变成负数,在 32 位程序中会被解释为超大正数),则会溢出
#include <stdio.h>

void vulnerable_func() {
    // 栈上仅开辟32字节的缓冲区
    char input_buf[32];
    
    printf("请输入内容:");
    // 漏洞核心:size=64(远大于缓冲区32字节),fgets会最多读取63个字符
    // 输入超过32字节时,超出部分会覆盖栈内存,触发溢出
    fgets(input_buf, 64, stdin); 

    printf("你输入的内容:%s\n", input_buf);
}

int main() {
    // 关闭缓冲区干扰,不影响核心逻辑
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    
    vulnerable_func();
    return 0;
}

3. scanf

scanf("%s", s)
fscanf(stdin, "%s", buf);
  • 当读取内容为%s,即读取字符串时,其遇到空白符停止。
    • 字符可以是空格、换行和制表符
  • 不指定长度时,和gets等价;即使指定%10s,若缓冲区长度 < 10 仍会溢出
// 存在溢出漏洞的函数
void vulnerable_func() {
    // 栈上开辟32字节的缓冲区(核心:固定大小,无长度校验)
    char input_buf[32];
    
    // 危险用法:scanf("%s", input_buf) 完全不限制输入长度
    printf("请输入你的名字:");
    scanf("%s", input_buf);  // 漏洞核心:%s格式符无长度限制
    
    // 正常输出(溢出后此处可能无法执行)
    printf("你好,%s!\n", input_buf);
}

int main() {
    vulnerable_func();
    return 0;
}

4. read

read(int fd, void *buf, size_t count)
  • 系统调用,无任何缓冲区边界检查,仅按count参数读取字节到buf
  • 用户可控count(如输入指定读取长度),或count设为缓冲区大小的 2 倍,直接溢出栈 / 堆缓冲区
#include <stdio.h>
#include <unistd.h>

void vulnerable_func() {
    char buf[32];  // 32字节缓冲区
    // 漏洞:read的count=64(远大于buf大小),读取64字节到buf,直接溢出
    read(0, buf, 64); // 0是标准输入fd
    printf("输入内容:%s\n", buf);
}

int main() {
    vulnerable_func();
    return 0;
}

5. recv/recvfrom

recv/recvfrom(int sockfd, void *buf, size_t len, int flags)
  • 网络场景的输入读取函数,逻辑同read,无长度检查
  • 用户通过网络发送超长数据,len参数过大触发溢出

二、字符串拷贝类函数

这类函数的核心问题是不检查目标缓冲区的剩余空间,或拷贝逻辑存在缺陷。

1. strcpy

strcpy(char *dest, const char *src)
  • 将src字符串拷贝到dest,遇到\0停止
  • 完全不校验dest缓冲区大小,src长度超过dest就会溢出
  • src字符串长度 > dest缓冲区长度
#include <stdio.h>
#include <string.h>

void vulnerable_func() {
    // 栈上开辟32字节的目标缓冲区
    char dest_buf[32];
    // 源字符串:用户输入(可控),通过scanf读取(先暂存到temp,再用strcpy拷贝)
    char temp[100]; // 临时缓冲区,仅用于接收用户输入
    
    printf("请输入任意内容:");
    scanf("%s", temp); // 读取用户输入(无长度限制,仅为了获取超长字符串)
    
    // 漏洞核心:strcpy无长度校验,直接拷贝temp到dest_buf
    // 若temp长度>32,超出部分会覆盖dest_buf所在的栈内存
    strcpy(dest_buf, temp);
    
    printf("你输入的内容:%s\n", dest_buf);
}

int main() {
    vulnerable_func();
    return 0;
}

2. strncpy

strncpy(char *dest, const char *src, size_t n)
  • 最多拷贝n个字符到dest
    1. 若src长度≥n,不会自动在dest末尾加\0,导致dest不是合法字符串;
    2. 若n大于dest长度,仍会溢出
  • n > dest缓冲区长度,或src长度≥n导致后续字符串操作越界
#include <stdio.h>
#include <string.h>

void vulnerable_func() {
    // 栈上32字节的目标缓冲区(核心:固定大小)
    char dest_buf[32];
    // 临时缓冲区:接收用户输入(无长度限制,方便输入超长内容)
    char input[100];

    printf("=== strncpy溢出测试 ===\n");
    // 步骤1:接收用户输入(scanf无长度限制,可输入任意长字符串)
    scanf("%99s", input);

    // 步骤2:漏洞核心:strncpy的n=32(等于缓冲区大小)
    // 若用户输入≥32字节,strncpy拷贝32个字符后,dest_buf无\0
    strncpy(dest_buf, input, sizeof(input));

    printf("最终内容:%s\n", dest_buf);
}

int main() {
    // 关闭缓冲区刷新干扰,确保输入/输出即时生效
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    vulnerable_func();
    return 0;
}

3. memcpy

memcpy(void *dest, const void *src, size_t n)
  • 拷贝n个字节的数据,不区分字符串
  • 不校验dest缓冲区是否能容纳n个字节,纯字节流拷贝
  • n > dest缓冲区剩余空间
  • 注意:memcpy是二进制安全的(不依赖\0),但正是因为不做边界检查,成为堆溢出、整数溢出场景的常用 “跳板”。
#include <stdio.h>
#include <string.h>

// 极简漏洞函数:仅输入内容,copy_len=输入长度
void vulnerable_func() {
    char dest_buf[32];  // 目标缓冲区仅32字节(核心限制)
    char input_buf[100];// 接收用户输入的临时缓冲区

    printf("输入任意内容(超过32字符触发溢出):");
    // 安全读取输入(限制99字符,避免input_buf自身溢出)
    scanf("%99s", input_buf);

    // 核心:copy_len = 输入内容的实际长度(用户输入多长,就拷贝多长)
    int copy_len = strlen(input_buf);
    // 漏洞触发:memcpy不检查dest_buf大小,copy_len>32时直接溢出
    memcpy(dest_buf, input_buf, copy_len);

    // 溢出后大概率执行不到这行
    printf("拷贝结果:%s\n", dest_buf);
}

int main() {
    vulnerable_func();
    return 0;
}

三、字符串拼接类函数

这类函数会从dest的末尾\0位置开始拼接src,同样不检查dest剩余空间。

1. strcat

strcat(char *dest, const char *src)
strlcat(char *dest, const char *src, size_t size)
  • 将src拼接到dest末尾
  • 不检查dest剩余空间,拼接后总长度超过dest缓冲区就会溢出

2. strncat

strncat(char *dest, const char *src, size_t n)
  • 最多拼接n个字符到dest
  • 若dest剩余空间 < n,仍会溢出;但会自动加\0,比strncpy稍安全

四、其他危险函数

这类函数虽不直接是拷贝 / 输入函数,但滥用会间接导致溢出。

1. sprintf

sprintf(char *str, const char *format, ...)
snprintf(char * str, size_t size, const char * format, ... );
  • 将格式化字符串写入str
  • 不检查str缓冲区大小,格式化后的字符串过长就会溢出
  • 与格式化字符串漏洞结合时危害加倍
#include <stdio.h>
#include <string.h>

void vulnerable_func() {
    // 栈上仅开辟32字节的目标缓冲区(核心限制)
    char dest_buf[32];
    // 接收用户输入的临时缓冲区(避免自身溢出)
    char input_buf[100];

    printf("输入任意内容(超过32字符触发溢出):");
    // 安全读取输入(限制99字符,仅为了聚焦sprintf漏洞)
    scanf("%99s", input_buf);

    // 漏洞核心:sprintf不检查dest_buf大小,直接将输入内容格式化写入
    // 若input_buf长度>32,格式化后的字符串会超出dest_buf边界,触发溢出
    sprintf(dest_buf, "%s", input_buf);

    // 溢出后大概率执行不到这行
    printf("格式化结果:%s\n", dest_buf);
}

int main() {
    vulnerable_func();
    return 0;
}

2. vsprintf

vsprintf(char *str, const char *format, va_list arg)
  • 功能同sprintf,参数为可变参数列表
  • 同sprintf,无边界检查
#include <stdio.h>
#include <string.h>
#include <stdarg.h>  // 必须包含:va_list/va_start/va_end的头文件

// 封装vsprintf的函数(模拟真实代码中对vsprintf的调用)
void my_vsprintf(char *dest, const char *format, ...) {
    va_list args;          // 定义可变参数列表
    va_start(args, format);// 初始化参数列表(绑定到format之后的参数)
    
    // 漏洞核心:vsprintf不检查dest缓冲区大小,直接写入格式化内容
    vsprintf(dest, format, args);
    
    va_end(args);          // 释放参数列表
}

void vulnerable_func() {
    char dest_buf[32];     // 仅32字节的目标缓冲区(核心限制)
    char input_buf[100];   // 接收用户输入的临时缓冲区

    printf("输入任意内容(超过32字符触发溢出):");
    scanf("%99s", input_buf);  // 安全读取输入,聚焦vsprintf漏洞

    // 调用封装函数:将用户输入通过vsprintf写入dest_buf
    my_vsprintf(dest_buf, "%s", input_buf);

    // 溢出后大概率执行不到这行
    printf("vsprintf结果:%s\n", dest_buf);
}

int main() {
    vulnerable_func();
    return 0;
}
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐