简单介绍
格式化字符串漏洞和栈溢出有相似之处,但又有所不同,都是利用了程序员的疏忽大意来改变程序运行的正常流程。
1、格式化字符串的介绍
printf()、fprint()等print()系列的函数可以按照一定的格式将数据进行输出。
实例举例:
printf("I am a %s","student");
执行这条语句后将会返回字符串:I am a student。
这个语句中的printf函数的参数%s就是格式化字符串,它告诉程序将数据以什么格式输出。
2、printf函数一般形式的介绍
printf()函数的一般形式为:printf("format",输出表列)
format就表示格式化符号
其中format的结构为:%(标志)(输出最小宽度)(精度)(长度)类型
其中常见的类型有以下几种:
①%d整型输出,%ld长整型输出
②%o以八进制输出
③%x以十六进制形式输出
④%u以十六进制数输出无符号型数据
⑤%c用来输出一个字符
⑥%s用来输出一个字符串
⑦%f用来输出一个实数,以小数形式输出
当我们控制了format参数之后,结合printf()函数的特性就可以进行相应的攻击了。
3、C语言中的格式化函数
C语言中的格式化函数(printf族函数,包括:printf、fprintf、sprintf、snprintf等)允许可变参数,它根据传入的格式化字符串获知可变参数的个数和类型,并依据格式化符号进行参数的输出。
格式化字符串漏洞的利用原理:
如果调用这些函数时,给出了格式化符号串,但没有提供实际对应参数时,这些函数会将格式化字符串后面的多个栈中的内容弹出作为参数,并根据格式化符号将其输出。
4、字符串溢出漏洞利用,常用的格式化符号
在利用格式化字符串溢出的时候,常见的格式化符号有%x,%s,%n:
①当格式化符号为%x时,以16进制的形式输出堆栈的内容;
②当格式化符号为%s时,则输出对应的地址所指向的字符串。
漏洞代码举例:
void function(char *buf)
{
int a=1;
printf(buf);
}
当向这个函数中传入的字符串为:"%x%x%x",则输出的结果是将栈中内容按十六进制输出,从而达到窃取栈内容目的。
③当格式化符号为%n时, 他的作用是将格式化函数输出字符串的长度,写入函数参数指定的位置。
解释%n的利用实例:
当一个输出函数的格式化字符为%n。需要注意的是:%n不向printf传递格式化信息,而是令printf把自己到该点已打出的字符总数放到相应变元(未知量)指向的整型变量中。
如:printf("yuanyexincun%n",&num)
这条语句将会向整型变量num写入整数5。
5、sprintf()函数的介绍
sprintf()函数的作用是把格式化的数据写入某个字符串缓冲区,函数原型为:
int sprintf(char *buffer,const char *format,[argument]...)
接下来写一个关于sprintf函数的格式化溢出漏洞代码:
int function(int argc,chat *argv[]) //argc表示字符串的个数;argv表示一个字符串
{
char buffer[100];
sprintf(buffer,argv[1]); //argv[1]是一个指向字符串的指针
}
这个函数咋一看感觉没什么问题。接一下仔细分析:该函数定义了一个长度为100的数组buffer,用来存放函数接受的字符串argv(此时可能存在缓冲区溢出)。然后调用sprintf函数将格式化数据写入buffer数组中。自此函数分析完毕。
这段代码如果正常输入数据,其实不会影响程序的正常执行,但是当攻击者向这个函数function函数传入的字符串为"aaaabbbb%n",则最后会将字符串的
长度8,写入到地址为0x61616161(aaaa)的内存单元。(a的ascall码为97,对应的十六进制数为61)
注意事项:当sprintf函数后面没有对应参数时,会从对堆栈中取出一个参数,将其作为整数指针使用。因为aaaabbbb传入栈中是小端存储,所以后入栈的参数是aaaa。