格式化串漏洞基础

#格式化串漏洞原理与利用

##实验目的

  • 了解格式化串漏洞产生的原理
  • 掌握格式化串漏洞分析与利用

##实验环境

  • 实验环境
    • 操作机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打开并编辑,写入如下内容:

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void)
{
int a=44,b=77;
printf("a=%d,b=%d\n",a,b);
printf("a=%d,b=%d\n");
return 0;
}

对于上述代码,第一个printf调用是正确的,第二个调用中则缺少了输出数据的变量列表。那么第二个调用将引起编译错误还是照常输出数据?如果输出数据又将是什么类型的数据呢?

在菜单栏右键选择Build选项卡,然后将Win32 Debug修改成Win32 Release,然后依次点击 Compile -> Build -> Execute三个键,如下图所示来运行程序:
1.png

第二次调用没有引起编译错误,程序正常执行。只是输出的数据有点出乎预料。我们这里使用OllyDbg调试一下。

####步骤2:通过OllyDbg调试了解printf中的缺陷

找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后点击打开,界面会变成这样:
2.png

在反汇编窗口中右键选择转到->表达式,弹出一个对话框,在文本框中输入0x00401000后,点击确定。此时我们来到了main函数代码入口。然后在地址00401000F2下断点,接着按F9,此时OllyDbg就来到了main函数起始处。如下图所示:
3.png

此时我们按三次F8来到00401009处,观察一下栈的变化,如下图所示:
4.png

地址00401000到地址00401004这三行汇编代码是将参数压入栈内,实现函数传参的功能。我们在看一下栈内存。栈地址0012FF40存放着第一个参数:格式控制符,栈地址0012FF440012FF48存放着数据列表也既是第二、第三个参数,分别为44和77。

此时我们在单步F8步过输出函数,控制台已经输出了字符串。如下图所示:
5.png

这个时候我们在观察栈顶会发现,上一次调用过printf后,使用过的参数还在栈中分部着。此时我们再F8单步,同上次调用传参一样,地址0040100E处的汇编指令进行压栈传参。同时栈结构又发生改变。如下图所示:
6.png

栈地址0012FF3C处存放格式化控制符,第一个参数;栈地址0012FF400012FF44出分别存放着数据列表也既是第二个、第三个参数,分别是:0x00407030、0x2C,由于格式化控制符中我们是用了%d,这代表以十进制数值显示。其实0x00407030就是十进制4223024。再单步F8看结果吧。如下图所示:
7.png

到这里,应该明白第二个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中的代码,如下内容:

1
2
3
4
5
6
#include <stdio.h>
int main(int argc,cha** argv)
{
printf(argv[1]);
return 0;
}

在菜单栏右键选择Build选项卡,然后将Win32 Debug修改成Win32 Release,然后依次点击 Compile -> Build生成可执行文件后,此时点击任务栏最左边开始弹出管理面板后,在搜索栏中输入cmd然后回车,此时弹出了一个黑色的命令行窗口。然后通过cd命令切换到Release版本的formatstr.exe的目录下面,如下图:

8.png

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

9.png

####步骤2:通过OllyDbg调试知其然

本节调试的程序由于需要向main传递参数,所以在使用OllyDbg打开可执行程序时,不要忘记设置参数!!!

找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后,然后在参数:文本框中输入ichunqiu,这就是向main函数传递参数。如下图所示:

10.png

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

11.png

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

12.png

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

13.png

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

14.png

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

15.png

####步骤3:通过OllyDbg调试知其所以然

根据步骤二的调试经验,让我们再次深入理解格式化串漏洞是如何利用的。

还是找出刚刚编译出的程序文件,位于formatstr.c同一目录的Release目录下,formatstr.exe。双击打开OllyDbg,在OllyDbg界面的左上角点击File -> Open,然后找到formatstr.exe,选中后,然后在参数:文本框中输入%p,%p,%p,%p,这就是向main函数传递参数。如下图所示:

16.png

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

17.png

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

18.png

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

19.png

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

20.png

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

21.png

看到了吧。我们通过输入格式符竟然可以泄漏栈上面的信息!

####实验结果分析与总结

在本部分中,我们通过两次调试,来证明只要我们输入特定的格式符进去,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的导出函数。

文章目录