06. 格式化字符串漏洞发生的条件

unqique-Elven's Blue Github Chart

1.实例程序1printf2.c

正常源码:

1
2
3
4
5
#include<stdio.h>
void main()
{
printf("%s,%d,%s","hello world!",233,"\n");
}

代码解释:可以知道第一个%s对应hello world!,%d对应233,第二个%s对应转义字符\n换行

提权:chmod 777 print2.c
编译:gcc -m32 -o printf2 printf2.c

调试:gdb printf2
查看mian函数:disass main

然后再printf函数加断点:b *0x56555552
再运行:

可以清楚的看到printf()函数里面的参数由于函数调用_cdecl被从右到左压栈
0xe9转化位十进制就是233
然后其实就是正常对应输出:

2.实例程序2printf3.c

漏洞源码:

1
2
3
4
#include<stdio.h>
void main(){
printf("%s %d %s %x %x %x %3$s","hello world!",233,"\n");
}

代码解释:第一个%s对应输出hello world,%d对应233,第二个%s对应转义字符\n换行,后面的格式化字符就对应后面堆栈里的随机内容了,注意%s$是输出对应第三行地址里面的内容,也就是换行\n

后面正常和上面一样的操作不多解释
哦对,加断点之前注意一个细节,注意运行完一遍,把地址调整号好正确

查看main函数,继续加断点:

运行:

前面的对应关系我们已经知道,但是分析后面的对应关系:
第一个%x对应输出后面的ffffd120
第二个%x又对应下一行0
可以看到后面的堆栈就看不到连续被省略,此时用命令:stack 0x20 查看20行堆栈

所以可以继续后看连续的堆栈地址:
第三个%x默认对应还是0
最后那个%3$s其实就是打印第三个参数的意思,所以也是换行
所以我们就可以回顾一下刚才执行完后为什么输出那些东西了。

注意:如果还有看不到的堆栈地址内容可以用以下方式查看:

这是x类命令概念

3.程序实例3printf4.c

源代码:

1
2
3
4
5
6
7
#include<stdio.h>
void main(){
char a[100];
if(fgets(a,sizeof a,stdin)==NULL)
return 0;
printf(a);
}

同上略!

fgets() 的原型为:

include <stdio.h>

char *fgets(char *s, int size, FILE *stream);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
s 代表要保存到的内存空间的首地址,可以是字符数组名,也可以是指向字符数组的字符指针变量名。
size 代表的是读取字符串的长度。
stream 表示从何种流中读取,可以是标准输入流 stdin,也可以是文件流,即从某个文件中读取,这个在后面讲文件的时候再详细介绍。
标准输入流就是前面讲的输入缓冲区。所以如果是从键盘读取数据的话就是从输入缓冲区中读取数据,即从标准输入流 stdin 中读取数据,所以第三个参数为 stdin。
一个程序:
纯文本复制
# include <stdio.h>
int main(void)
{
char str[20]; /*定义一个最大长度为19, 末尾是'\0'的字符数组来存储字符串*/
printf("请输入一个字符串:");
fgets(str, 7, stdin); /*从输入流stdin即输入缓冲区中读取7个字符到字符数组str中*/
printf("%s\n", str);
return 0;
}

此时先执行一次:

可以看到我们输入格式化字符串的话,会打印处对应堆栈内的随机内容!

然后下断点,还是要注意细节哦,先运行,地址才不是偏移:
b *0x56555607

运行:

单步执行+输入:

再观察:

堆栈也许看的不够,那可以像上面一样:stack 0x20 查看32行

由于一行堆栈只能存4字节内容,所以从0xffffd0a8到0xffffd0b4是连续储存我们输入的内容的。

后面继续单步执行到+68行,还会遇到判断标志位跳转
这里之前所过不多说:记得修改命令:set $eflag = 数值

+77行,执行printf函数,执行前先看堆栈详情:

可以看到,执行原理也是和上面的程序一样,字符原样输出,格式化字符接续匹配堆栈参数!
最后面其实还要注意fgets()函数会自动添加的换行操作!