格式化字符串攻击原理及示例.RP

格式化字符串攻击原理及示范

1、类printf函数簇落成原理

类printf函数的最大的性情就是,在函数定义的时候不能够知晓函数实参的数额和项目。

对此那种情景,能够运用省略号内定参数表。

带有省略号的函数定义中,参数表分为两局地,前半局地是明确个数、分明项目标参数,第叁片段正是省略号,代表数量和体系都不分明的参数表,省略号参数表中参数的个数和参数的类型是先行的约定总括出来的,各个实参的地址(指针)是依赖规定参数表中最终3个实参的地点算出来的。

此间涉及到函数调用时的栈操作。函数栈的栈底是高地址,栈顶是底地址。在函数调用

时函数实参是从最终四个参数(最右侧的参数)到第三个参数(最左侧的参数)依次被压入栈顶方向。也正是说函数调用时,函数实参的地点是持续的,并且从左到右地址是各样扩充的。如:

 1 #include <stdio.h>  
 2 #include <stdlib.h>  
 3   
 4 void fun(int a, ...)  
 5 {  
 6     int i;  
 7     int *temp = &a;  
 8     temp++;  
 9     for (i = 0; i < a; ++i)  
10     {  
11         printf("%d ",*temp);  
12         temp++;  
13     }  
14     printf("/n");  
15 }  
16   
17 int main()  
18 {  
19     int a = 1;  
20     int b = 2;  
21     int c = 3;  
22     int d = 4;  
23     fun(4, a, b, c, d);  
24     return 0;  
25 }   

在上头的例证中,void fun(int a,
…)函数约定第2个规定参数表示省略号参数表中参数的个数,省略号参数表中的参数全皆以int
类型的,那样fun函数就足以健康干活了。

类printf函数簇的行事规律和fun函数是千篇1律的,只可是更为复杂和精细。

如printf的函数方式为 int printf(const char *fmt, …)。

出于printf函数达成的功力比较复杂,大家来看一个大家友好达成的myprintf函数,改函数不涉及低层系统io操作。

 1 #include <stdio.h>  
 2 #include <stdlib.h>  
 3   
 4 void myprintf(char* fmt, ...) //一个简单的类似于printf的实现,//参数必须都是int 类型  
 5 {  
 6     char* pArg=NULL; //等价于printf原始实现的va_list  
 7     char c;  
 8     pArg = (char*) &fmt; //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值  
 9      pArg += sizeof(fmt); //等价于原来的va_start  
10   
11     do  
12     {  
13         c =*fmt;  
14         if (c != '%')  
15         {  
16             putchar(c); //照原样输出字符  
17           }  
18         else  
19         {  
20             //按格式字符输出数据  
21                switch(*++fmt)  
22             {  
23                 case 'd':  
24                     printf("%d",*((int*)pArg));  
25                     break;  
26                 case 'x':  
27                     printf("%#x",*((int*)pArg));  
28                     break;  
29                 default:  
30                     break;  
31             }  
32             pArg += sizeof(int); //等价于原来的va_arg  
33         }  
34         ++fmt;  
35     }while (*fmt != '/0');  
36     pArg = NULL; //等价于va_end  
37     return;  
38 }  
39   
40 int main(int argc, char* argv[])  
41 {  
42     int i = 1;  
43     int j = 2;  
44     myprintf("the first test:i=%d/n",i,j);  
45     myprintf("the secend test:i=%d; %x;j=%d;/n",i,0xabcd,j);   
46     return 0;  
47 }  

myprintf函数中也有像样的预定,分明参数表中最终2个参数是一个const char*
类型的字符串,在这些字符串中冒出“%d”和“%x”次数的和正是简约号参数表中参数的个数,省略号参数表中的参数类型也都以int类型。

如出一辙的,实际的printf函数也有那样的约定:明确参数表中最后贰个参数是几个const
char*
类型的字符串,省略号参数表中参数个数正是那一个字符串中冒出的“%d”,“%x”,“%s”…次数的和,省略号参数表中参数的项目也是由“%d”,“%x”,“%s”……等格式化字符来提示的。

由此,类printf函数中省略号参数表中参数的个数和体系都以由类printf函数中的那么些格式化字符串来调节的。

二、格式化字符串攻击原理

因为类printf函数中省略号参数表中参数的个数和连串都是由类printf函数中的那多少个格式化字符串来决定的,所以攻击者能够运用编程者的大意或漏洞,玄妙构造格式化字符串,达到攻击目的。

尽管多个技士的义务是:打字与印刷输出三个字符串大概把这一个串拷贝到某缓冲区内。他能够写出如下的代码:printf(“%s”,
str);可是为了省去时间和提升功效,并在源码中少输入四个字节,他会那样写:printf(str);

何以工程师写的是指鹿为马的吧?他传播了二个她想要逐字打字与印刷的字符串。实际上该字符串被printf函数解释为一个格式化字符(formatstring),printf就会根据该字符串来支配printf函数中省略号参数表中参数的格式和类型,若是这些程序猿想要打字与印刷的字符串中正好有“%d”,“%x”之类的格式化字符,那么贰个变量的参数值就从商旅中收取。

比如:

 1 #include <stdio.h>  
 2 #include <stdlib.h>   
 3   
 4 int main(int argc, char* argv[])  
 5 {  
 6     if(argc != 2)  
 7         return 0;  
 8     printf(argv[1]);  
 9     return 0;  
10 }

当./a.out “hello world”时一切符合规律,然而当./a.out
“%x”时,就会有无缘无故的数字被打字与印刷出来了。

很鲜明,攻击者至少能够透过打字与印刷出货仓中的那一个值来窥探程序的内部存款和储蓄器。不过多少职业就不那么分明了,这些轻便的荒唐允许向运维中先后的内部存款和储蓄器里写入大四值。

printf有二个相比较另类的用法:%n,当在格式化字符串中相遇”%n”的时候,在%n域从前输出的字符个数会保留到下2个参数里。例如,为了博取在五个格式化的数字之间空间的偏量:

1 int main(int argc, char* argv[])  
2 {  
3     int pos, x = 235, y = 93;  
4     printf("%d %n%d/n", x, &pos, y);  
5     printf("The offset was %d/n", pos);  
6     return 0;  
7 }

输出4(“235 ”的长度)

%n格式重临应该被输出的字符数目,而不是实在出口的字符数目。当把3个字符串格式化输出到三个定长缓冲区内时,输出字符串可能被截短。不考虑截短的震慑,%n格式表示只要不被截短的偏量值(输出字符数目)。为了声明那一点,上边包车型客车代码会输出100而不是20:

1 int main()  
2 {  
3     char buf[20];  
4     int pos, x = 0;  
5     snprintf(buf, sizeof(buf), "%.100d%n", x, &pos);  
6     printf("position: %d/n", pos);  
7     return 0;  
8 }

而%n和%d,%x,%s的强烈的例外就是%n是会改换变量的值的,那也正是格式化字符串攻击的爆破点。

三、多少个实际上的例子

下边这一个事例至少可以X8陆的Redhat和arch Linux上边进行现身说法。

 1 #include <stdio.h>  
 2 #include <stdlib.h>  
 3 #include <string.h>  
 4   
 5 char daddr[16];  
 6   
 7 int main(int argc, char **argv)  
 8 {  
 9     char buf[100];  
10     int x;  
11     x = 1;  
12     memset(daddr,'/0',16);  
13     printf("before format string x is %d/%#x (@ %p)/n", x, x, &x);  
14     strncpy(daddr,"PPPPPPP%n",9);         
15     snprintf(buf,sizeof(buf),daddr);   //实施格式化字符串攻击  
16   
17     buf[sizeof(buf) - 1] = 0;  
18     printf("after format string x is %d/%#x (@ %p)/n", x, x, &x);  
19     return 0;  
20 }

运作的结果是:x被成功的改观了柒。

地点的事例利用了linux函数调用时的内部存款和储蓄器残像,来达成格式化字符串攻击的。(参考的优异文章是用猜地址的法子来贯彻的,猜的二头雾水)

那里我们来分析一下main函数中的旅社变化境况:

图片 1

如上海教室所示,在调用snprintf函数在此以前,首先调用了printf函数,printf的函数第多少个参数是&x,那样在main函数的货仓内部存款和储蓄器中留下了&x的内部存款和储蓄器残像。当调用snprintf时,系统本来只给snprintf打算了3个参数,但是由于格式化字符串攻击,使得snprinf以为应当有八个参数字传送给它,那样snprintf就私下把&x的内部存款和储蓄器残像作为第5个参数读走了,而snprintf所谓的第四个参数对应的“%n”,于是snprintf就成功的修改了变量x的值。

而在事实上互联网遭受中可使用的格式化字符串攻击也是大多的。下图正是3个实际网络攻击的截图。

图片 2

 

 

文.RP.URL:http://blog.csdn.net/immcss/article/details/6267849

相关文章