05_格式化字符串发生的条件
标准输出函数
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之外,还打印了四个栈上的内容。所以存在格式化字符串漏洞,我们就可以利用格式化字符串漏洞去泄露栈上的内容