【PWN】溢出常见危险函数
本文总结了Pwn中常见的导致溢出的危险函数,主要分为四类:输入读取类(gets、scanf、fgets误用)、字符串拷贝类(strcpy、strncpy、memcpy)、字符串拼接类(strcat、strncat)及其他危险函数(sprintf、vsprintf)。这些函数由于缺乏边界检查或存在设计缺陷,当输入数据超过目标缓冲区大小时会导致溢出漏洞。文章通过具体代码案例演示了各类函数的危险用法,为
·
本文简单介绍一些在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
- 若src长度≥n,不会自动在dest末尾加\0,导致dest不是合法字符串;
- 若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;
}
更多推荐


所有评论(0)