缓冲区溢出是一个常见的且大家经常耳闻的软件安全漏洞,缓冲区溢出即是数据被过多的写入内存或者缓冲区,当一个缓冲区内的数据写满后,如果继续写入数据,数据就会溢出到其他缓冲区中,这将会覆盖或者说是破坏已存储的数据。
为了利用缓冲区溢出,你需要了解堆栈,CPU寄存器,内存分配等基础知识。由于缓冲区溢出本身就是一个非常庞大的话题,在这篇文章中我们会试着去理解基于堆栈的缓冲区溢出的基础知识,希望你能够喜欢。
首先,我们会创建一个简单的C程序并介绍一些基本知识,比如程序在内存中是如何运行的,函数调用是如何发生的,什么是返回地址等等。这就让我们从最基本的知识开始吧。
在程序中,堆栈就是用来存储临时数据的一块连续的内存。堆栈的原理即是后进先出,其有两个函数 ,PUSH是将数据放入堆栈中,POP是将数据从堆栈中取出来。在基于英特尔的系统中,从下往上他的内存地址逐渐增加,在英特尔32位架构下最大为4个字节的数据。
基本上程序的堆栈包含以下几类数据:
函数参数 调用函数的地址 返回地址 局部变量
在了解开始之前,我们需要安装会用到的工具。
虚拟机中配置完成的Windows XP Service Pack 2 Immunity Debugger Dev C ++
首先,我们会看看函数是如何被调用,返回地址等等东西。从这一阶段开始,我们已经编写好一个简单的C程序,在这段代码中我们定义了一个函数。EXE文件以及源码会在文章最后打包分享给大家。让我们看一看程序的源代码,这样我们能理解一些基本概念。
从上面的截图我们可以看到,我们在这个简单的C程序中定义了一些局部变量。接着我们使用变量调用函数,然后定义一个函数打印出“Function is called”,最后返回值为1,返回主程序。
在程序编译完成之后,我们使用Immunity Debugger工具打开程序。
点击文件菜单或者拖动这个EXE文件到Immunity Debugger工具,之后我们将在屏幕上看到以下信息。
我们在屏幕上看到四个部分,每一个部分都代表着不同类型的CPU信息。
1.信息中含有各种寄存器以及它们的值。 2.我们可以看到程序的内存转储,我们可以看到什么类型的数据被写到内存中。 3.我们也可以根据需求编辑这些值。 4.最重要的是:它显示了程序堆栈的状态
如果我们仔细观察屏幕,可以在窗口的右下角看到一个暂停按钮。这意味着用Debugger打开程序,Debugger可以对程序运行进行暂停,所以我们可以手动先启动程序。
现在,我们就开始对程序进行分析吧
第一步就是确定主函数,以及我们在程序中定义的其他函数。 如果我们的鼠标在第一个区域进行滚动,可以看到以文件名命名的主函数的ASCII以及其他函数的汇编代码,在本例中EXE文件名为First.exe。
截图中,我们有标识一些数字,这些数字是为了方便我们在接下来的内容中更好的表述而设立的。
这是主函数的汇编代码,我们可以在截图中看到“First.xxxxx”“Main Function”。 在本例中, First.xxx是我们加载到Debugger中的EXE文件名。在左边我们看到的是每一个汇编指令的内存地址,00401290这个内存地址本例中表示程序启动,004012DA则表示程序结束。图中蓝色的一行是调用我们在C程序中定义的函数。
在我们仔细观察主函数的调用时发现,主函数内存地址的后面8位中调用了一个“First.004012D3”
最后,我们可以看到函数加载到内存中,并通过printf函数打印出“Function is called”。 这个函数是将数据放入EBP寄存器中,即为入栈。在本例中,函数开始的内存地址为“004012DE”, 函数结束的内存地址为“004012FE”,这两个地址都可以在上面截图中看到。
这是最重要的一部分,我们将了解如何定义程序的断点。 断点可以帮助我们在某一个位置冻结程序,让我们可以分析寄存器状态,栈,指针等信息。 当然也允许我们对某些变化的值进行修改。所以就让我们在调用函数之前创建一个断点吧,设置断点我们可以选中某行并点击一下或者按下F2。
如上图所示,创建一个断点之后,这一行会被高亮显示。接着我们启动程序,我们可以使用F9快捷键。我们可以在屏幕上看到有些数字变化了。
在上图中,我们发现堆栈以及寄存器的值变化了,另一个有趣的事是存储断点的EIP寄存器的值。
目前,我们有两个选择
逐条指令跟踪:当我们在创建一个断点之后想执行下一条指令,我们可以使用逐条指令跟踪,键盘快捷键进入F7。 多条指令跟踪:有时候我们不需要获取详细的函数调用细节,在这种情况下我们可以使用多条指令跟踪,键盘快捷键进入F8。
好了,我接下来需要执行下一条指令,我们按下键盘快捷键F7,对堆栈进行分析。
如上图所示,在4号窗口中没有什么亮点。在1号窗口中我们可以看到断点下一条指令被执行,在2号窗口中我们看到下一条指令在EIP寄存器中分配的内存地址。
继续按下F7快捷键,我们可以看到如下这张图
我们可以看到下一个指令被执行,以及屏幕上许多数据的变化。
在1号窗口中可以看到,控制器已在前面给出的地址调用函数。仔细观察4号窗口,在堆栈的顶部,我们看到内存地址004012D4,这个内存地址是主程序的返回地址,这意味着函数执行完成后,通过这个地址计数器会跳转到主程序,也就是说程序执行完毕。
分析堆栈 创建断点 分析函数调用以及寄存器状态
注:原作者未有提及第一节会将什么内容,所以就让他保持点神秘感吧。
文章中涉及源码链接: http://pan.baidu.com/s/1c0dafPQ 密码:mr5l
http://debugger.immunityinc.com/ID_register.py
https://www.owasp.org/index.php/Buffer_Overflows
http://en.wikipedia.org/wiki/Buffer_overflow
http://en.wikipedia.org/wiki/Stack_buffer_overflow
http://www.bloodshed.net/dev/devcpp.html
译者注:此乃基础教程,求轻喷。 Ps.我就知道你们会说AV画质的
* 参考来源 infosec ,翻译/鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)