#格式化串漏洞原理与利用
##实验目的
- 了解格式化串漏洞产生的原理
- 掌握格式化串漏洞分析与利用
##实验环境
- 实验环境
- 操作机
Windows7 - OllyDbg
- Visual Studio 6.0
- 操作机
##实验内容
本实验中,我将带领大家编写几个具有格式化串漏洞的可执行程序,然后使用OllyDbg调试工具对程序进行动态调试,定位漏洞触发点,最后进行简单的漏洞利用。
###PART 1 了解格式化串漏洞产生的原理
认识格式化串
所谓格式化串,就是在*printf()系列函数中按照一定的格式对数据进行输出,可以输出
到标准输出,即printf(),也可以输出到文件句柄,字符串等,对应的函数有fprintf,sprintf,
snprintf,vprintf,vfprintf,vsprintf,vsnprintf等
####步骤1:编写简单的测试程序
本步骤使用Visual Studio编写示例程序
格式化漏洞产生与数据输出函数中对输出格式解析的缺陷,我们以最熟悉的printf函数为例,其参数应该含有两部分:格式控制符和待输出的数据列表。
新建一个文本文档,将其重命名为formatstr.c,右键 -> 打开方式 -> Microsoft (R) Developer Studio,用Visual Studio 6.0打开并编辑,写入如下内容:
|
|
对于上述代码,第一个printf调用是正确的,第二个调用中则缺少了输出数据的变量列表。那么第二个调用将引起编译错误还是照常输出数据?如果输出数据又将是什么类型的数据呢?
在菜单栏右键选择Build选项卡,然后将Win32 Debug修改成Win32 Release,然后依次点击 Compile -> Build -> Execute三个键,如下图所示来运行程序:
第二次调用没有引起编译错误,程序正常执行。只是输出的数据有点出乎预料。我们这里使用OllyDbg调试一下。
####步骤2:通过OllyDbg调试了解printf中的缺陷
找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后点击打开,界面会变成这样:
在反汇编窗口中右键选择转到->表达式,弹出一个对话框,在文本框中输入0x00401000后,点击确定。此时我们来到了main函数代码入口。然后在地址00401000按F2下断点,接着按F9,此时OllyDbg就来到了main函数起始处。如下图所示:
此时我们按三次F8来到00401009处,观察一下栈的变化,如下图所示:
地址00401000到地址00401004这三行汇编代码是将参数压入栈内,实现函数传参的功能。我们在看一下栈内存。栈地址0012FF40存放着第一个参数:格式控制符,栈地址0012FF44和0012FF48存放着数据列表也既是第二、第三个参数,分别为44和77。
此时我们在单步F8步过输出函数,控制台已经输出了字符串。如下图所示:
这个时候我们在观察栈顶会发现,上一次调用过printf后,使用过的参数还在栈中分部着。此时我们再F8单步,同上次调用传参一样,地址0040100E处的汇编指令进行压栈传参。同时栈结构又发生改变。如下图所示:
栈地址0012FF3C处存放格式化控制符,第一个参数;栈地址0012FF40和0012FF44出分别存放着数据列表也既是第二个、第三个参数,分别是:0x00407030、0x2C,由于格式化控制符中我们是用了%d,这代表以十进制数值显示。其实0x00407030就是十进制4223024。再单步F8看结果吧。如下图所示:
到这里,应该明白第二个printf为什么会输出那样的数值了吧?!
####实验结果分析与总结
我们通过使用OllyDbg动态跟踪到两次调用printf函数的地方,观察了参数在栈上的分部情况,了解了第二次调用printf函数时,为什么会输出其他的数值。同时也了解格式控制符的缺陷。
####思考
1、我们能否控制printf的格式控制符进而输出其他的数值?如果能,又当如何?
####题目
1、printf函数的参数个数是否是固定的?()
A 固定的
B 不固定的
C 有限个
正确答案:B 解析:不是固定的,因为printf函数为多参数函数。
2、printf函数中%d是哪种格式控制符?()
A 十六进制
B 八进制
C 十进制
D 字符串
正确答案:C 解析:C语言基础知识,%d为十进制格式控制符。
3、本实验中,第二个printf打印出的第一个数值是?()
A 00407032
B 00408030
C 00407030
D 00408032
正确答案:C 解析:没啥好讲的,动手做实验可知。
###PART2:用printf读取内存数据
到此为止,这个问题还只是一个bug,算不上漏洞。但如果printf函数参数中的“格式控制符”可以被外界输入影响,那就是所谓的格式化串漏洞了。
####步骤1:修改测试程序
修改formatstr.c中的代码,如下内容:
|
|
在菜单栏右键选择Build选项卡,然后将Win32 Debug修改成Win32 Release,然后依次点击 Compile -> Build生成可执行文件后,此时点击任务栏最左边开始弹出管理面板后,在搜索栏中输入cmd然后回车,此时弹出了一个黑色的命令行窗口。然后通过cd命令切换到Release版本的formatstr.exe的目录下面,如下图:

当我们向程序传入普通字符串(如“ichunqiu”)时,将得到简单的反馈。但是如果传入的字符串中带有格式控制符时,printf就会打印出栈中“莫须有”的数据。例如,输入%p,%p,%p......,实际上可以读出栈中的数据,如下图所示:

####步骤2:通过OllyDbg调试知其然
本节调试的程序由于需要向main传递参数,所以在使用OllyDbg打开可执行程序时,不要忘记设置参数!!!
找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后,然后在参数:文本框中输入ichunqiu,这就是向main函数传递参数。如下图所示:

然后再次点击打开,界面如下图:

在反汇编窗口中右键选择转到->表达式,弹出一个对话框,在文本框中输入0x004010C5后,点击确定。然后在地址004010C5按F2下断点,接着按F9,此时OllyDbg暂停下来。地址004010C5出对应的汇编call dword ptr ds:[<&KERNEL32.GetCommandLineA>]表达的是调用GetCommandLineA函数获取命令行。然后再单步F8`,执行完函数后,将出现一串字符串。如下图所示:

这段字符串就是我们前面通命令行窗口执行的命令,其中ichunqiu为main函数的参数。接着在地址00401100处按F2下断点,然后按F9运行后,再单步F7步入来到main函数入口,如下图所示:

然后单步F8两次到达地址00401007处。该行汇编即将进行压栈传参,寄存器ecx中存放这字符串ichunqiu的地址。此时再一次单步F8执行到地址00401008,准备调用printf函数。如下图所示:

根据我们第一个PART的调试的经验可知。栈地址0012FF48中存放着第一个参数ichunqiu。在本次程序中我们并没有利用格式化串漏洞,因此这次只简单输出一个字符串。单步F8后,命令行窗口输出结果,如下图所示:

####步骤3:通过OllyDbg调试知其所以然
根据步骤二的调试经验,让我们再次深入理解格式化串漏洞是如何利用的。
还是找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后,然后在参数:文本框中输入%p,%p,%p,%p,这就是向main函数传递参数。如下图所示:

然后点击打开,OllyDbg运行后,界面如下:

然后在反汇编窗口中滚动鼠标滑轮,在地址004010CB处按F2下断点,然后按F9运行,OllDbg断下来后,程序已经获取到了命令行字符串,同时我们可以观察,传给main函数的参数。如下图所示:

然后在反汇编窗口中右键选择转到->表达式,弹出一个对话框,在文本框中输入0x00401000后,点击确定。然后在地址004010C5按F2下断点,接着按F9,此时OllyDbg暂停下来。我们来到了main函数入口,是不是似曾相识?不过这次唯一不通的是我们给main函数的参数。这也是关键的地方。如下图所示:

接下来我们单步F8三次,到达地址00401008处。此时我们观察栈内存。如下图所示:

根据我们在PART1中所讲,printf函数中的参数为两个部分,第一个是格式控制符,第二个是数据列表,栈地址0012FF48中存放的是格式控制符%p,%p,%p,%p,这也是我们可以认为控制的输入参数。但是程序逻辑中并没有匹配的数据列表,但是对于printf函数来讲,它只会根据格式控制符中的控制符个数依次打印栈中的值。也就是说从栈地址0012FF4C到0012FF58这四个地址中存放的数值被printf函数当成数据列表,然后异常打印出来。我们再单步F8步过看效果。如下图所示:

看到了吧。我们通过输入格式符竟然可以泄漏栈上面的信息!
####实验结果分析与总结
在本部分中,我们通过两次调试,来证明只要我们输入特定的格式符进去,printf函数的数据列表是可控的,进而达到泄漏栈信息的目的。
####思考
1、既然printf函数能泄漏信息,那该如何进行防范呢?
####题目
1、本次实验中我们总共泄漏了几个栈内存信息?()
A 1
B 2
C 3
D 4
正确答案:D 解析:可以通过%p,%p,%p,%p判断。
2、格式符%p是下列哪种数值的?()
A 十进制
B 十六进制
C 八进制
D 二进制
正确答案:B 解析:C语言基础知识。
3、获取命令行的是哪个函数?()
A GetLine
B GetCommandLineA
C GetCommand
正确答案:B 解析:实验步骤中有具体介绍。该函数为kernel32的导出函数。