#格式化串漏洞原理与利用
##实验目的
- 了解格式化串漏洞产生的原理
- 掌握格式化串漏洞分析与利用
##实验环境
- 实验环境
- 操作机
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的导出函数。