printf函数的漏洞(转载)
创建时间:2001-03-03
文章属性:转载
文章来源:作者:isno (isno@sina.com)
文章提交:xundi (xundi_at_xfocus.org)
浅析格式化串漏洞
作者:isno (isno@sina.com)
-----------------目录-------------------
一.前言
二.基础知识简介
三.格式化串漏洞原理
(1)参数个数不固定造成访问越界数据
(2)利用%n格式符写入跳转地址
(3)利用附加格式符控制跳转地址的值
(4)总结
四.对wu-ftp 6.0格式化串漏洞的分析
(1)问题出现在哪里
(2)wu-ftp漏洞的利用
五.后记
----------------------------------------
一.前言
最近许多软件被发现存在格式化串漏洞,其中最著名的是wu-ftp6.0和rpc.statd,由于
相当多的网站缺省安装了这两种软件,而且网上针对这两个漏洞的攻击程序很多很好用,所
以由这两个漏洞而被攻破的网站非常之多。因此非常有必要认真研究一下格式化串漏洞,但
网上介绍格式化串漏洞的中文文章却特别少,就我知道的只有一篇warning3写的和另一篇
xuzq翻译的文章,我又参考了几篇英文文章,费了半天工夫看的头疼了才搞明白这种漏洞的
机理。
由于那几篇文章写的较为深奥,像我这样的普通初学者看起来很费劲,我想就我的理解
写一篇浅显一点的文章,使其他像我一样的菜鸟免受头疼之苦,同时也把这篇文章作为备忘
材料,等我以后忘了再回过头来看看:-)由于本人水平有限,谬误之处再所难免,欢迎多多
指教。
二.基础知识简介
在了解格式化串漏洞之前有必要复习一下关于堆栈的基础知识,网上介绍缓冲区溢出的
文章很多,其中大多都介绍了堆栈的知识,读者可以自行参考那些文章,我在这里只是简单
的介绍一下。
一个程序的动态数据通过一块叫做堆栈的区域来存放。堆栈处于内存的高端,它有个特
性:后进先出。当程序中调用子函数时,计算机首先把参数依次压入堆栈,然后把指令寄存
器(EIP)中的内容做为返回地址(RET)压入堆栈,第三个压入堆栈的是基址寄存器(EBP),然后
把当前的栈顶指针(ESP)拷贝到EBP,做为新的基地址。最后把ESP减去一定的数值,用来为本
地变量留出一定空间。
普通的缓冲区溢出就是利用了堆栈生长方向和数据存储方向相反的特点,用后存入的数
据覆盖先前压栈的数据,一般是覆盖返回地址,从而改变程序的流程,这样子函数返回时就跳到了
黑客指定的地址,就可以按照黑客意愿做任何事情了。
格式化串漏洞和普通的缓冲溢出有相似之处,但又有所不同,它们都是利用了程序员的
疏忽大意来改变程序运行的正常流程。下面详细介绍格式化串漏洞的原理,最后对wu-ftp6.0
格式化串漏洞进行一下分析。
三.格式化串漏洞原理
所谓格式化串,就是在*printf()系列函数中按照一定的格式对数据进行输出,可以输出
到标准输出,即printf(),也可以输出到文件句柄,字符串等,对应的函数有fprintf,sprintf,
snprintf,vprintf,vfprintf,vsprintf,vsnprintf等。能被黑客利用的地方也就出在这一系列
的*printf()函数中,可能有人会问:这些函数只是把数据输出了,怎么能造成安全隐患呢?
在正常情况下当然不会造成什么问题,但是*printf()系列函数有三条特殊的性质,这些特殊
性质如果被黑客结合起来利用,就会形成漏洞。
(注:以下测试环境为RedHat Linux 6.0)
#可以被黑客利用的*printf()系列函数的三个特性:
(1)参数个数不固定造成访问越界数据
首先第一个可以被利用的性质是:*printf()系列函数的参数的个数是不固定的。拿printf()
函数举例来说,如果我们要依次输出3个整型数据和1个字符串,可以用以下程序:
#include
int main(void)
{
int i=1,j=2,k=3;
char buf[]="test";
printf("%s %d %d %d\n",buf,i,j,k);
return 0;
}
这是正常的使用方法,程序会输出:
test 1 2 3
这个printf()函数共有5个参数,第一个是格式化串"%s %d %d %d\n",第二个是字符串buf的
地址,%s对应buf,其后的三个%d分别对应i,j,k,这样就把数据输出了。但是如果我们减少printf()
函数的参数个数,写成这样:
printf("%s %d %d %d\n",buf,i,j);
格式化输出符号仍然是4个,但对应的数据却只剩下3个了(buf,i,j)了,那么情况会怎样呢?
我们编译运行一下看看,这个程序输出:
test 1 2 1953719668
我们可以清楚的看到,尽管没有给最后一个%d提供对应的数据,但是它还是输出了一个10位的
整数1953719668,这个大整数到底是什么呢?我们再修改源程序,把输出的语句改为:
printf("%s %d %d %x\n",buf,i,j);
即按照16进制输出最后一个参数,这时输出的结果就是:
test 1 2 74736574
也就是说,当没有给printf()函数的格式化串提供足够的对应参数时,printf()并没有报错,而
是把内存中某个4字节的内容打印了出来,这四个字节的内容是74736574。
那么74736574究竟是什么玩意呢?如果你对ASCII码熟悉的话应该可以反映过来,字符串在内存
当中是以ASCII码的形式存储的,它们有如下对应关系:
十六进制 十进制 字符
74 ----------> 116 ---------> t
73 ----------> 115 ---------> s
65 ----------> 101 ---------> e
74736574对应的字符串恰好是tset,由于字符串在内存当中是以反序排列的,74736574对应的
实际字符串应该是:test。是不是看起来有点眼熟?翻回前面再看看那个程序,对了,就是我们在程序
中定义的字符串buf[]的内容。这决不是偶然的,回忆一下前面说过的堆栈的工作流程,我们可以想象
到这个程序在堆栈中的情况:
i) 调用main()函数之前首先把返回地址压栈;
ii) 然后压入的是EBP,并把ESP拷贝到EBP;
iii)把ESP减去一定的数量,也就是把堆栈扩大,给变量i,j,k,buf留出空间;
iv) 开始调用printf(),把printf()的4个参数j,i,buf和格式串"%s %d %d %x\n"依次压入堆栈;
v) 压入printf()的返回地址;
vi) 压入此时的EBP;
vii)开始执行printf()。
这时候的堆栈看起来应该是这个样子的:
栈顶 栈底
--------------------------------------------------------------------------
| EBP | EIP | 格式串| buf地址| i | j |buf内容| \0 | k | j | i | EBP | EIP|
--------------------------------------------------------------------------
看到堆栈的实际内容,就不难理解为什么会打印出74736574即"test"了,printf()首先找到第
一个参数格式串"%s %d %d %x\n",然后就开始按照对应关系依次打印前面堆栈中内容,%s对应
buf地址,也就打印出了buf[]的内容,第一个%d对应i,第二个%d对应j,%x本来是应该对应k的,可是
由于我们提供给printf()的参数中没有k,而j前面正好是buf内容,所以就把buf的内容作为16进制
数输出了,也就是我们看到的74736574。可以预测,如果提供给printf()的格式串中再多几个%x的话,
printf()还会继续打印前面堆栈里的"\0"(buf的结束符),k,j,i,EBP,EIP等内容。
说到这里,已经把产生格式化串漏洞的根源揭露出来了:因为*printf()系列函数的参数的个数
是不固定的,如果其第一个参数即格式串是由用户来提供的话,那么用户就可以访问到格式串前面的
堆栈里的任何内容了。
之所以会出现格式化串漏洞,就是因为程序员把printf()的第一个参数即格式串,交给用户来
提供,如果用户提供特定数量的%x(或%d,%f,随你的便啦),就可以访问到特定地址的堆栈内容。
有些人会说:"靠!你费了这么半天劲,就只是为了打印出了前边堆栈里的内容啊?"我们当然不
只是为了看看堆栈里的内容,我们是要改变堆栈的内容,改变返回地址,使程序跳去执行我们提供的
代码,这就需要联系上*printf()系列函数的第二个特殊的性质。
(2)利用%n格式符写入跳转地址
到目前为止我们都只是显示内存的内容而没有改变它,但是利用*printf()的一个特殊的格式符
%n,我们就向内存中写入内容。
%n是一个在编程中不经常用到的格式符,它的作用是把前面已经打印的长度写入某个内存地址,
为了搞清其具体用法和性质,我们看一看下面的例程:
#include
int main(void)
{
int num;
int i=1,j=2,k=3;
printf("%d%d%d%n\n",i,j,k,&num);
printf("%d\n",num);
return 0;
}
运行显示:
123
3
可以看出,%n的作用就是把已经打印出来字符的数量保存到对应的内存地址当中,这里是num当中。
注意,这里必须对应一个内存地址,%n把字符数写入到这个地址的内存。如果把上述语句改成:
printf("%d%d%d%n\n",i,j,k,num);
这样就会出现段访问错误。强调这一点很重要,因为在实际利用某个漏洞时,并不是直接把跳转
地址写入函数的返回地址单元,而是写入一个存放着函数的返回地址的地址当中,即经常说的retloc,
这个存放函数的返回地址的地址通常在我们提供的字符串的前面,这么说可能有点绕,换种说法,就是
说我们并不直接覆盖返回地址,而是通过地址来间接的改写返回地址,这一点经常有人混淆,如果你还没
理解的话,可以仔细体会一下C语言中指针的用法,它们之间有相似之处。
好的,到目前为止我们已经知道可以利用提交格式串来访问格式串前面堆栈里的内容,并且利用
%n可以向一个内存单元中的地址去写入一个值,既然我们可以访问到我们提交的串,就可以在我们提
交的串当中放上某个函数的返回地址的地址,这样就可以利用%n来改写这个返回地址。
但是但是,%n向内存中写入的值并不是随意的,它只能写入前面打印过的字符数量,而我们需要的
是写入我们存放shellcode的地址,就象普通溢出做的那样。这个问题实在是麻烦,可能有人会想:那
就用和跳转地址的数值相同多的数量的%d放在%n的前面不就行了?这样做理论上可行,但实际却不行,
因为堆栈在内存的高端,堆栈里面的内存地址也是一个相当大的数,如果我们用一个%d来对应4字节内容
即一个整型的话,首先数量太多就是一个问题,而且每4个字节的内存单元作为整数打印出来的话,它的
实际长度也是没法确定的,有的可能打印出一位的'1',有的则可能打印出5位的'45367',这是我们没法
预料的。
这时就需要利用到*printf()系列函数的第三个"良好"的性质了。
(3)利用附加格式符控制跳转地址的值
*printf()系列函数有个性质是:程序员可以定义打印字符的宽度。学过C语言的人肯定都知道这一点,
就是在格式符的中间加上一个整数,*printf()就会把这个数值作为输出宽度,如果输出的实际大于指定宽
度仍按实际宽度输出,如果小于指定宽度,则按指定宽度输出。例如我们可以用下面语句以100个字符的宽
度输出整数i:
printf("%100d",i);
而用printf("%.100f",i)的形式则是以100位的小数输出i。而printf("%.f",i)不是以1位小数输出,而是
以总共8位的带小数的数输出i。如果i等于1的话,输出应该是1.000000。由于这个"%.f"一次能向前推进8位,所
以它经常在实际攻击时被放在提交的格式串的中间,用来快速地到达返回地址处。
我们就可以利用这个特性来用很少的格式符来输出一个很大的数值到%n,而且这个数值是可以由我们
来指定的。我们所要做的就是作一些计算,把要返回的地址转化为整数,放到%n前面的格式串中。例如我们要
把200放入num中,则可以利用如下语句:
printf("%.200d%n",i,&num);
当这条语句执行完后,num的值就变成200了。如果用跳转地址的值代替200,那么%n就可以把跳转地址写入
num了。
(4)总结
好了,到这里已经把格式化串漏洞的所有理论依据都介绍完了。让我们再来回顾一下:
首先,如果程序中的*printf()系列函数中的格式串参数是由用户来提供的话,我们就可以提交给它一串
%d(或%f,%u等)来访问堆栈中格式串前面的任意内存单元。
在我们提交的格式串的后面加上一个%n格式符,我们就可以向堆栈中格式串前面的某个内存单元中写入
已经打印的字符数量。在实际攻击时通常是在提交的格式串前面放上存放着某个函数的返回地址的地址,然后
让%n恰好对应着这个地址值,这样写入到数值,就存放到函数返回地址当中了。
我们通过附加格式符来控制向函数返回地址中写入的值,一般是利用%n前面的最后一个格式符来控制
这个数值,这通常需要一些计算,计算方法一般是用shellcode的地址减去最后一个格式串前的所有格式串的
打印长度。这样%n写入的数值就恰好是shellcode的地址了。
理论上讲的只是理想情况,在实际攻击某个程序时往往会出现更多的问题。首先要解决的是必须使%n正好
对应存放函数返回地址的地址,否则无法改变返回地址的值。这一点对于一些可重复启动且能够返回格式串的
程序来说是比较容易解决的,我们可以先不放入跳转地址,而是在提交的格式串前填上几个特殊字符,例如"abcd",
然后在提交的格式串最后用%x代替%n来显示这个特殊字符串,我们要做的只是不断增加格式串中间的格式符的个
数,直到程序返回的值恰好是我们提交的特殊字符的ASCII码为止。这样我们就知道用来存放函数返回地址的地
址在什么位置了,然后我们再把真正的存放函数返回地址的地址放在格式串的前面,用%n作为格式串的结尾,这
样就可以把跳转地址正确的写进函数返回地址了。
当然这种方法需要被攻击的程序可以不断的启动而且能够返回提交的格式串的打印内容,wu-ftp6.0就是
这样的程序,所以针对wu-ftp6.0的格式串漏洞的攻击比较容易成功的。而有些程序则不行,例如cfengine,这
个程序被发现存在格式串漏洞已经很久了,但是一直没有可成功的攻击程序发布,主要就是因为一但向cfengine
发送格式串它就当掉了,所以无法多次猜测。对这种程序必须一次攻击就必须猜中存放函数返回地址的地址和
跳转地址,所以在现实的攻击中往往是不容易成功的。
另外一个难以解决的问题是,需要把精确的函数返回地址填在格式串开头,这样%n才能把跳转地址写进
正确的位置了。这个函数的返回地址一般是在写攻击程序时测试得到的,这在测试的机器上固然可以成功,但是
在不同机器上这个返回地址的值是根据环境变量和编译选项的不同而不同的,例如用rpm安装的wu-ftp和从源
代码编译安装的wu-ftp,它们的返回地址往往就是不同的,需要根据被攻击主机的实际情况进行调整。这只有根
据经验来确定,或者干脆用暴力法了猜测返回地址,但根据实际测试,即使用暴力法猜测返回地址,成功的概率
也是不大的。
还有就是跳转地址的问题,即shellcode的地址。因为我们要改写函数的返回地址,使它跳转去执行shellcode。
所以必须要知道shellcode的地址。这个问题相对容易解决,我们可以像普通溢出做的那样,在shellcode前面
填上一串NOP,这样只需要知道一个大概的地址范围就可以了,只要跳转到NOP的范围当中就可以执行shellcode
了。所以一般的攻击程序里的跳转地址往往是不需要调整的,因为只要在一个大概的地址范围当中就可以了。
好了,理论就讨论这么多,下面我们来看看Wu-ftp6.0的格式化串漏洞和利用的方法。
四.对wu-ftp 6.0格式化串漏洞的分析
(1)问题出现在哪里
wu-ftp(Washington University ftp server)是一个非常流行的unix/linux系统ftp服务器,它的6.0版本
存在格式化串漏洞。由于在大多数Linux系统中它是默认安装的,所以相当多的网站都受这个漏洞的影响,针对它
的攻击也是非常普遍的。
下面我们看看wu-ftp的源代码,究竟是哪里出现了可以被黑客利用的漏洞?
用户提交的"site exec"命令是由一个名为 void site_exec(char *cmd) 的函数来处理的,其中cmd是用户
提交的命令。在这个函数中有这么一个语句:
--------------ftpcmd.y文件 第1929行----------------------
lreply(200, cmd);
-----------------------cut here--------------------------
site_exec()函数把用户提交的命令交给lreply()函数来处理了,我们再看看lreply()函数的定义:
--------------ftpd.c文件 第5343行------------------------
void lreply(int n, char *fmt,...)
{
VA_LOCAL_DECL
if (!dolreplies)
return;
VA_START(fmt);
/* send the reply */
vreply(USE_REPLY_LONG, n, fmt, ap);
VA_END;
}
-----------------------cut here--------------------------
显然lreply()的第二个参数char *fmt应该是格式串,而在前面的调用中却把它交由用户命令来
提供,这就是造成问题的地方。继而lreply()有把fmt交给vreply()函数来处理,我们再来看看vreply()
的定义:
--------------ftpd.c文件 第5275行------------------------
void vreply(long flags, int n, char *fmt, va_list ap)
{
char buf[BUFSIZ];
flags &= USE_REPLY_NOTFMT | USE_REPLY_LONG;
if (n)
sprintf(buf, "%03d%c", n, flags & USE_REPLY_LONG ? '-' : ' ');
/* This is somewhat of a kludge for autospout. I personally think that
* autospout should be done differently, but that's not my department. -Kev
*/
if (flags & USE_REPLY_NOTFMT)
snprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), "%s", fmt);
else
vsnprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), fmt, ap);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!!注意这一句!!!
if (debug)
syslog(LOG_DEBUG, "<--- %s", buf);
/* Yes, you want the debugging output before the client output; wrapping
* stuff goes here, you see, and you want to log the cleartext and send
* the wrapped text to the client.
*/
printf("%s\r\n", buf); /* and send it to the client */
#ifdef TRANSFER_COUNT
byte_count_total += strlen(buf);
byte_count_out += strlen(buf);
#endif
fflush(stdout);
}
-----------------------cut here-----------------------------------------------
由于提交给vreply()的第一个参数(即flags)是USE_REPLY_LONG,所以经过&=操作
之后flags仍然为USE_REPLY_LONG。这样(flags & USE_REPLY_NOTFMT)的值就为0。所以下面的
判断语句会进入else执行:
if (flags & USE_REPLY_NOTFMT)
snprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), "%s", fmt);
else
vsnprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), fmt, ap);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
!!!注意这一句!!!
注意看要执行的vsnprintf()函数,它把fmt放在了格式串的参数位置上了。而这个fmt
正是由用户提交的命令cmd。回忆一下前面讲过的格式化串漏洞原理:如果*printf()系列
函数的格式串参数是由我们来提交的话,那么我们就可以用一串格式符来访问格式串前面
堆栈里的内容了,如果我们能访问到自己提交的内容,就可以在这内容的前面放上存放某个
函数返回地址的地址,然后就可以用%n来改写这个返回地址,使它跳转去执行我们提供
的shellcode。
(2)wu-ftp漏洞的利用
所以我们要想利用这个漏洞,需要先登陆上FTP服务器(编程实现,不用我们自己去登陆了),
用匿名用户(ftp,anonymous)登陆就可以,然后提交一个如下形式的site exec命令:
site| exe|c aa|retloc|%.f...%.f|%.(ret)d|%n
注意:其中的'|'符号是为了使读者看清楚字符串结构而加上的分隔符,实际提交的串里没有'|'。
这个由我们提交的格式串前面是site exec命令,后跟的"aa"的作用是为了使retloc以4字节
为单位对齐,就是我们通常所说的align。
然后紧跟着的retloc就是我们要写入的存放函数返回地址的地址,呆会儿我们要用%n来对应
它,就可以把跳转地址写入函数返回地址了。一般我们要改写的函数应该是最近的一个函数,这里
可以改写vreply()的返回地址,使它返回时跳去执行我们的shellcode。
在retloc后面放上一串%.f,前面说过%.f一次显示8位数字,在这里它的作用是显示在我们提
交的命令串之后压入堆栈的局部变量,使%n能够正好对应retloc。
然后紧跟着的%.(ret)d的作用是把打印的字符数量调整为正好是shellcode地址的值,使得%n
能够把已打印的字符数量,即shellcode的地址正好写进vreply()函数的返回地址当中。注意,这里
的ret并不是shellcode的地址,而应该是[shellcode地址-(%.f个数*8)-16]。其中的16是前面的
"site| exe|c aa|retloc|"的字符数。这样在%n之前打印的总符数就应该恰好为shellcode的地址
了。
最后的%n的作用当然就是为了把跳转地址(shellcode地址)写入vreply()的返回地址,使它返
回时跳转去执行shellcode。正如在讲格式化串漏洞原理时提到过的那样:%n并不是直接对应着返回
地址进行修改,而是对应着存放返回地址的地址,间接的修改了返回地址。因此虽然vreply()的返回
地址是比格式串后压入堆栈的,但是我们仍然可以改写它的内容。
还有重要的一点忘记说了。shellcode是什么时候提交的呢?我们可以把shellcode放进用户的密
码里提交给wu-ftpd,由于匿名用户的密码是可以随便指定的,所以这样做并不影响我们使用匿名登
陆进服务器。此时shellcode应该作为全局变量被存放在程序的Heap/BSS段里,而不是存放在堆栈里。
在本地机器上调试一下可以知道shellcode的大致地址为0x80756xx,这是在RedHat 6.0上的值,在其
它系统中会有所不同。由于我们可以在shellcode之前放上一堆NOP,所以也没必要知道shellcode的
精确位置,只要让程序跳转到NOP范围里就行了。
需要说明的是,这里的shellcode必须是带有突破chroot()功能的,因为如果用匿名用户登陆的
话,只能访问被chroot()保护的目录,即匿名用户登陆后的目录,这样就没法绑定/bin/sh了。所以要
在shellcode中先chroot()到根目录。网上有很多写得非常好的chroot shellcode,直接拿来用就可
以了。
以上就是攻击wu-ftp6.0格式化串漏洞的方法,现在流传着很多写得非常好的wu-ftp6.0攻击程
序,我本来想找其中一个来逐条语句解释出来的,但解释了几句之后发现太TMD麻烦了。而且也没有必
要解释攻击程序了,因为我已经把攻击这个漏洞的步骤都在前面解释过了。
这里只把我用过的几个比较好的攻击程序介绍给大家:
(1)攻击以rpm安装wuftp6.0的RedHat 6.0, 6.1, 6.2比较有效的程序:
http://go6.163.com/~antiroot/exploit/wu-lnx.c
(2)攻击安装wuftp6.0的FreeBSD和SuSe 6.3, 6.4比较有效的程序:
http://go6.163.com/~antiroot/exploit/wuftpd-god.c
(3)攻击安装wuftp6.0的solaris 2.x比较有效的程序:
http://go6.163.com/~antiroot/exploit/ftpd.c
根据我的经验,以rpm方式安装wuftp6.0的RedHat 6.0, 6.1, 6.2是最容易攻击成功的了,可能是
因为大多数攻击程序都是在这种系统环境下调试编写的。而用源代码编译安装的wuftp不容易攻击成功,
需要攻击者调整攻击程序中的某些参数,主要要修改的是retloc,也就是存放函数返回地址的地址,有
时候需要反复调整这个值才能攻击成功。
五.后记
本文的目的是把格式化串漏洞尽量用通俗易懂的语言解释出来,所以我在解释原理时没有用gdb等
工具的调试结果来讲,而是尽量地只讲原理本身,这样对于某些不熟悉调试器的读者来说可能比较容易
理解。也许有人还是会觉得本文写得不是很易懂,因为格式化串漏洞本身就是个比较复杂的东西,起码
需要读者了解一些关于C语言以及堆栈溢出的基本知识,这样才有助于理解本文。
也许有些人会说:"为什么非要搞清楚这个漏洞的技术细节呢?我不懂细节直接用攻击程序攻击一下
也能成功。"没错,在实际攻击的时候我们一般都是不怎么考虑细节问题的,但是但是但是,你的目标是成
为一个Hacker呢?还是一个Scriptkid?如果你的目标是前者,那毫无疑问你需要了解技术细节。如果是
后者,你同样也需要了解技术细节!!因为了解的细节越多,你攻击的成功率就越大了。
感谢warning3等nsfocus的同志,他们写的文章使我受益非浅。
参考文献:
<<*printf()格式化串安全漏洞分析>>---warning3
<<格式化字符串攻击>>---Tim Newsham(xuzq译)
<< Format Bugs:What are they,Where did they come from,...How to exploit them >>---lamagra
欢迎访问我的主页:http://isno.yeah.net
(*转载请保持文章完整*)