
1、printf中的缺陷
格式化串漏洞产生于数据输出函数中对输出格式解析的缺陷。以最熟悉的printf函数为例,其参数应该含有两部分:格式控制符、待输出的数据列表,程序清单1如下:
#include"stdio.h"
main()
{
int a=44,b=77;
printf("a=%d,b=%d\n",a,b);
printf("a=%d,b=%d\n");
}
对于上述代码,第一个printf调用是正确的,第二介调用中则缺少输出数据的变量列表,那么第二个调用将引起编译错误还是照常输出数据?如果输出数据又将是什么类型的数据呢?将上述代码编译运行,结果如下:
a=44,b=77
a=4218928,b=44
press any key to continue
第二个a的值随机器的不同而异,第二次调用没有引起编译错误,程序正常执行,只是输出的数据有点出乎预料。使用debug调试一下,得到“a=4218928,b=44”的正相。
第一次调用printf的时候,参数按照从右向左的顺序入栈。
当第二次调用发生时,由于参数中少了输入数据列表部分,故只压入格式控制符参数,虽然函数调用时没有给出“输出数据列表”,但系统仍然按照“格式控制符”所指明的方式输出了栈中紧随其后的两个DWORD。现在应该明白输出“a=4218928,b=44”的原因了:4218928的16进制是0x00406030,是指向格式控制符“a=%d,b=%d\n”的指针44是残留下来的变量a的值。
2、用printf读取内存数据
至此为止,就个问题还只是一个Bug,算不上漏洞。但如果printf函数参数中的“格式控制符”可以被外界输入影响,那就是所谓的格式化串漏洞了。程序清单2如下:
#include"stdio.h"
int main(int argc,char**argv)
{
printf(argv[1]);
}
编译,运行程序,当我们向程序传入普通字符串(如“xpress”)时,将得到简单的反馈。但如果传入的字符串带有格式控制符时,printf就会打印出栈中“莫须有”的数据。例如,输入“%p,%p”等,实际可以读出栈中的数据,结果如下:
004010f5,00000002
3、用printf向内存写数据
只是允许读数据还不算很糟糕,但是如果配合上修改内存数据,就有可能引起进程劫持和恶意代码植入了。
在格式化控制符中,有一种鲜为人知的控制符%n,这个控制符用于把当前输出的所有数据的长度写回一个变量中去,程序清单3如下:
#include"stdio.h"
main(int argc,char**argv)
{
int len_print=0;
printf("before write:length=%d\n",len_print);
printf("xpressst:%d%n\n",len_print,&len_print);
printf("after write:length=%d\n",len_print);
}
第二次printf调用中使用了%n控制符,它会将这次调用最终输出的字符串长度写入变量len_print中。“xpressst:%d”长度为10,所以这次调用后len_print将被修改为10。