A. 网络安全-----缓冲区溢出的保护方法有哪些
目前有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响。
编写正确的代码 非执行的缓冲区 数组边界检查 程序指针完整性检查
一、编写正确的代码 Top
编写正确的代码是一件非常有意义但耗时的工作,特别像编写C语言那种具有容易出错倾向的程序(如:字符串的零结尾),这种风格是由于追求性能而忽视正确性的传统引起的。尽管花了很长的时间使得人们知道了如何编写安全的程序组具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。
最简单的方法就是用grep来搜索源代码中容易产生漏洞的库的调用,比如对strcpy和sprintf的调用,这两个函数都没有检查输入参数的长度。事实上,各个版本C的标准库均有这样的问题存在。为了寻找一些常见的诸如缓冲区溢出和操作系统竞争条件等漏洞,一些代码检查小组检查了很多的代码。然而依然有漏网之鱼存在。尽管采用了strcpy和sprintf这些替代函数来防止缓冲区溢出的发生,但是由于编写代码的问题,仍旧会有这种情况发生。比如lprm程序就是最好的例子,虽然它通过了代码的安全检查,但仍然有缓冲区溢出的问题存在。
为了对付这些问题,人们开发了一些高级的查错工具,如faultinjection等。这些工具的目的在于通过人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。还有一些静态分析工具用于侦测缓冲区溢出的存在。虽然这些工具可以帮助程序员开发更安全的程序,但是由于C语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞。所以,侦错技术只能用来减少缓冲区溢出的可能,并不能完全地消除它的存在,除非程序员能保证他的程序万元一失。
二、非执行的缓冲区 Top
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被植入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统为实现更好的性能和功能,往往在数据段中动态地放人可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。但是我们可以设定堆栈数据段不可执行,这样就可以最大限度地保证了程序的兼容性。Linux和Solaris都发布了有关这方面的内核补丁。因为几乎没有任何合的
程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性问题,除了在Linux中的两个特例,这时可执行的代码必须被放入堆栈中:
1.信号传递
Linux通过向进程堆栈释放代码然后引发中断来执行在堆栈中的代码进而实现向进程发送Unix信号.非执行缓冲区的补丁在发送信号的时候是允许缓冲区可执行的.
2.GCC的在线重用
研究发现gcc在堆栈区里放置了可执行的代码以便在线重用。然而,关闭这个功能并不产生任何问题.只有部分功能似乎不能使用。非执行堆栈的保护可以有效地对付把代码植入自动变量的缓冲区溢出攻击,而对于其他形式的攻击则没有效果。通过引用一个驻留
的程序的指针,就可以跳过这种保护措施。其他的攻击可以采用把代码植入堆或者静态数据段中来跳过保护。
三、数组边界检查 Top
植入代码引起缓冲区溢出是一个方面,扰乱程序的执行流程是另一个方面。不像非执行缓冲区保护,数组边界检查完全没有了缓冲区溢出的产生和攻击。这样,只要数组不能被溢出,溢出攻击也就无从谈起。为了实现数组边界检查,则所有的对数组的读写操作都应当被检查以确保对数组的操作在正确的范围内。最直接的方法是检查所有的数组操作,但是通常可以来用一些优化的技术来减少检查的次数。目前有以下的几种检查方法:
1、Compaq C编译器
Compaq公司为Alpha CPU开发的C编译器支持有限度的边界检查(使用—check_bounds参数)。这些限制是:只有显示的数组引用才被检查,比如“a[3]”会被检查,而“*(a
+3)"则不会。由于所有的C数组在传送的时候是指针传递的,所以传递给函数的的数组不会被检查。带有危险性的库函数如strcpy不会在编译的时候进行边界检查,即便是指定了边界检查。在C语言中利用指针进行数组操作和传递是非常频繁的,因此这种局限性是非常严重的。通常这种边界检查用来程序的查错,而且不能保证不发生缓冲区溢出的漏洞。
2、Jones&Kelly:C的数组边界检查
Richard Jones和Paul Kelly开发了一个gcc的补丁,用来实现对C程序完全的数组边界检查。由于没有改变指针的含义,所以被编译的程序和其他的gcc模块具有很好的兼容性。更进一步的是,他们由此从没有指针的表达式中导出了一个“基”指针,然后通过检查这个基指针来侦测表达式的结果是否在容许的范围之内。当然,这样付出的性能上的代价是巨大的:对于一个频繁使用指针的程序,如向量乘法,将由于指针的频繁使用而使速度慢30倍。这个编译器目前还很不成熟,一些复杂的程序(如elm)还不能在这个上面编译、执行通过。然而在它的一个更新版本之下,它至少能编译执行ssh软件的加密软件包,但其实现的性能要下降12倍。
3、Purify:存储器存取检查
Purify是C程序调试时查看存储器使用的工具而不是专用的安全工具。Purify使用"目标代码插入"技术来检查所有的存储器存取。通过用Purify连接工具连接,可执行代码在执行的时候带来的性能的损失要下降3—5倍。
4、类型——安全语言
所有的缓冲区溢出漏洞都源于C语言的类型安全。如果只有类型—安全的操作才可以被允许执行,这样就不可能出现对变量的强制操作。如果作为新手,可以推荐使用具有类型—安全的语言如JAVA和ML。
但是作为Java执行平台的Java虚拟机是C程序.因此攻击JVM的一条途径是使JVM的缓冲区溢出。因此在系统中采用缓冲区溢出防卫技术来使用强制类型—安全的语言可以收到预想不到的效果。
四、程序指针完整性检查 Top
程序指针完整性检查和边界检查有略微的不同。与防止程序指针被改变不同,程序指针完整性检查在程序指针被引用之前检测到它的改变。因此,即便一个攻击者成功地改变程序的指针,由于系统事先检测到了指针的改变,因此这个指针将不会被使用。与数组边界检查相比,这种方法不能解决所有的缓冲区溢出问题;采用其他的缓冲区溢出方法就可以避免这种检测。但是这种方法在性能上有很大的优势,而且兼容性也很好。
l、手写的堆栈监测
Snarskii为FreeBSD开发丁一套定制的能通过监测cpu堆栈来确定缓冲区溢出的libc。这个应用完全用手工汇编写的,而且只保护libc中的当前有效纪录函数.这个应用达到了设计要求,对于基于libc库函数的攻击具有很好的防卫,但是不能防卫其它方式的攻击.
2、堆栈保护
堆栈保护是一种提供程序指针完整性检查的编译器技术.通过检查函数活动纪录中的返回地址来实现。堆栈保护作为gcc的一个小的补丁,在每个函数中,加入了函数建立和销毁的代码。加入的函数建立代码实际上在堆栈中函数返回地址后面加了一些附加的字节。而在函数返回时,首先检查这个附加的字节是否被改动过,如果发生过缓冲区溢出的攻击,那么这种攻击很容易在函数返回前被检测到。但是,如果攻击者预见到这些附加字节的存在,并且能在溢出过程中同样地制造他们.那么它就能成功地跳过堆栈保护的检测。通常.我们有如下两种方案对付这种欺骗:
1.终止符号
利用在C语言中的终止符号如o(null,CR,LF,—1(Eof)等这些符号不能在常用的字符串函数中使用,因为这些函数一旦遇到这些终止符号,就结束函数过程了。
2.随机符号
利用一个在函数调用时产生的一个32位的随机数来实现保密,使得攻击者不可能猜测到附加字节的内容.而且,每次调用附加字节的内容都在改变,也无法预测。通过检查堆栈的完整性的堆栈保护法是从Synthetix方法演变来的。Synthetix方法通过使用准不变量来确保特定变量的正确性。这些特定的变量的改变是程序实现能预知的,而且只能在满足一定的条件才能可以改变。这种变量我们称为准不变量。Synthetix开发了一些工具用来保护这些变量。攻击者通过缓冲区溢出而产生的改变可以被系统当做非法的动作。在某些极端的情况下,这些准不变量有可能被非法改变,这时需要堆栈保护来提供更完善的保护了。实验的数据表明,堆栈保护对于各种系统的缓冲区溢出攻击都有很好的保护作用.并能保持较好的兼容性和系统性能。分析表明,堆栈保护能有效抵御现在的和将来的基于堆栈的攻击。堆栈保护版本的Red Hat Linux 5.1已经在各种系统上运行了多年,包括个人的笔记本电脑和工作组文件服务器。
3、指针保护
在堆栈保护设计的时候,冲击堆栈构成了缓冲区溢出攻击的常见的一种形式。有人推测存在一种模板来构成这些攻击(在1996年的时候)。从此,很多简单的漏洞被发现,实施和补丁后,很多攻击者开始用更一般的方法实施缓冲区溢出攻击。指针保护是堆钱保护针对这种情况的一个推广。通过在所有的代码指针之后放置附加字节来检验指针在被调用之前的合法性,如果检验失败,会发出报警信号和退出程序的执行,就如同在堆栈保护中的行为一样。这种方案有两点需要注意:
(1)附加字节的定位
附加字节的空间是在被保护的变量被分配的时候分配的,同时在被保护字节初始化过程中被初始化。这样就带来了问题:为了保持兼容性,我们不想改变被保护变量的大小,因此我们不能简单地在变量的结构定义中加入附加字。还有,对各种类型也有不同附加字节数目。
(2)查附加字节
每次程序指针被引用的时候都要检查附加字节的完整性。这个也存在问题因为“从存取器读”在编译器中没有语义,编译器更关心指针的使用,而各种优化算法倾向于从存储器中读人变量.还有随着变量类型的不同,读入的方法也各自不同。到目前为止,只有很少—部分使用非指针变量的攻击能逃脱指针保护的检测。但是,可以通过在编译器上强制对某一变量加入附加字节来实现检测,这时需要程序员自己手工加入相应的保护了。
B. KEIL 中 PUSH
不能这样写,因为0x00不是特殊功能寄存器。要用嵌入式汇编的方法:
#pragma asm
PUSH 0x00
#pragma endasm
然庆扒后,选择要嵌入式汇编的文件,然后右键
C. GCC编译器局部变量地址分配为什么总是从低
原因:GCC的堆栈保护技术—— canary的使用。
使用的原因是为了防止某些溢出的攻击。但是只是溢出时方向发生了改变,并没有起到太大的作用,可能对于传统的一些攻击方法有用。
GCC 中的堆栈保护实现
Stack Guard 是第一个使用 Canaries 探测的堆栈保护实现,它于 1997 年作为 GCC 的一个扩展发布。最初版本的 Stack Guard 使用 0x00000000 作为 canary word。尽管很多人建议把 Stack Guard 纳入 GCC,作为 GCC 的一部分来提供堆栈保护。但实际上,GCC 3.x 没有实现任何的堆栈保护。直到 GCC 4.1 堆栈保护才被加入,并且 GCC4.1 所采用的堆栈保护实现并非 Stack Guard,而是 Stack-smashing Protection(SSP,又称 ProPolice)。
SSP 在 Stack Guard 的基础上进行了改进和提高。它是由 IBM 的工程师 Hiroaki Rtoh 开发并维护的。与 Stack Guard 相比,SSP 保护函数返回地址的同时还保护了栈中的 EBP 等信息。此外,SSP 还有意将局部变量中的数组放在函数栈的高地址,而将其他变量放在低地址。这样就使得通过溢出一个数组来修改其他变量(比如一个函数指针)变得更为困难。
D. 怎么绕过内核堆栈检测
1、使用缓冲区溢出技术,在程序运行期间答贺,绕过内核堆栈检测,修改程序的返回地址,使其指向恶意代码;
2、使用汇编技术,利用汇编指令来操作,绕过内核堆栈检测,修改程清禅派袭野序的返回地址,使其指向恶意代码;
3、使用软件保护技术,如混淆和加壳技术,可以有效的绕过内核堆栈检测,防止程序的返回地址被恶意修改;
4、使用编译器优化技术,可以有效的绕过内核堆栈检测,防止程序的返回地址被恶意修改。
E. 什么叫“缓冲区益出保护”啊我的机器这几天总出现这个问题
缓冲区溢出是当前一些软件存在的最常见的安全隐患之一,通过提供一个恶意的输入黑客可以改变进程的执行流程,缓冲区溢出能够威胁到整个进程,机器,甚至相关的系统领域。如果运行的进程是在权限比较高的用户下面,比如administrator或者本地的系统帐户(Local System Account),那么黑客破坏所导致的损失将会很严重而且将会面临更广泛的潜在危胁。最近时期爆发的一些众所周知的病毒像,红色代码病毒和震荡波蠕虫病毒,都是C/C++代码里存在着缓冲区溢出的结果。
1.什么是缓冲区溢出?
~~~~~~~~~~~~~~~~~~~
buffer overflow,buffer overrun,smash the stack,trash the stack,
scribble the stack, mangle the stack,spam,alias bug,fandango on core,
memory leak,precedence lossage,overrun screw...
指的是一种系统攻击的手段,通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。 造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数。
2.制造缓冲区溢出
~~~~~~~~~~~~~~~~
一个程序在内存中通常分为程序段,数据端和堆栈三部分。程序段里放着程序的机器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。在内存中,它们的位置是:
+------------------+ 内存低端
| 程序段 |
|------------------|
| 数据段 |
|------------------|
| 堆栈 |
+------------------+ 内存高端
当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指令寄存器(IP)中的内容做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP);然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定空间,把SP减去适当的数值。
3.通过缓冲区溢出获得用户SHELL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果在溢出的缓冲区中写入我们想执行的代码,再覆盖返回地址(ret)的内 容,使它指向缓冲区的开头,就可以达到运行其它指令的目的。
低内存端 buffer sfp ret *str 高内存端
<------ [ ][ ][ ][ ]
栈顶 ^ | 栈底
|________________________|
通常,我们想运行的是一个用户shell。
4.利用缓冲区溢出进行的系统攻击
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在那儿放入shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过反复重试缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百上千次,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一长串的NOP,返回地址可以指向这一串NOP中任一位置,执行完NOP指令后程序将激活shell进程。这样就大大增加了猜中的可能性。
三. 缓冲区溢出的保护方法
目前有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响。在3.1中介绍了强制写正确的代码的方法。在3.2中介绍了通过操作系统使得缓冲区不可执行,从而阻止攻击者殖入攻击代码。这种方法有效地阻止了很多缓冲区溢出的攻击,但是攻击者并不一定要殖入攻击代码来实现缓冲区溢出的攻击(参见 2.1节),所以这种方法还是存在很弱点的。在3.3中,我们介绍了利用编译器的边界检查来实现缓冲区的保护。这个方法使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁,但是相对而言代价比较大。在3.4中我们介绍一种间接的方法,这个方法在程序指针失效前进行完整性检查。这样虽然这种方法不能使得所有的缓冲区溢出失效,但它的的确确阻止了绝大多数的缓冲区溢出攻击,而能够逃脱这种方法保护的缓冲区溢出也很难实现。然后在3.5,我们要分析这种保护方法的兼容性和性能优势(与数组边界检查)。
3.1 编写正确的代码
编写正确的代码是一件非常有意义但耗时的工作,特别象编写C语言那种具有容易出错倾向的程序(如:字符串的零结尾),这种风格是由于追求性能而忽视正确性的传统引起的。尽管花了很长的时间使得人们知道了如何编写安全的程序,具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。
最简单的方法就是用grep来搜索源代码中容易产生漏洞的库的调用,比如对strcpy和sprintf的调用,这两个函数都没有检查输入参数的长度。事实上,各个版本C的标准库均有这样的问题存在。
为了寻找一些常见的诸如缓冲区溢出和操作系统竞争条件等漏洞,代码检查小组检查了很多的代码。然而依然有漏网之鱼存在。尽管采用了 strncpy和snprintf这些替代函数来防止缓冲区溢出的发生,但是由于编写代码的问题,仍旧会有这种情况发生。比如lprm程序就是最好的例子,虽然它通过了代码的安全检查,但仍然有缓冲区溢出的问题存在。
为了对付这些问题,人们开发了一些高级的查错工具,如fault injection等。这些工具的目的在于通过人为随机地产生一些缓冲区溢出来寻找代码的安全漏洞。还有一些静态分析工具用于侦测缓冲区溢出的存在。
虽然这些工具帮助程序员开发更安全的程序,但是由于C语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞。所以,侦错技术只能用来减少缓冲区溢出的可能,并不能完全地消除它的存在。除非程序员能保证他的程序万无一失,否则还是要用到以下3.2到3.4部分的内容来保证程序的可靠性能。
3.2 非执行的缓冲区
通过使被攻击程序的数据段地址空间不可执行,从而使得攻击者不可能执行被殖入被攻击程序输入缓冲区的代码,这种技术被称为非执行的缓冲区技术。事实上,很多老的Unix系统都是这样设计的,但是近来的Unix和MS Windows系统由于实现更好的性能和功能,往往在在数据段中动态地放入可执行的代码。所以为了保持程序的兼容性不可能使得所有程序的数据段不可执行。
但是我们可以设定堆栈数据段不可执行,这样就可以最大限度地保证了程序的兼容性。Linux和Solaris都发布了有关这方面的内核补丁。因为几乎没有任何合法的程序会在堆栈中存放代码,这种做法几乎不产生任何兼容性问题,除了在Linux中的两个特例,这时可执行的代码必须被放入堆栈中:
信号传递:
Linux通过向进程堆栈释放代码然后引发中断来执行在堆栈中的代码来实现向进程发送Unix信号。非执行缓冲区的补丁在发送信号的时候是允许缓冲区可执行的。
GCC的在线重用:
研究发现gcc在堆栈区里放置了可执行的代码作为在线重用之用。然而,关闭这个功能并不产生任何问题,只有部分功能似乎不能使用。
非执行堆栈的保护可以有效地对付把代码殖入自动变量的缓冲区溢出攻击,而对于其他形式的攻击则没有效果(参见2.1)。通过引用一个驻留的程序的指针,就可以跳过这种保护措施。其他的攻击可以采用把代码殖入堆或者静态数据段中来跳过保护。
3.3 数组边界检查
殖入代码引起缓冲区溢出是一个方面,扰乱程序的执行流程是另一个方面。不象非执行缓冲区保护,数组边界检查完全放置了缓冲区溢出的产生和攻击。这样,只要数组不能被溢出,溢出攻击也就无从谈起。为了实现数组边界检查,则所有的对数组的读写操作都应当被检查以确保对数组的操作在正确的范围内。最直接的方法是检查所有的数组操作,但是通常可以采用一些优化的技术来减少检查的次数。目前有以下的几种检查方法:
3.3.1 Compaq C 编译器
Compaq公司为Alpha CPU开发的C编译器(在Tru64的Unix平台上是cc,在Alpha Linux平台上是ccc)支持有限度的边界检查(使用-check_bounds参数)。这些限制是:
只有显示的数组引用才被检查,比如“a[3]”会被检查,而“*(a+3)”则不会。
由于所有的C数组在传送的时候是指针传递的,所以传递给函数的的数组不会被检查。
带有危险性的库函数如strcpy不会在编译的时候进行边界检查,即便是指定了边界检查。
由于在C语言中利用指针进行数组操作和传递是如此的频繁,因此这种局限性是非常严重的。通常这种边界检查用来程序的查错,而且不能保证不发生缓冲区溢出的漏洞。
3.3.2 Jones & Kelly: C的数组边界检查
Richard Jones和Paul Kelly开发了一个gcc的补丁,用来实现对C程序完全的数组边界检查。由于没有改变指针的含义,所以被编译的程序和其他的gcc模块具有很好的兼容性。更进一步的是,他们由此从没有指针的表达式中导出了一个“基”指针,然后通过检查这个基指针来侦测表达式的结果是否在容许的范围之内。
当然,这样付出的性能上的代价是巨大的:对于一个频繁使用指针的程序如向量乘法,将由于指针的频繁使用而使速度比本来慢30倍。
这个编译器目前还很不成熟;一些复杂的程序(如elm)还不能在这个上面编译,执行通过。然而在它的一个更新版本之下,它至少能编译执行ssh软件的加密软件包。其实现的性能要下降12倍。
3.3.3 Purify:存储器存取检查
Purify是C程序调试时查看存储器使用的工具而不是专用的安全工具。Purify使用“目标代码插入”技术来检查所有的存储器存取。通过用Purify连接工具连接,可执行代码在执行的时候数组的所有引用来保证其合法性。这样带来的性能上的损失要下降3-5倍。
3.3.4 类型-安全语言
所有的缓冲区溢出漏洞都源于C语言缺乏类型安全。如果只有类型-安全的操作才可以被允许执行,这样就不可能出现对变量的强制操作。如果作为新手,可以推荐使用具有类型-安全的语言如Java和ML。
但是作为Java执行平台的Java虚拟机是C程序,因此通过攻击JVM的一条途径是使JVM的缓冲区溢出。因此在系统中采用缓冲区溢出防卫技术来使用强制类型-安全的语言可以收到意想不到的效果。
3.4 程序指针完整性检查
程序指针完整性检查和边界检查由略微的不同。与防止程序指针被改变不同,程序指针完整性检查在程序指针被引用之前检测到它的改变。因此,即便一个攻击者成功地改变了程序的指针,由于系统事先检测到了指针的改变,因此这个指针将不会被使用。
与数组边界检查相比,这种方法不能解决所有的缓冲区溢出问题;采用其他的缓冲区溢出方法就可以避免这种检测。但是这种方法在性能上有很大的优势,而且在兼容性也很好。
程序完整性检查大体上有三个研究方向。在3.4.1中会介绍Snarskii为FreeBSD开发了一套定制的能通过监测cpu堆栈来确定缓冲区溢出的libc。在3.4.2中会介绍我们自己的堆栈保护方法所开发的一个编译器,它能够在函数调用的时候自动生成完整性检测代码。最后在3.4.3,我们介绍正在开发中的指针保护方法,这种方法类似于堆栈保护,它提供对所有程序指针的完整性的保护。
3.4.1 手写的堆栈监测
Snarskii为FreeBSD开发了一套定制的能通过监测cpu堆栈来确定缓冲区溢出的libc。这个应用完全用手工汇编写的,而且只保护 libc中的当前有效纪录函数。这个应用达到了设计要求,对于基于libc库函数的攻击具有很好的防卫,但是不能防卫其它方式的攻击。
3.4.2 堆栈保护:编译器生成的有效纪录完整性检测
堆栈保护是一种提供程序指针完整性检查的编译器技术,通过检查函数活动纪录中的返回地址来实现。堆栈保护作为gcc的一个小的补丁,在每个函数中,加入了函数建立和销毁的代码。加入的函数建立代码实际上在堆栈中函数返回地址后面加了一些附加的字节。而在函数返回时,首先检查这个附加的字节是否被改动过。如果发生过缓冲区溢出的攻击,那么这种攻击很容易在函数返回前被检测到。
但是,如果攻击者预见到这些附加字节的存在,并且能在溢出过程中同样地制造他们,那么他就能成功地跳过堆栈保护的检测。通常,我们有如下的两种方案对付这种欺骗:
终止符号:
利用在C语言中的终止符号如0(null),CR,LF,-1(EOF)等不能在常用的字符串函数中使用,因为这些函数一旦遇到这些终止符号,就结束函数过程了。
随机符号:
利用一个在函数调用时产生的一个32位的随机数来实现保密,使得攻击者不可能猜测到附加字节的内容。而且,每次调用,附加字节的内容都在改变,也无法预测。
通过检查堆栈的完整性的堆栈保护法是从Synthetix方法演变来的。Synthetix方法通过使用准不变量来确保特定变量的正确性。这些特定的变量的改变是程序实现能预知的,而且只能在满足一定的条件才能可以改变。这种变量我们称为准变量。Synthetix开发了一些工具用来保护这些变量。
攻击者通过缓冲区溢出而产生的改变可以被系统当做非法的动作。在某些极端的情况下,这些准不变量有可能被非法改变,这是就需要堆栈保护来提供更完善的保护了。
实验的数据表明,堆栈保护对于各种系统的缓冲区溢出攻击都有很好的保护作用,并能保持较好的兼容性和系统性能。随后,我们用堆栈保护的方法重新构造了一个完整的Linux系统(Red Hat 5.1)。然后我们用XFree86-3.3.2-5和lsof的漏洞对此进行了攻击,结果表明,这个系统有效地抵御了这些攻击。这些分析表明,堆栈保护能有效抵御现在的和将来的基于堆栈的攻击。
堆栈保护版本的Red Hat Linux 5.1已经在各种系统上运行了多年,包括个人的笔记本电脑和工作组文件服务器。从我们的Web服务器上可以得到这个版本,而且在我们的邮件列表里已经有了 55个成员。出了仅有的一次例外,这个系统和本来的系统工作完全一样,这表明堆栈保护并不对系统的兼容性构成很大的影响。
我们已经用各种性能测试来评测堆栈保护的性能。Mircobenchmarks的结果表明在函数的调用,堆栈保护中增加了系统的
开销。而在网络的测试中(需要用到堆栈保护的地方),则表明这种开销不是很大。
我们的第一个测试对象是SSH,它提供了极强的加密和认证,用来替代Berkeley的r系列指令。SSH使用了软件加密,因此系统的占用的带宽不大,我们用网络间复制一个大的文件来测试带宽:
scp bigsource localhost:bigdest
测试结果表明:堆栈保护几乎不影响SSH的网络吞吐性能。
第二个测试使用了Apache Web服务器。如果这种服务器存在基于堆栈的攻击,那么攻击者就可以轻易地取得Web服务器的控制权,允许攻击者阅读隐秘的内容和肆意篡改主页的内容。同时,Web服务器也是对性能和带宽要求较高的一个服务器部件。
我们用WebStone对带有和不带堆栈保护的Apache Web服务器进行了测试。
和SSH一样,他们的性能几乎没有区别。在客户数目较少的情况下,带有保护的服务器性能比不带保护的略微好些,在客户端数目多的时候,不带保护的性能好些。在最坏的情况下,带保护的服务器比不带保护的要差8%的连接性能,而在平均延时上保持优势。象以前一样,我们把这些归结为噪声的影响。因此,我们的结论是:堆栈保护对Web服务器系统性能没有重大的影响。
3.4.3 指针保护:编译器生成程序指针完整性检查
在堆栈保护设计的时候,冲击堆栈构成了缓冲区溢出攻击的常见的一种形式。有人推测存在一种模板来构成这些攻击(在1996年的时候)。从此,很多简单的漏洞被发现,实施和补丁了,很多攻击者开始用在第二部分中描述的更一般的方法实施缓冲区溢出攻击。
指针保护是堆栈保护针对这种情况的一个推广。通过在所有的代码指针之后放置附加字节来检验指针在被调用之前的合法性。如果检验失败,会发出报警信号和退出程序的执行,就如同在堆栈保护中的行为一样。这种方案有两点需要注意:
附加字节的定位:
附加字节的空间是在被保护的变量被分配的时候分配的,同时在被保护字节初始化过程中被初始化。这样就带来了问题;为了保持兼容性,我们不想改变被保护变量的大小,因此我们不能简单地在变量的结构定义中加入附加字。还有,对各种类型也有不同附加字节数目。
检查附加字节:
每次程序指针被引用的时候都要检查附加字节的完整性。这个也存在问题;因为“从存取器读”在编译器中没有语义;编译器更关心指针的使用,而各种的优化算法倾向于从存储器中读入变量。
还有随着不同类型的变量,读入的方法也各自不同。
我们已经开发了指针保护的一个原型(还是基于gcc的),通过附加字节来保护静态分配的函数指针,但不适用于结构和数组类型。这个计划还远没有完成。一旦这个项目完成了,那么用它和堆栈保护构成的可执行代码将不会受到缓冲区溢出的攻击了。
目前为止,只有很少一部分使用非指针变量的攻击能逃脱指针保护的检测。但是,可以通过在编译器上强制对某一变量加入附加字节来实现检测,这时需要程序员自己手工加入相应的保护了。
3.5 兼容性和性能的考虑
程序指针完整性检查与边界检查相比,并不能防止所有的缓冲区溢出问题。然而在执行的性能和兼容性上具有相当的优势:
性能:
边界检查必须在每个数组元素操作时完成一次检查。相比之下,程序指针检查只在被引用的时候实现检查。无论在C还是在C++中,这种花在程序指针引用上的开销始终比数组的指针引用小。
应用效能:
边界检查最难实现之处在于在C语言中,很能确定数组的边界。这是由于在C中,数组的概念和通用指针的混用造成的。由于一个指针是一个独立的对象,没有与特定的边界条件关联,只有一个系统的机器字来存储它,而标识边界信息的数据却没有存放。因此需要特殊的方法来恢复这些信息;数组的引用将不在是一个简单的指针,而是一个对缓冲区描述的指针组。
与现有代码的兼容性:
一些边界检查方法为了与现有的代码保持兼容而在系统的性能上得到了损失。而另一些则用别的方法达到目的。这样就打破的传统的C的转换规则,转而产生了一类新的C编译器,只能编译C的一个子集,有的还不能使用指针或者需要别的改变。
四 有效的组合
在这里我们研究、比较在第二部分描述的各种漏洞攻击和在第三部分描述的防卫方法,以此来确定何种组合能完全消除缓冲区溢出问题。但是我们没有把边界检查计算在内,因为它能有效地防止所有的缓冲区溢出,但是所需的开销也是惊人的。
最普通的缓冲区溢出形式是攻击活动纪录然后在堆栈中殖入代码。这种类型的攻击在1996年中有很多纪录。而非执行堆栈和堆栈保护的方法都可以有效防卫这种攻击。非执行堆栈可以防卫所有把代码殖入堆栈的攻击方法,堆栈保护可以防卫所有改变活动纪录的方法。这两种方法相互兼容,可以同时防卫多种可能的攻击。
剩下的攻击基本上可以用指针保护的方法来防卫,但是在某些特殊的场合需要用手工来实现指针保护。全自动的指针保护需要对每个变量加入附加字节,这样使得指针边界检查在某些情况下具有优势。
最为有趣的是,第一个缓冲区溢出漏洞--Morris蠕虫使用了现今所有方法都无法有效防卫的方法,但是却很少有人用到,也许是这种方法过于复杂的缘故吧。
五. 结论
在本文中,我们详细描述和分析了缓冲区溢出的攻击和防卫方法。由于这种攻击是目前常见的攻击手段,所以进行这个方面的研究工作是有意义和成效的。研究的结果表明,堆栈保护方法和非执行缓冲区方法对于当前绝大多数的攻击都能有效地防御,指针保护的方法可以对剩下的攻击进行有效的防御。最后声明的是对于Morris蠕虫的攻击,迄今还没有有效的防御手段
F. 关于protues中仿所用的程序,用c语言编的程序和用汇编语言编写的程序,哪个更有更有优势
对于问题我想说以下几点:
1.Proteus仿真时需要加载的是经过汇编(由汇编语言编写的源程序文件时)或编译与汇编(由C或其他高级语言编写的源程序文件要先编译成汇编语言告则,再汇编成hex、bin文件)的hex文件(称为目标文件)。
2.Proteus与C语言没有必然关系,要的只是hex文件,可以经过编译生成hex文件的语言一大堆。
3.学好Proteus没有成就迟者感,Proteus只适合一些小的设计,上不了正堂,而且Proteus仿真过的,实物不一定行的通,Proteus只适合理论上的初步验证,当然学了还是多多益善。
4.汇编语言比C语言等在生成hex文件时形成指令的效率更高,比如,汇编语言几乎每一句都有用,都会生成有效的二进制指令(除伪指令外),而C语言在生成二进制指令时有好多多余的语句(比如函数调用时的不必要的入堆栈保护)。
5.汇编简练,不易移植,不适合大一点的工程;C的编译效率低,但是可移植性好,总之各有优势,你现在看的应该是一些学习性的单片机程序,当然是小程序,经常袜旦棚遇到汇编就正常。
6.建议既要会C也要会汇编,这样在以后用C等编写的大工程中调试程序时,也可以知道某条语句其实现的原理,以便更好发现问题,解决问题。
G. dns溢出攻击的条件
缓冲溢出漏洞危害不容忽视
当您在家里通过宽带体验冲网的极速感觉时,当您在办公室通过即时通讯软件与远在大洋彼岸的客户商业交流时,是否想过您的系统是否坚如磐石,是否被网络另一端心怀叵测的黑客或脚本少年盯上。或许有人认为装上了防火墙、防病毒就可以安全无忧了,遗憾的是这些仅解决了部分问题,而系统本身的漏洞而遭受攻击却极难防范。我们讨论一种漏洞攻击方式-----远程缓冲溢出攻击,因为这种攻击可以使得一个匿名的Internet用户有机会获得一台主机的册档穗部分或全部的控制权,危害很大,也是最普遍的漏洞,大约80%的安全事件与缓冲区溢出有关。如最近微软于7月17日公开的Windows安全漏洞“RPC接口缓存溢出可能导致运行任意代码(MS03-026)”,其影响范围和危害不亚于“SQL Slammer”,被认为至今微软最严重缓冲溢出漏洞。
我们知道,缓冲区溢出漏洞是一种软件中边境条件、函数指针等设计不当的造成地址空间错误州卜,它的原理是:向一个有限空间的缓冲区中拷贝了过长的字符串,带来了两种后果,一是过长的字符串覆盖了相临的存储单元而造成程序瘫痪,甚至造成宕机、系统或进程重启等;二是利用漏洞可以让攻击者运行恶意代码,执行任意指令,甚至获得超级权限等。
早在1988年,美国康奈尔大学的计算机科学系研究生,23岁的莫里斯(Morris)利用了UNIX fingered程序不限制输入长度的漏洞使缓冲器溢出。Morris又写了一段程序使他的恶意程序能以root(根)身份执行,并传播到其他机器上,结果造成6000台Internet上的服务器瘫痪,占当时总数的10%。 “SQL Slammer”蠕虫王的发作原理,就是利用未及时更新补丁的MS SQL Server数据库缓冲溢出漏洞。采用不正确的方式将数据发到MS Sql Server的监听端口,这个错误可以引起缓冲溢出攻击。目前新出现的MSBLAST病毒正是利用了微软关于RPC 接口中远程任意可执行代码漏洞,“中招”的机器会反复重启,或者拷贝、粘贴功能不工作等现象。事实上,如果成功利用缓冲漏洞,攻击者就有可能获得对远程计算机的完全控制,并以本地系统权限执行任意指令,如安装程序、查看或更改、删除数据、格式化硬盘等,危害性不言而喻。
通常缓冲区溢出攻击都是一次完成攻击代码植入和程序转向攻击代码两种功能。如通常攻击者将目标定为具有溢出漏洞的自动变量,然后向程序传递超长的字符串,进而引发缓冲区溢出。然后这段精巧设计的攻击代码以一定的权限运行漏洞程序,既而获得目标主机的控制权。这种攻击手段屡次得逞主要利用了C程序中数组边境条件、函数指针等设计不当的漏洞,大多数Windows、Linux、Unix、数据库系列的开发都依赖于C语言,而C的缺点是缺乏类型安全,所以缓冲区溢出攻击成为操作系统、数据库等大型应用程序最普遍的漏洞公告之一。
面对漏洞威胁 积极应对防范
在1998年CERT的13份建议中,有9份是是与缓冲区溢出有关的,在1999年,蠢芹至少有半数的建议是和缓冲区溢出有关的。2003年7月份公开的10大安全漏洞通告至少有五项属于缓冲区溢出漏洞。可见完全避免这种漏洞造成的安全威胁是不可能的,但我们可以构建完善的防范体系来降低冲区溢出攻击的威胁。
作为编程人员可以使用具有类型-安全的语言 Java以避免C的缺陷;如果在C开发环境下应避免使用Gets、Sprintf等未限定边境溢出的危险函数;此可使用检查堆栈溢出的编译器(如Compaq C编译器)等。另外一个有效的办法是采用非执行堆栈和堆栈保护的方法,最后在产品发布前仍需要仔细检查程序溢出情况,将威胁降至最低。作为普通用户或系统管理员应该做些什么呢?首先确保及时为自己的操作系统和应用程序更新Patch,以修补公开的漏洞,其次应减少不必要的开放服务端口。举一个例子,你屋子里的门和窗户越少,入侵者进入的方式就越少。
值得关注的是,通常传统安全工具如防火墙对这种攻击方式无能为力,因为攻击者传输的数据分组并无异常特征,没有任何欺骗。另外可以用来实施缓存器溢出攻击的字串非常多样化,无法与正常数据有效进行区分。缓冲器溢出攻击不是一种窃密和欺骗的手段,而是从计算机系统的最底层发起攻击,因此在它的攻击下系统的身份验证和访问权限等安全策略形同虚设。
我们知道,单纯依赖部署安全产品如防火墙、IDS等仅能构筑静态被动的防御体系,所以还应注重P2DR模型的作用,即增加Policy(安全策略)、Protection(防护)、Detection(检测)和Response(响应)的能力,按照公式Pt>Dt+Rt,和Et=Dt+Rt(Pt=0),应提高系统的防护时间Pt,降低检测时间Dt和响应时间Rt。国内着名安全公司天行网安秉承安全视为一个过程,而非产品的技术理念,“全程网络安全服务”应运而生,其核心内容包括系统漏洞分析、安全风险评估、方案咨询设计、状态跟踪与安全监控、事件紧急响应和安全修复等部分。“全程网络安全服务”不替代用户现存的任何安全工具,而是充分发挥安全审计和管理平台的作用,构成主动的动态防御体系,充分发挥安全工具的作用,降低威胁并保证网络的可持续健康运行。安全专家小组Security Force与安全专家、民间安全组织具有密切关系,他们具备精湛的技术和丰富的经验,可以针对每一个用户的特定需求和网络环境定制服务策略和服务内容,从而为用户构建安全可信、可持续运行的网络应用平台。
H. 程序设计中,堆和栈比较重要。栈存取速度大于堆,而且编译器可以修改栈大小,这个值可以随意设置吗
学习电脑编程多年了,在程序设计方面也算有一番见解,希望这些经验能对题主有所帮助。
现在堆栈通常默认为8M,对吧?事实上,如果你不滥用递归,或者alloca / vla,大多数时候,就足够了。即使它不超过内核限制,通常也不适合驱动大型的,或者它会影响并发进程的数量。并且过程启动时间也应该稍微减少。当然,如果是专用的服务器系统,问题就不严重了。
I. [gdb]函数堆栈乱掉的解决办法 [转]
程序core掉,要去debug,但是函数堆栈乱掉了,很恶心.....经过Google/wiki一番,找到两种解决办法.
x86ManualBacktrace
This tutorial will show you how to manually rebuild a backtrace with GDB on x86 using the stack frame pointer and current instruction pointer.
Consider the following gdb backtrace:
It's pretty clear that this is corrupted, evidence the following field:
Get the register information for the process:
On x86:
Stack Frame Layout for x86:
Color Key:blue
Using the current stack frame address from %ebp mp the stack above it:
Using this stack mp we know that the stack frame address in the %ebp register is the stack frame for the current instruction in the %eip register (unless this was a leaf function which didn't stack a frame but that's irrelevant for this discussion). Using the %ebp and %eip registers as a starting point we can build the first line in our backtrace rebuild:
When program control branches to a new function a stack frame is stacked and the callee function's last address is stored at the new frame's address %ebp + 4 (i.e. the last memory address in the callee's stack frame which is +4 from the current stack frame at %ebp). In order to get the caller's stack frame and instruction pointer just look at %ebp and %ebp+4:
In the stack find the memory at the stack frame in %ebp and the one at %ebp+4 (which will hold the callee's instruction pointer).
So using the address at 0xbf9ef358 and 0xbf9ef35c which is 0xbf9ef388 and 0x00d94cf7 continue to build our list:
Continue by looking at the next stack frame address 0xbf9ef388:
Keep doing this until we have a full back trace. You'll know you've reached the bottom of the stack when the previous stack frame pointer is 0x00000000.
Here's a complete manually rebuilt stack frame:
amd64下面,无非就是寄存器变成rbp,字长增加了一倍.当然这边选择了手动寻找函数返回地址,然后info symbol打印出函数名,其实还可以通过gdb格式化来直接打印函数名:
gdb>x/128agrbp内的内容
所以手动还原的办法就变得很简单:
gdb>info reg rbp*x86换成info reg ebp
gdb>x/128ag rbp内的内容 *x86换成 x/128aw ebp的内容
这样就能看到函数栈.如果你想解析参数是啥,也是可以的,只是比较麻烦,苦力活儿....想解析参数,就要知道栈的布局,可以参考这篇文章: http://blog.csdn.net/liigo/archive/2006/12/23/1456938.aspx
昨天和 海洋 一块研究了下函数调用栈,顺便写两句。不足或错误之处请包涵!
理解调用栈最重要的两点是:栈的结构,EBP寄存器的作用。
首先要认识到这样两个事实:
1、一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作。
2、几乎所有本地编译器都会在每个函数体之前插入类似如下指令:PUSH EBP; MOV EBP ESP;
即,在程序执行到一个函数的真正函数体时,已经有以下数据顺序入栈:参数,返回地址,EBP。由此得到类似如下的栈结构(参数入栈顺序跟调用方式有关,这里以 C语言 默认的CDECL为例):
“PUSH EBP”“MOV EBP ESP”这两条指令实在大有深意:首先将EBP入栈,然后将栈顶指针ESP赋值给EBP。“MOV EBP ESP”这条指令表面上看是用ESP把EBP原来的值覆盖了,其实不然——因为给EBP赋值之前,原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,而该地址处又存储着上一层函数调用时的EBP值!
一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层EBP值。
由于EBP中的地址处总是“上一层函数调用时的EBP值”,而在每一层函数调用中,都能通过当时的EBP值“向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值”。如此形成递归,直至到达栈底。这就是函数调用栈。
编译器对EBP的使用实在太精妙了。
从当前EBP出发,逐层向上找到所有的EBP是非常容易的:
这个办法比较简单,很容易实践,但是有一个前提,如果栈的内容被冲刷干净了,你连毛都看不到(事实就是这样).所以你需要开始栈保护...至少你还能找到栈顶的函数...
gcc有参数: -fstack-protector 和 -fstack-protector-all,强烈建议开启....
**********************************************************************/
用打印方法调试
在客户项目那里混了半年,发现Top的客户确实是比我们牛逼。先说说调试的方法。
客户那边不依赖于GDB调试,因为他们可能觉得GDB依赖于系统 实现,不利于移植吧,所以客户的程序完全是依赖于打印调试的。这点很佩服他们的软件规划能力和项目管理,实现能力。说老实话,如果换了一家中国公司,每人 一个调试方法,要follow 一个rule是很不容易的。
完善的调试菜单。调试菜单并不难实现,只是一个打印和字符接受的函数。在其中控制是开放某些打印信息。
在每个模块中加上仔细规划打印输出,根据需求分成不同的基本。最好情况是在最高打印级别中可以可以发现所有的问题。打印级别可以很方便的动态控制。
函数调用LOG
如果能定位发生问题的模块,可以在该模块的在每个函数的调用入口加上打印一个函数名字+Enter,在返回处加上一个函数名字+Exit。对于每个模块用一个打印开关控制是否打印Trace信息。在调试菜单中控制这个打印开关。
如果懒得加打印语句,可以利用gcc 的-finstrument-functions 选项来快速的加入调试信息。-finstrumnet-function会是的编译器在函数调用的开始和退出处调用
可以利用这两个函数来跟踪函数调用的过程。
在 实现这两个函数时要加入 attribute ((no_instrument_function));以避免编译器再调用这两个函数的时候也调用__cyg_profile_func_enter 和 __cyg_profile_func_exit 而造成循环调用。
可以用dladdr()来获得this_fn的文件和函数名。code如下:
// 由于dladdr是GNU扩展,不是dl的标准函数,因此在这句话必须加在文件的开始处
关于打印堆栈。可以用
该方法需要编译器支持。
但是需要在编译的时候加上-rdynamic 否则只能输出在内存中的绝对地址。
在没有-rdynamic的时候,关于如何找到动态库的运行时地址还需要研究。
可以在系统运行的时候发送SIGSEGV给应用程序,产生当前进程的Coremp来获取动态库中函数的运行是地址。
用GDB获取backtrace的方法(在有-g选项的时候可以看到,不需要-rdynamic):
list <*address>
在没有-g的时候,又该如何呢?