2022年格式化字符串攻击设计实现 .pdf
西南科技大学本科生毕业论文格式化字符串攻击设计实现摘要:格式化字符串漏洞对计算机系统和网络有很大的威胁,攻击者可以利用格式化字符串漏洞实现远程攻击,比如使程序崩溃,偷窥堆栈内容,甚至还可以修改程序返回地址为恶意代码,从而得到系统控制权。本文首先通过分析在格式化字符串漏洞中常用的格式化字符和格式化参数来详细的说明格式化字符串漏洞形成的原因,然后根据不同的格式化字符串漏洞,编写相应的调试程序,然后针对程序进行模拟攻击,并分析攻击结果。本文主要在模拟环境下实现了使程序崩溃、偷窥堆栈内容和修改程序返回地址的攻击。其次本文还结合现实中的格式化字符串漏洞进行解析,阐述如何利用这些漏洞进行攻击。最后本文还介绍了格式化字符串漏洞的防范工具,包括静态分析软件和动态保护工具。关键词:格式化字符串漏洞;形成原因;攻击;防范工具名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 28 页 -西南科技大学本科生毕业论文Design and Implementation of a format string attack Abstract:Format string vulnerability is a vital threat to computer systems and networks,an attacker could exploit format string vulnerabilities for remote attack,such as cause program crashes,peeping stack content,even modify the return address of program to malicious code to get control of the system.Firstly,this paper through the analysis of the common format characters and formatting parameters in the format string vulnerability to detailed description of the reasons for the formation of the format string vulnerability.Then according to the different format string vulnerabilities,preparation the appropriate debugger,then simulated attacks against the program and analyze the result of the attack.In this paper,in a simulated environment to make the program crashes,peeping stack content and modify the program to return address.Furthermore,this article combined with format string vulnerabilities in the reality to parse,explains how to exploit these vulnerabilities to attack.The last,this paper also describes the prevention tool of the format string vulnerability,include static analysis software and dynamic protection tools.Keyword:Format string vulnerability,Formation,Attack,Prevention tools 名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 28 页 -西南科技大学本科生毕业论文目录第 1 章 绪论.1 1.1 课题背景 .1 1.2 课题研究意义 .1 第 2 章 格式化字符串漏洞形成原理.2 2.1 格式化字符串漏洞基本原理.2 2.2 格式化函数 .2 2.3 printf 函数的一般格式 .2 2.4 格式化规定符 .4 2.5 堆栈.5 2.6 本章小结 .5 第 3 章 格式化字符串攻击方法及实现.6 3.1 使程序崩溃 .6 3.2 偷窥堆栈内容 .7 3.3 修改程序返回地址 .10 3.4 地址偏移量的确定 .16 3.5 格式化字符串漏洞实例 .17 3.5.1 漏洞描述 .17 3.5.2 漏洞利用 .19 3.6 本章小结 .20 第 4 章 防范工具 .21 4.1 静态分析软件 .21 4.2 动态保护工具 .22 结论.23 致谢.24 参考文献 .25 名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 28 页 -西南科技大学本科生毕业论文1 第 1 章绪论1.1 课题背景随着现代科技的不断发展,计算机技术的应用已经深入到社会的各个领域。它在给我们的生活带来了极大便利的同时,也潜伏着危险。1999年,Tymm Twillman 发现了第一个格式化字符串漏洞。2000年,tf8 在 BugTraq上发布的 wu-ftpd 中的格式化字符串漏洞使得该漏洞广为人知。之后,众多的格式化字符串漏洞被发现。虽然已经过去了很多年,但是新的格式化字符串漏洞仍在不断被发现。根据中国国家信息安全漏洞库网站上的信息来看,从2000 年 1 月到 2012 年 5月,总共有 50721 个漏洞被发现,其中只有 202 个格式化字符串漏洞,占总共漏洞的0.4%,所以格式化字符串漏洞其实并不常见。格式化函数是一系列的ANSIC 函数,它们可以接受可变数量的参数,其中一个称为格式化字符串参数(format string)。格式化函数对输入的格式化字符串参数进行解释,根据该参数的要求使用其它的输入参数,形成输出的字符串。格式化字符串函数几乎被用在所有的C 程序中,用来输出、打印信息或处理字符串。正因为这些函数使用的广泛性,他们的漏洞对系统的安全有重要影响。攻击者利用格式化字符串漏洞可以获得系统的控制权,这一点对于高可信软件和服务控件来说是致命的1。1.2 课题研究意义通过对该课题的研究,能让我掌握如何利用由于格式化函数的微妙程序设计错误造成的安全的漏洞,以及其丰富的格式化语言函数来输入经过精心编制的含有格式化指令的文本字符串来达到使程序崩溃,查看堆栈内容和修改程序返回地址的目的2。注入恶意格式化串可以导致系统程序读取非可读地址的数据,进而使程序发生段错误,使程序崩溃3;通过为 printf 函数提供多余自身要求的参数来使程序输出堆栈内本不应该输出的内容,从而达到偷窥堆栈内存的目的;%n 参数可以把已经打印的字节数量存入到一个我们指定的参数内,该参数的地址作为格式化字符串函数的参数存放在栈上,通过精心设计要输入的字节数目来达到修改栈内返回地址为攻击代码地址,从而达到控制程序的目的。名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 28 页 -西南科技大学本科生毕业论文2 第 2 章 格式化字符串漏洞形成原理2.1 格式化字符串漏洞基本原理格式化字符串漏洞其实是由于程序员的不规范操作造成的,在该指定输出格式的时候没有进行指定4。例如要将变量“x”以字符串的格式输出,规范的格式应该是这样:printf(%s,x),但是程序员可能因为懒惰就忽略了它的格式化字符串,直接写成:printf(x)。编译的时候是可以通过的。但就是这样的一个疏忽,如果这个变量“x”是由外部提供的话,就为这个程序留下了格式化字符串漏洞9。攻击者可以通过输入精心编制的含有格式化指令的文本字符串来达到使程序崩溃、偷窥程序内容甚至是取得程序控制权的目的。下面介绍一下格式化字符串攻击中常用的一些函数和参数。2.2 格式化函数C 语言中常用到的格式化输出函数,如表2-1 所示:表 2-1 格式化输出函数2.3 printf 函数的一般格式printf 函数的一般格式为printf(格式控制,输出列表)函数特性Fprintf 将格式化的数据打印至文件Printf 将格式化的数据打印至标注输出“stdout”Sprintf 将格式化的数据存储到缓存中Snprintf 将指定长度的格式化数据存储到缓存中Vfprintf 将 va_arg 结构中的格式化数据打印到文件Vprintf 将结构中的格式化数据打印到标准输出“stdout”Vsprintf 将结构中的格式化数据存储到缓存中Vsnprintf 将结构中的格式化数据存储到缓存中名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 28 页 -西南科技大学本科生毕业论文3 例如:printf(%d,%cn,i,c)括号内包含两部分:(1)“格式控制”是用双撇号括起来的一个字符串,称“转换控制字符串”,简称为“格式字符串”5。它包括两个信息:格式声明。格式声明由“%”和格式字符组成,如%d、%f 等。它的作用是将输出的数据转换为指定的格式然后输出。格式声明总是由“%”字符开始的。普通字符。普通字符即需要在输出是原样输出的字符。例如上面printf 函数中的双撇号内的逗号、空格和换行符,也可以包括其他字符。(2)“输出列表”是程序需要输出的一些数据,可以是常量、变量或表达式。下面是 printf 函数的具体例子:printf(%d%dn,a,b)printf(a=%db=%dn,a,b)在第二个 printf 函数中的双撇号内的字符,除了两个“%d”以外,还有非格式声明的普通字符(如 a=,b=和n),它们全部按原样输出。如果 a 和 b 的值分别为 3 和 4,则输出为:a=3 b=4 执行“n”使输出控制移到下一行开头,从显示屏幕上可以看到光标已移到下一行开头。上面输出结果中有下划线的字符是printf 函数中的“格式控制字符串”中的普通字符按原样输出的结果。3 和 4 是 a 和 b 的值(注意 3 和 4 这两个数字前和后都没有外加空格),其数字位数由a和 b 的值而定。加入a=12,b=123,则输出结果为:a=12 b=123 由于 printf 是函数,因此,“格式控制字符串”和“输出列表”实际上都是函数的参数。printf 函数的一般形式可以表示为:printf(参数 1,参数 2,参数 3,参数 n)参数 1 是格式控制字符串,参数 2参数 n 是需要输出的数据。执行 printf 函数时,将参数 2参数 n 按参数 1 所指定的格式进行输出。参数1 是必须有的,参数2参数n 是可选的。名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 28 页 -西南科技大学本科生毕业论文4 2.4 格式化规定符printf 格式字符及附加格式说明符6分别如表 2-1 和 2-2 所示:表 2-2 printf 格式字符表 2-3 printf 的附加格式说明符字符说明l用于长整型整数,可加在格式符d、o、x、u 前面m(代表一个正整数)数据最小宽度n(代表一个正整数)对实数,表示输出n 位小数;对字符串,表示截取的字符个数输出的数字或字符在域内向左靠【说明】(1)printf 函数输出时,务必注意输出对象的类型应与上述格式说明匹配,否则将会出现错误。出科 X,E,G 外,其他格式字符必须用小写字母,如%d 不能写成%D。格式字符说明d,i 以带符号的十进制形式输出整数(正数不输出符号)o 以八进制无符号形式输出整数(不输出前导符0)x,X 以十六进制无符号形式输出整数(不输出前导符0 x),用 x 则输出十六进制数的af 时以小写形式输出。用 X 时,则以大写字母输出u 以无符号十进制形式输出整数c 以字符形式输出,只输出一个字符s 输出字符串f 以小数形式输出单双精度数,隐含输出6 位小数e,E 以指数形式输出实数,用e 时指数以“e”表示(如1.2e+0.2),用 E 时指数以 E 表示如(1.2E+0.2)g,G 选用%f 或%e 格式中输出宽度较短的一种格式,不输出无意义的 0,用 G 时,若以指数形式输出,则指数以大写表示名师资料总结-精品资料欢迎下载-名师精心整理-第 7 页,共 28 页 -西南科技大学本科生毕业论文5(2)可以在 printf 函数中的“格式控制字符串”内包含转义字符,如“n”,“t”,“b”,“r”,“f”和“377”等。(3)表中所列出的字母d,o,x,u,c,s,f,e,g,X,E 和 G 等,如用在“格式声明”中就作为格式字符。一个格式声明以“%”开头,以上述 12 个格式字符之一为结束,中间可以插入附加格式字符(也称修饰符)。例如:Printf(c=%cf=%fs=%s,c,f,s);(4)如果想输出字符“%”,应该在“格式控制字符串”用连续两个“%”表示,如:Printf(%f%n,1,0/3);输出:0.333333%实现了输出“%”符号。2.5 堆栈一个程序的动态数据通过一块叫做堆栈的区域来存放。堆栈处于内存的高端,它具有后进先出的特性。当程序发生调用时,计算机做如此啊操作:首先把参数压入堆栈;然后保存指令寄存器(eip)中的内容作为返回地址(ret);再把基址寄存器(ebp)压入堆栈;随后将当前的栈指针(esp)拷贝到 ebp作为新的基地址;最后为本地变量留出一定空间,同时将 esp减去适当的数值7。普通的缓存区溢出就是利用了堆栈生长方向和数据存储方向相反的特点8,用后存入的数据覆盖先前压栈的数据。一般是覆盖返回地址,从而改变程序的流程,这样,子函数返回时就跳到了攻击者指定的地址,就可以按照攻击者的意愿做任何事情了。格式化字符串漏洞和普通的缓存区溢出有相似之处,但又有所不同,他们都是利用了程序员的疏忽大意来改变程序的正常流程的。2.6 本章小结本章首先详细的分析了格式化字符串漏洞形成的原理,然后又介绍了格式化漏洞攻击中常用到的格式化字符和格式化参数,其中重点介绍了 printf 函数的格式化字符。名师资料总结-精品资料欢迎下载-名师精心整理-第 8 页,共 28 页 -西南科技大学本科生毕业论文6 第 3 章 格式化字符串攻击方法及实现3.1 使程序崩溃使程序崩溃主要用到“%s”这个格式化规定字符,“%s”的作用是规定将变量为字符串的形式,但是不正常的使用会使程序读取不可读取地址里的内容,从而导致程序意外中断17。假设有如下一段代码3-1:代码3-1#include#include void main(int argc,char*argv)int count=1;while(argc 1)printf(argvcount);printf();count+;argc-;这段程序的作用是将输入的数逐个输出,但是这个程序存在一个格式化字符串漏洞,因为在这段代码“printf(argvcount)”中,缺少了一个指定以字符串格式输出的格式化规定符“%s”。如果输入的值是“ab cd ef”,输出如图 3-1:名师资料总结-精品资料欢迎下载-名师精心整理-第 9 页,共 28 页 -西南科技大学本科生毕业论文7 图 3-1 输入“ab cd ef”得到的正确结果结果是正常的,但是如果输入的是“%s”,结果却是如下图3-2:图 3-2 程序崩溃结果该程序在试图读取不可读取地址“0 xbc73e708”的内容,所以出现了要强制终止程序的结果。但是如果将“printf(argvcount)”修改为“printf(%s,argvcount)”后,我们再次输入“%s”,结果如图 3-3 所示:图 3-3 修改后输出“%s”的正确结果此时可以看到程序正常的输出了字符串“%s”。使程序崩溃的原理很简单,就是输入了特殊的字符“%s”让程序去读取受保护的地址内容,从而让程序意外中断。但是要达到使程序崩溃的目的,必须要满足两个条件,第一个条件就是这个程序必须存在这样的一个格式化字符串漏洞,第二个条件就是存在格式化字符串漏洞的这个变量必须由我们外部输入。只要满足了这两个条件,就可以达到使程序崩溃的目的。3.2 偷窥堆栈内容偷窥堆栈内容我们主要用到“%x”这个格式化规定字符,这个字符的本来作用是名师资料总结-精品资料欢迎下载-名师精心整理-第 10 页,共 28 页 -西南科技大学本科生毕业论文8 规定变量的格式为无符号十六进制的格式18。但是在某些时候,它却可以读取堆栈中的 32 个字节的内容并以十六进制的形式输出,堆栈中有很多重要的信息,比如返回地址,是不因该被用户知道的,但是通过攻击却可以得到这些重要的内容。假设有如下程序代码 3-2:这个程序的本来作用是将输入的一系列数的第一个数进行输出,但是这个程序也存在一个格式化字符串漏洞。就在这段代码中“printf(buff)”,因为它没有一个格式化规定符来规定变量“buff”以什么样的格式进行输出,而程序默认的是以字符串的格式进行输出,假如输入的是“AAAA DDDD”,则输出结果如图3-4 所示:图 3-4 输入“AAAA DDDD”得到的正确结果代码 3-2#include#include void function(char*string)char buff256;memset(buff,0,sizeof(buff);strncpy(buff,string,sizeof(buff)-1);printf(buff);int main(int argic,char*argiv)function(argv1);return(0);名师资料总结-精品资料欢迎下载-名师精心整理-第 11 页,共 28 页 -西南科技大学本科生毕业论文9 结果是正确的,但是如果当输入的是“AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x”的时候(这里有 25 个“%x”),结果却是如图 3-5 所示的:图 3-5 带有堆栈内容的偷窥结果可以看到,结果并没有输出25 个“%”,而是输出了一系列堆栈中的内容,经过仔细观察,可以发现,程序输出结果中有 4 个“41”,而这 4 个“41”其实就是“AAAA”的十六进制 ASCII 码.所以从结果可以看出,程序不仅输出了AAAA,还输出了存放的堆栈中的内容。在 printf 处设置断点,进入断点调试,在查看里面打开“Registers”,可以看到寄存器的内容如图 3-6 所示:图 3-6 查看 EAX 寄存器内容输入的变量是放在EAX 中的,而 EAX 的堆栈地址是 0012FE28,然后在查看中打开“Memory”窗口,在地址处输入 0012FE28然后回车,就可以看到这里的堆栈里的内容,恰好就是在cmd调试窗口中的得到的输出结果如图3-7 所示:名师资料总结-精品资料欢迎下载-名师精心整理-第 12 页,共 28 页 -西南科技大学本科生毕业论文10 图 3-7 堆栈内容就这样,通过“%x”查看到了堆栈中本不应该本看到的重要内容,达到了偷窥堆栈内容的目的。其实要达到这样的攻击目的,也同样需要两个条件,一个是这个程序存在格式化字符串漏洞,就像这里的在输出代码中缺少一个格式规定符%s,第二个条件就是这里的输出变量是外面用户可以控制的。所以,如果没有程序员的疏忽,在输出代码中他规范的写成:printf(%s,buff),那么当再次输入同样的攻击数据的时候,得到的结果却是正确的,如下图3-8 所示:图 3-8 修复漏洞后程序正确结果所以,当程序存在这样的格式化字符串漏洞的时候,可以通过向程序传递精心计算过的字符串,来使程序输出那些存在堆栈中的本不应该被输出的内容,在上面的例子中,就成功的得到了存放输入变量的堆栈中的内容,甚至,还可以通过计算,得到程序的返回地址这些相当重要的信息。3.3 修改程序返回地址要修改程序的返回地址,就需要向程序里写入内容来覆盖原来的返回地址,而要向程序写入内容,就必须要用到“%n”这个格式化规定符,“%n”的作用是把它的参数作为内存地址,把前面已经打印的字符长度写入到那个内存地址中,这就意味做,有名师资料总结-精品资料欢迎下载-名师精心整理-第 13 页,共 28 页 -西南科技大学本科生毕业论文11 机会改写某个内存地址的内容,而就是要改写某个返回地址的内容,将它改为shellcode的地址,这样程序在执行返回的时候,就直接跳转到攻击程序里了,这样就取得了这个程序的完全控制权15。这里先用一个简单的程序来说明“%n”的作用,程序如下:这个程序就是利用“%n”来统计字符串“show the use of n.”的长度,然后把统计出来的值放在变量 i 中输出,运行结果如下图3-9 所示:图 3-9“%n”的使用方法所以,如果需要精心的设计这个字符串的长度为shellcode 地址,但是,一般的地址的数值都是比较大的,例如0012FF409,如果要用字符串来表示的话,就需要1244992 个字符,很明显这是不现实的,这个时候,就需要用到另一个格式化字符“%nx”这里的“n”可以指定输出字段的宽度。例如,攻击代码地址是0X00124440,那么就要让 printf 表达式格式 124492(0012FF40 的十进制数)个字符。那么就可以这样输入:“%622496x%622496x%n”。这样就可以成功将程序的返回地址覆盖为指定的程序的地址。这里任然用代码3-2 来说明如何利用“%n”来修改程序的返回地址。代码 3-3#include#include void main()int i;printf(show the use of n.%nn,&i);printf(i=%dn,i);名师资料总结-精品资料欢迎下载-名师精心整理-第 14 页,共 28 页 -西南科技大学本科生毕业论文12 我们这里先输入“AAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x”,可以得到如下结果:图 3-10 利用“%x”显示出堆栈内容这里可以看到,20 个“%x”刚好可以刚刚显示出“41414141”(AAAA 的十六进制ASCII 码),然后我们把最后一个“%x”改为“%n”,看看会有什么结果:图 3-11 读取未定义地址出现错误可以看到程序奔溃了,因为程序试图向地址“0 x41414141”写入内容,但是这个地址在程序中是没有被定义的,所以会出现错误。但是这样就可以得出一个结论,我们有机会向某个地址写入一些内容。我们在 VC 中调试这个程序,在程序崩溃后查看Disassembly,可以看到如下一段代码:图 3-12 寄存器参数传递在这里可以看到,程序会让ecx 寄存器的的值传入到eax 所指向的地址中,如果我们能控制 eax和 ecx 的值,就可以取得这个程序的控制权。我们又调出Registers,名师资料总结-精品资料欢迎下载-名师精心整理-第 15 页,共 28 页 -西南科技大学本科生毕业论文13 查看寄存器的内容,如下图:图 3-13 查看 ECX 寄存器内容我们看到EAX 的值是“41414141”,而这恰好正是我们所输入的字符串中的“AAAA”,这就表明,我们可以通过控制输入的格式化字符串来控制eax的值。此时,ecx 的值是93,如果我们在刚才的输入字符串中加入“0123456789”,就是这样“AAAA0123456789%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n”,再来看看 ecx寄存器的值,如下图:图 3-14 修改后查看ECX 寄存器内容此时的 ecx 是“9D”,其实简单的运算一下就可以知道其中的奥妙,9D93=A,十六进制的 A 就是十进制的 10,这恰好就是我们多加的字符“0123456789”的个数,所以,我们也可以通过控制输入的格式化字符串来控制ecx 的值,这样我们就可以完全的控制程序,改写程序中某个地址中的内容为我们精心设计的值,一般来说我们会修改程序的返回地址为我们的shellcode 的地址,这样就可以完全取得程序的控制权了。现在,我们需要构造特殊的格式化字符,来使ecx 的值为我们 shellcode 的值。在这里我们假设 shellcode的值是“0 x0012FE74”,其十进制数是 1244788,我们不可能输入 1244788个字符进去,所以这个时候就需要用到前面的“%n”了,我们把 1244788名师资料总结-精品资料欢迎下载-名师精心整理-第 16 页,共 28 页 -西南科技大学本科生毕业论文14 先减去 157(9D)得到 1244631,再把 1244631除以 3 得到 414877,我们修改输入的格式化字符串为这样的:“AAAA%414788x%414877x%414877x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n”,运行程序,程序崩溃后,查看寄存器内容,如下图:图 3-15 调试中寄存器ECX 内容此时的 ecx是 0012FE5B,已经很接近 0012FE74了,0012FE740012FE5B=19,19的十进制数是 25,我们把第一个%414877x改为%414902x,调试结果如下图:图 3-16 调试后 ECX 寄存器内容可以看到,ecx 的值刚好是我们假设的shellcode地址 0012FE74,这样,我们就成功的将 shellcode的地址值写入了 ecx。这里我们假设我们要改写的目标地址是“41414141”,可是一般我们要改写的地址很有可能开头都是“00”,比如“0 x0012FE20”,这样我们就没办法在格式化字符串的开头来控制 eax的值,因为这个包含nul 字节的地址可能将字符截断,所以我们不能把要修改的地址放在开头,而要房子字符串的结尾。因为IA-32 是小端法体系结构,存储在内存中的数据均会被反序,abcd 会议 dcba 的形式排列,这样开头的nul 就会被放在结尾了,而结尾是没有影响的,为了更好的说明这个问题,我们将代码2-3 稍做修改,如代码 3-4 所示:名师资料总结-精品资料欢迎下载-名师精心整理-第 17 页,共 28 页 -西南科技大学本科生毕业论文15 在这段代码中,我们要通过设计特殊的格式化字符串输入,来修改pwd 变量的值,首 相 我 们 要 知道pwd 变 量 存 放 的 位 置,加 入 我 们 输 入 的 值 是 这 样 的“AAAAAA%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%n%x%x%x%x%x%x%x%x%xABCDE”,调试运行结果是这样的,如下图:图 3-17 再次读取未定义地址导致程序崩溃代码 3-4#include#include char pwd=1234;void function(char*string)char buff256;printf(pwd addr:%p/n,pwd);memset(buff,0,sizeof(buff);strncpy(buff,string,sizeof(buff)-1);printf(pwd=%sn,pwd);printf(buff);printf(pwd=%sn,pwd);int main(int argic,char*argv)function(argv1);return(0);名师资料总结-精品资料欢迎下载-名师精心整理-第 18 页,共 28 页 -西南科技大学本科生毕业论文16 从 结 果 可 以 看 出,pwd 变 量存 放的 地 址是“0 x00425168”,而 我 们 试 图向“0 x00454443”写入内容,而“454443”恰好是“EDC”的 ASCII 字符,我们只需要将格式化字符串中最后面的“BCDE”修改为“BhQB(这是 425168的 ASCII 字符)”,我们再运行,结果如下图:图 3-18 成功修改变量“pwd”内容从结果可以看出,我们已经成功的修改了变量pwd 的值了,但是这里的pwd 却由于某些原因不能正常显示出来,但是它的确是变了,在printf(buff)后。3.4 地址偏移量的确定在利用格式化字符串漏洞时,一个很重要的问题就是地址偏移量(格式化字符串的地址到要覆盖的存放eip 的地址之间的距离)的确定。只有在找到地址偏移量后,才能修改返回地址,是程序返回是跳转到我们提供的代码去执行。为获得这个偏移量,一个简单的方法就是采用暴力法,一次一次的去试10。为了得到这个偏移量,采用如下形式的格式化字符串:“AAAABBBB|stackpop|%08x|”其中 stackpop 是一个用以递增堆栈指针的格式序列,它取决于我们猜测的偏移量。猜测的偏移量随测试的次数而递增。假设地址偏移量为32,采用“%u”来递增堆栈指针。随猜测的进行而逐渐增加“%u”的个数。当格式化字符串形式为“AAAABBBB|%u%u%u%u%u%u%u%u|%08x”时,输出将有如下形式:“AAAABBBB|1525484516258495875418256458951352465|41414141”“41414141”是“AAAA”的十六进制表示。在这里要注意的一点是,当地址偏移量不是 4 的倍数时,要调整stackpop或采用其他地方对其。名师资料总结-精品资料欢迎下载-名师精心整理-第 19 页,共 28 页 -西南科技大学本科生毕业论文17 由此可见,我们是利用格式化函数对我们提供的格式化字符串的响应来确定地址偏移量的。这种技术有一个缺点,对于有些守护进程或程序只允许测试一次,这是这种方法就失效了4。3.5 格式化字符串漏洞实例WU-Ftp(washington University Ftp server)是一个非常流行的UNIX/Linux 系统 Ftp服务器,他的 6.0 版本存在格式化字符串漏洞。由于在大多数Linux 系统中它是默认安装的,所以相当多的网站都受这个漏洞的影响,针对它的攻击也是相当普遍的10。3.5.1 漏洞描述首先分析以下 WU-Ftp 的源代码以确定漏洞所在。在 Ftp 的内部命令中,site arg1,arg2 是将参数作为 site 命令逐字发送给远程ftp主机的16。在 WU-Ftp 中,用户提交的“site exec”命令是由一个名为void site_exec(char*cmd)的函数来处理,其中cmd 是用户提交的命令。在这个函数中有这样的一条语句为:ftpcmd.y 文件 第 1929行 lerply(200,cmd);Site_exec()函数把用户提交的命令交给lreply()函数来处理,我们再看看lreply()函数的定义。ftpd.c 文件 第 5343行 代码 3-7 Void lerply(int n,char*fmt,.)VA_LOCAL_DECL if(!dolreplies)return;VA_START(fmt);vreply(USE_REPL Y_LONG,n,fmt,ap);VA_END;名师资料总结-精品资料欢迎下载-名师精心整理-第 20 页,共 28 页 -西南科技大学本科生毕业论文18 显然 lerply()的第二个参数 char*fmt 应该是格式化字符串,而在前面的调用中却把它交由用户命令来提供,这就是造成问题的地方。继而 lreply()把 fmt 交给 vreply()函数来处理,我们再来看看vreply()的定义。代码 3-8void vreply(long flags,int n,char*fmt,va_list ap)char bufBUFSIZ flags&=USE_REPL Y_NOTFMT|USE_REPL Y_LONG;if(n);sprintf(buf,%03d%c,n,flags&USE_REPLY_LONG?-:);if(flags&USE_REPL Y_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);printf(%srn,buf);#ifdefTRANSFER_COUNT Byte_count_total+=strlen(buf);Byte_count_out+=strlen(buf);#endif Fflush(stdout);由于提交给 vreply()的第一个参数(即flags)是 USE_REPLY_LONG,所以经过&=操作之后 flags 任然为 USE_REPLY_LONG。这样(flags&USE_REPLY_LONG)的值就是 0.所以下面的判断语句会进入else执行17:名师资料总结-精品资料欢迎下载-名师精心整理-第 21 页,共 28 页 -西南科技大学本科生毕业论文19 注意看要执行的vsprintf()函数,它把 fmt 放在了格式化字符串的参数位置上了。而这个 fmt 正是有用回提交的命令cmd。根据前面讲过的格式化字符串漏洞原理:如果 printf()函数的格式化字符串参数是由我们来提交的话,那么我们就可以用一格式化字符串来访问格式化字符串前面堆栈里的内容了,如果我们能访问到自己提交的内容,就可以在这内容的前面放上存放某个函数返回地址的地址,然后就可以用%n 来改写这个返回地址,使它跳转去执行我们提供的shellcode。3.5.2 漏洞利用要想利用这个漏洞,需要先登录Ftp 服务器然后提交一个如下形式的site exec命令:Site|exec|aa|retloc|%.f,%.f|%.(ret)d|%n【说明】(1)其中的“1”符号是为了使读者看清楚字符串结构而加上的分隔符,实际提交的字符串里是没有的。(2)这个有我们提交的格式串前面是site exec命令,后跟的“aa”的作用是为了使retloc 以 4 字节为单位对齐,就是我们通常所说的align。(3)然后紧跟着的retloc 就是我们要写入的储存函数返回地址的地址(我们将用%n 来对应它),可以把跳转地址写入函数返回地址了。一般我们要改写的函数是最近的一个函数,这里可以改写vreply()的返回地址,使它返回时跳去执行我们的shellcode。(4)在 retloc 后面放上一串%.f,前面说过一个%.f 一次显示 8 位数字,在这里它的作用是显示在我们提交的命令串后压入堆栈的局部变量,是%n 能够正好对应代码 3-9 if(flags&USE_REPL Y_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);名师资料总结-精品资料欢迎下载-名师精心整理-第 22 页,共 28 页 -西南科技大学本科生毕业论文20 retloc。(5)然后紧跟着的%.(ret)d 的作用是把打印的字符数量调整为正好是shellcode地址的值,使得%n 能够把已打印的字符数量,即shellcode的地址正好写进 vreply()函数的返回地址中。要注意的是这里的ret 并不是 shellcode的地址,而应该是shellcode地址-(%.f 个数*8)-16 其中的 16 是前面的“Site|exec|aa|retloc”的字符数。这样在%n 之前打印的字符数就恰好是 shellcode的地址。(6)最后的%n 的作用是为了把跳转地址(shellcode地址)写入 vreply()的返回地址,使它返回时跳转去执行shellcode