标准输出函数
fprintf、printf、sprintf 和 dprintf 是 C 语言中的标准输入输出函数,用于格式化输出数据到不同的目标(如文件、标准输出、字符串等)。它们都属于格式化输出家族,核心原理是基于格式化字符串输出特定的变量值。下面是它们的详细介绍:
1. fprintf
int fprintf(FILE *stream, const char *format, ...);
作用:向指定的文件流
stream中输出格式化的字符串。常见用法:用于将数据写入文件或其他输出流(如标准错误
stderr)。参数:
stream:指定目标流,常见的流包括stdout(标准输出)、stderr(标准错误输出)或文件流FILE *。format:格式化字符串,用于指定输出内容的格式。后面的参数:可变参数,表示具体要输出的数据。
返回值:成功时返回写入字符的数量,失败时返回负值。
示例:
FILE *file = fopen("output.txt", "w");
if (file) {
fprintf(file, "Hello, %s! You are %d years old.\n", "Alice", 25);
fclose(file);
}
2. printf
int printf(const char *format, ...);
作用:将格式化的字符串输出到标准输出(
stdout)。常见用法:最常见的输出函数,用于在控制台显示数据。
参数:
format:格式化字符串,用于指定输出内容的格式。后面的参数:可变参数,表示具体要输出的数据。
返回值:成功时返回写入字符的数量,失败时返回负值。
示例:
printf("Hello, %s! You are %d years old.\n", "Alice", 25);
3. sprintf
int sprintf(char *str, const char *format, ...);
作用:将格式化的字符串输出到指定的字符数组
str中。常见用法:将数据格式化为字符串存储,而不是输出到文件或标准输出。
参数:
str:输出的目标字符数组。format:格式化字符串,用于指定输出内容的格式。后面的参数:可变参数,表示具体要输出的数据。
返回值:成功时返回写入字符的数量(不包括终止字符
\0),失败时返回负值。注意:
sprintf不会检查缓冲区的大小,容易导致缓冲区溢出,应优先使用更安全的snprintf。
示例:
char buffer[100];
sprintf(buffer, "Hello, %s! You are %d years old.", "Alice", 25);
printf("%s\n", buffer); // 输出:Hello, Alice! You are 25 years old.
4. dprintf
int dprintf(int fd, const char *format, ...);
作用:向指定的文件描述符
fd输出格式化的字符串。常见用法:可以直接写入特定的文件描述符,类似于
printf和fprintf,但使用文件描述符而不是FILE *流。参数:
fd:目标文件描述符(常见的值包括1表示stdout,2表示stderr,以及通过系统调用如open返回的文件描述符)。format:格式化字符串,用于指定输出内容的格式。后面的参数:可变参数,表示具体要输出的数据。
返回值:成功时返回写入字符的数量,失败时返回负值。
示例:
int fd = open("output.txt", O_WRONLY | O_CREAT, 0644);
if (fd != -1) {
dprintf(fd, "Hello, %s! You are %d years old.\n", "Alice", 25);
close(fd);
}
各函数的对比与应用场景
|函数|目标|用途|备注| |-|-|-|-| |fprintf|文件流 FILE *|输出到文件或流|适用于将数据输出到文件、标准错误等| |printf|标准输出 stdout|输出到控制台|最常用的输出函数,默认输出到控制台| |sprintf|字符数组 char *|输出到字符串|将格式化数据保存到字符串,易溢出风险| |dprintf|文件描述符 int|输出到文件描述符|适用于文件描述符输出,如文件、管道等|
https://blog.csdn.net/u012763794/article/details/60955530修改标志位
示例
printf1
#include <stdio.h>
void main()
{
printf("%s %d %s","Hello World!",233,"\n");
}
gcc -m32 -o printf1 ./printf1

我们打一个断点到printf b *0x000011c2,run

已经能看到这个三个参数压入栈了,顺序是从右到左入栈,因为栈是LIFO,所以从右到左入栈,最后才能正确的从左到右出栈。这时候我们三个百分号对应的是后续的三个参数。

单步输出之后,输出Hello World! 233 \n
printf2
#include <stdio.h>
void main()
{
printf("%s %d %s %x %x %x %3$s","Hello World!",233,"\n");
}
这时候我们运行第二个程序,我们多写一点格式化字符串。

会发现打印了一些奇怪的值,我们进入gdb ,还是打断点到printf

0xe9就是233的十六进制

这时候我们发现,除了前面的Hello World! 233 \n 之后还多了ffffd020 f7fa4e34 0 \n
如果格式化字符串后面参数不够,自动会从栈上自动寻找,进而可以输出栈上内容。
printf3
#include <stdio.h>
void main()
{
char a[100];
if(fgets(a,sizeof a,stdin)==NULL)
return;
printf(a);
}
printf3就是读入一个数据,然后如果是0那就return 如果不是就打印。我们编译之后gdb

这时候我们打断点到fgets,run之后单步,然后输入kiciot %x %x %x %x

能看到已经入栈了,我们继续单步到printf




发现除了kiciot之外,还打印了四个栈上的内容。所以存在格式化字符串漏洞,我们就可以利用格式化字符串漏洞去泄露栈上的内容
评论