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

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

一、类printf函数簇实现原理

类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型。

对于这种情况,可以使用省略号指定参数表。

带有省略号的函数定义中,参数表分为两部分,前半部分是确定个数、确定类型的参数,第二部分就是省略号,代表数目和类型都不确定的参数表,省略号参数表中参数的个数和参数的类型是事先的约定计算出来的,每个实参的地址(指针)是根据确定参数表中最后一个实参的地址算出来的。

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

时函数实参是从最后一个参数(最右边的参数)到第一个参数(最左边的参数)依次被压入栈顶方向。也就是说函数调用时,函数实参的地址是相连的,并且从左到右地址是依次增加的。如:

 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, ...)函数约定第一个确定参数表示省略号参数表中参数的个数,省略号参数表中的参数全都是int 类型的,这样fun函数就可以正常工作了。

类printf函数簇的工作原理和fun函数是一样的,只不过更为复杂和精巧。

如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函数中也有类似的约定,确定参数表中最后一个参数是一个const char* 类型的字符串,在这个字符串中出现“%d”和“%x”次数的和就是省略号参数表中参数的个数,省略号参数表中的参数类型也都是int类型。

同样的,实际的printf函数也有这样的约定:确定参数表中最后一个参数是一个const char* 类型的字符串,省略号参数表中参数个数就是这个字符串中出现的“%d”,“%x”,“%s”…次数的和,省略号参数表中参数的类型也是由“%d”,“%x”,“%s”……等格式化字符来指示的。

因此,类printf函数中省略号参数表中参数的个数和类型都是由类printf函数中的那个格式化字符串来决定的。

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

因为类printf函数中省略号参数表中参数的个数和类型都是由类printf函数中的那个格式化字符串来决定的,所以攻击者可以利用编程者的疏忽或漏洞,巧妙构造格式化字符串,达到攻击目的。

如果一个程序员的任务是:打印输出一个字符串或者把这个串拷贝到某缓冲区内。他可以写出如下的代码:printf("%s", str);但是为了节约时间和提高效率,并在源码中少输入6个字节,他会这样写: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域之前输出的字符个数会保存到下一个参数里。例如,为了获取在两个格式化的数字之间空间的偏量:

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格式返回应该被输出的字符数目,而不是实际输出的字符数目。当把一个字符串格式化输出到一个定长缓冲区内时,输出字符串可能被截短。不考虑截短的影响,%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是会改变变量的值的,这也就是格式化字符串攻击的爆破点。

三、一个实际的例子

下面这个例子至少可以X86的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被成功的改成了7。

上面的例子利用了linux函数调用时的内存残像,来实现格式化字符串攻击的。(参考的经典文章是用猜地址的方法来实现的,猜的一头雾水)

这里我们来分析一下main函数中的堆栈变化情况:

如上图所示,在调用snprintf函数之前,首先调用了printf函数,printf的函数第四个参数是&x,这样在main函数的堆栈内存中留下了&x的内存残像。当调用snprintf时,系统本来只给snprintf准备了3个参数,但是由于格式化字符串攻击,使得snprinf认为应该有四个参数传给它,这样snprintf就私自把&x的内存残像作为第4个参数读走了,而snprintf所谓的第4个参数对应的“%n”,于是snprintf就成功的修改了变量x的值。

而在实际网络环境中可利用的格式化字符串攻击也是很多的。下图就是一个实际网络攻击的截图。

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

时间: 2024-12-13 05:53:30

格式化字符串攻击原理及示例.RP的相关文章

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

一.类printf函数簇实现原理 类printf函数的最大的特点就是,在函数定义的时候无法知道函数实参的数目和类型. 对于这种情况,可以使用省略号指定参数表. 带有省略号的函数定义中,参数表分为两部分,前半部分是确定个数.确定类型的参数,第二部分就是省略号,代表数目和类型都不确定的参数表,省略号参数表中参数的个数和参数的类型是事先的约定计算出来的,每个实参的地址(指针)是根据确定参数表中最后一个实参的地址算出来的. 这里涉及到函数调用时的栈操作.函数栈的栈底是高地址,栈顶是底地址.在函数调用 时

格式化字符串攻击

什么是格式化字符串攻击? format string attack https://www.owasp.org/index.php/Format_string_attack 首先攻击发生在 格式化字符串所涉及的函数(例如 printf), 其次用户输入字符串提交后作为格式化字符串参数执行. 攻击者可以执行代码, 读取栈空间. 写栈空间. 导致进行段错误(segment fault),或者导致其他的威胁到计算机安全和稳定性的新的行为. 举例子 正确例子,其中 "Bob %%x %%x"

格式化字符串溢出实验

sh视频链接:  课程编写 类别 内容 实验课题名称 格式化字符串溢出实验 实验目的与要求 了解格式化字符串溢出的基本概念 掌握格式化字符串溢出的原理和性质 熟悉格式化字符串溢出攻击的方法 简单掌握格式化字符串溢出的防范方法 实验环境 VPC1(虚拟PC) Windows XP操作系统 软件描述 实验代码 预备知识 格式化字符串的漏洞是由像这样的代码 printf(user_input)引起的,其中 user_input 是用户输入的数据,具有 Set-UID root 权限这类程序在运行的时候

148.CSRF攻击原理分析、防御、装饰器、中间件、IFrame以及js实现csrf攻击

CSRF攻击概述: CSRF(Cross Site Request Forgery 跨站域请求伪造)是一种网站攻击的方式,它在2007年曾被列为互联网20大安全隐患之一.其他的安全隐患,比如SQL脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御.然而,对于大多数人来说,CSRF还是很陌生的,Gmail在2007年底也存在csrf漏洞,从而被黑客攻击而使Gmail的用户造成巨大的损失. CSRF攻击原理: 网站是通过cookie来实现登录功能的.而cookie只

编写高质量代码改善C#程序的157个建议——建议13: 为类型输出格式化字符串

建议13: 为类型输出格式化字符串 有两种方法可以为类型提供格式化的字符串输出.一种是意识到类型会产生格式化字符串输出,于是让类型继承接口IFormattable.这对类型来 说,是一种主动实现的方式,要求开发者可以预见类型在格式化方面的要求.更多的时候,类型的使用者需为类型自定义格式化器,这就是第二种方法,也是最灵活 多变的方法,可以根据需求的变化为类型提供多个格式化器.下面就来详细介绍这两种方法. 最简单的字符串输出是为类型重写ToString方法,如果没有为类型重写该方法,默认会调用Obj

【python】format函数格式化字符串的用法

来源:http://www.jb51.net/article/63672.htm 自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓威力十足.那么,他跟之前的%型格式化字符串相比,有什么优越的存在呢?让我们来揭开它羞答答的面纱.语法 它通过{}和:来代替%.“映射”示例 通过位置 ? 1 2 3 4 5 6 In [1]: '{0},{1}'.format('kzc',18) Out[1]: 'kzc,18' In [2]: '{},{}'.format('kz

冒泡算法,递归,正则表达式,模块,格式化字符串的整理

一.冒泡排序原理整理图 二.递归流程图剖析 三.正则表达式内容整理 1.re模块:python中re模块提供了正则表达式相关操作 2.元字符的整理 .  ^  $  *  +  ?  {}  []  |  \ 3.   .   匹配除换行符以外的任意字符(可以通过加参数re.S匹配上换行符) \w 匹配字母或数字或下划线或汉字 \s  匹配任意的空白符 \d  匹配数字 \b  匹配单词的开始或结束 ^   匹配字符串的开始 $  匹配字符串的结束 4.   * 重复零次或更多次(常用的) +

详谈Format String(格式化字符串)漏洞

格式化字符串漏洞由于目前编译器的默认禁止敏感格式控制符,而且容易通过代码审计中发现,所以此类漏洞极少出现,一直没有笔者本人的引起重视.最近捣鼓pwn题,遇上了不少,决定好好总结了一下. 格式化字符串漏洞最早被Tymm Twillman在1999年发现,当时并未引起重视.在tf8的一份针对wu-ftpd格式化字符串漏洞实现任意代码执行的漏洞的报告之后(详情可参阅 <黑客攻防技术宝典-系统实战篇>),才让人们意识到它的危害,至此而发现了大量的相关漏洞. 格式化字符串漏洞的产生根源主要源于对用 户输

.NET中DateTime.Now.ToString的格式化字符串

.NET中DateTime.Now.ToString显示毫秒:DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") DateTime.Now.ToString的其他格式化字符串:   格式化字符串 释义 示例 D long date Thursday, 10 April 2008 d short date 04/10/2008 F full date long Thursday, 10 April 2008 06:30:00 f full d