拼吾爱程序人生

首页 » .Net编程 » Visual Studio.NET » 攻击方式学习之(3) - 缓冲区溢出(Buffer Overflow)
cobra - 2008-9-7 8:12:00
堆栈溢出

堆栈溢出通常是所有的缓冲区溢出中最容易进行利用的。了解堆栈溢出之前,先了解以下几个概念:

现 代计算机被设计成能够理解人们头脑中的高级语言。在使用高级语言构造程序时最重要的技术是过程(procedure)和函数(function)。从这一 点来看,一个过程调用可以象跳转(jump)命令那样改变程序的控制流程,但是与跳转不同的是,当工作完成时,函数把控制权返回给调用之后的语句或指令。 这种高级抽象实现起来要靠堆栈的帮助。堆栈也用于给函数中使用的局部变量动态分配空间,同样给函数传递参数和函数返回值也要用到堆栈。

堆栈由逻辑堆栈帧组成。当调用函数时逻辑堆栈帧被压入栈中,当函数返回时逻辑堆栈帧被从栈中弹出。堆栈帧包括函数的参数,函数地局部变量,以及恢复前一个堆栈帧所需要的数据,其中包括在函数调用时指令指针(IP)的值。

当一个例程被调用时所必须做的第一件事是保存前一个 FP(这样当例程退出时就可以恢复)。然后它把SP复制到FP,创建新的FP,把SP向前移动为局部变量保留空间。这称为例程的序幕(prolog)工 作。当例程退出时,堆栈必须被清除干净,这称为例程的收尾(epilog)工作。Intel的ENTER和LEAVE指令,Motorola的LINK和 UNLINK指令,都可以用于有效地序幕和收尾工作。

下面我们用一个简单的例子来展示堆栈的模样: example1.c:


Code




为了理解程序在调用function()时都做了哪些事情, 我们使用gcc的-S选项编译, 以产生汇编代码输出:
$ gcc -S -o example1.s example1.c
通过查看汇编语言输出, 我们看到对function()的调用被翻译成:

Code


以从后往前的顺序将function的三个参数压入栈中, 然后调用function(). 指令call会把指令指针(IP)也压入栈中. 我们把这被保存的IP称为返回地址(RET). 在函数中所做的第一件事情是例程的序幕工作:

Code


将帧指针EBP压入栈中. 然后把当前的SP复制到EBP, 使其成为新的帧指针. 我们把这个被保存的FP叫做SFP. 接下来将SP的值减小, 为局部变量保留空间. 我 们必须牢记:内存只能以字为单位寻址. 在这里一个字是4个字节, 32位. 因此5字节的缓冲区会占用8个字节(2个字)的内存空间, 而10个字节的缓冲区会占用12个字节(3个字)的内存空间. 这就是为什么SP要减掉20的原因. 这样我们就可以想象function()被调用时堆栈的模样:

 附件: 您所在的用户组无法下载或查看附件
所以,从上图来看,假如我们输入的buffer1超长了,直接覆盖掉后面的sfp和ret,就可以修改该函数的返回地址了。下面来看一个示例吧。


示例

关于如何编写Shell Code,如何在内存中预先准备好一段危险的执行代码以及如何精确计算通过缓冲区溢出执行那段危险代码同时又让返回地址调回原来返回地址……这中间涉及太 多的底层汇编知识,小弟不才也只是走马观花,成不了真正的黑客高手。但从黑客朋友的水平之高看来,提高我们的代码安全性是非常必要的!

因此,在这个例子中,我们假设所谓的危险代码已经在 源代码中,即函数bar。函数foo是正常的函数,在main函数中被调用,执行了一段非常不安全的strcpy工作。利用不安全的strcpy,我们可 以传入一个超过缓冲区buf长度的字符串,执行拷贝后,缓冲区溢出,把ret返回地址修改成函数bar的地址,达到调用函数bar的目的。

Code


用GCC编译上面的程序,同时注意关闭Buffer Overflow Protect开关:
gcc -g -fno-stack-protector test.c -o test

为了找出返回地址,我用gdb调试上面编译出来的程序。

Code


因此,我们只要输入一个超长的字符串,覆盖掉0x08048499,变成bar的函数地址0x8048419,就达到了调用bar函数的目的。为了将0x8048419这样的东西输入到应用程序,我们需要借助于Perl或Python脚本,如下面的Python脚本:

Code


注意上面的08 04 84 19要两个两个反着写。执行一下:

Code


(文/coderzh  出处/http://www.cnblogs.com/coderzh/)

 您可能对 [Visual Studio.NET] 的这些文章也感兴趣:

使用 MethodImplAttribute 实现方法级别的线程同步
.NET技术书籍推荐
VSTS报表自定义之Bug重现频率报表
Visual Studio 2008的文本模板转换工具箱
Rhino Mocks的Lambda版本
Tracing memory leaks in .NET applications with ANTS Profiler
COM组件开发实践(四)---From C++ to COM :Part 1
BlogEngine.Net架构与源代码分析系列part11:开发扩展(下)——自定义Theme
使用微软分布式缓存服务Velocity Part 1
微软正式发布支持Visual Studio 2008的Enterprise Library 4.0
cobra - 2008-9-7 8:17:00
堆溢出及其他溢出

堆溢出

堆是内存的一个区域,它 被应用程序利用并在运行时被动态分配。堆内存与堆栈内存的不同在于它在函数之间更持久稳固。这意味着分配给一个函数的内存会持续保持分配直到完全被释放为 止。这说明一个堆溢出可能发生了但却没被注意到,直到该内存段在后面被使用。这里只是简单了解一下,下面看一个最简单的堆溢出例子:

Code


我们来看执行结果:

Code


我们来看看是如何溢出的:


格式化字符串错误

这类错误是指使用printf,sprintf,fprint等函数时,没有使用格式化字符串,比如:正确用法是:
printf("%s", input)


如果直接写成:
printf(input)


将会出现漏洞,当input输入一些非法制造的字符时,内存将有可能被改写,执行一些非法指令。


Unicode和ANSI缓冲区大小不匹配

我们经常碰到需要在Unicode和ANSI之间互相转换,绝大多数Unicode函数按照宽字符格式(双字节)大小,而不是按照字节大小来计算缓冲区大小,因此,转换的时候不注意的话就可能会造成溢出。比如最常受到攻击的函数是MultiByteToWideChar,看下面的代码:

Code


wszUserName是宽字符的,因此,sizeof(wszUserName)将会是256*2个字节,因此存在潜在的缓冲区溢出问题。正确的写法应该是这样的:

Code


曾真实出现的Internet打印协议缓冲区溢出就是由于此类问题导致的。

预防和发现问题

不安全的函数

避免使用不安全的字符串处理函数,比如使用安全的函数代替:

不安全的函数
安全函数
strcpy
strncpy
strcat
strncat
sprintf
_snprintf
gets
fgets


Visual C++ NET的/GS选项

/GS选项能够阻止堆栈的破坏,保证堆栈的完整性,但是不能完全防止缓冲区溢出问题,比如,对于堆溢出,/GS是无能为力的。

源代码扫描

最简单的源代码扫描:
grep strcpy *.c

然后就是一些开源的或是商业的源代码扫描工具了。


工具



参考资料



下一篇: 攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
1
查看完整版本: 攻击方式学习之(3) - 缓冲区溢出(Buffer Overflow)
Modify by pin5i DZNT_ExpandPackage 2.1.3258 2007-2008 pin5i.com
  Total Unique Visitors: