分类: C/C++
2012-09-10 18:52:49
C对于数组引用不进行任何边界检查,并且局部变量和一些状态信息都存放在栈中,这就可能引起严重的程序错误,因为这将会引起对不属于自己范围之内的一些数据的读写。一种特别常见的状态破坏称为缓冲区溢出,下面举个常见的例子,用一个数组存储字符串,但是字符串的长度超出了数组大小。在这里我们故意定义了一个只有8个元素的数组,在调用gets()函数时没有超出数组大小的提醒。这样超出数组大小的部分就会存放到临近数组空间的存储区。要是在这些临近空间存放了其它的数据的话就会被覆盖,我们可以将上面的C程序反汇编一下,看看在这些操作在栈中是如何实现的。点击(此处)折叠或打开
- char *gets(char *s)
- {
- int c;
- char*dest = s;
- int gotchar=0;
- while((c=getchar())!='\n' && c!=EOF)
- {
- *dest++ = c;
- gotchar++;
- }
- *dest = '\0';
- if(c==EOF && !gotchar)
- return NULL
- return s;
- }
- int hello()
- {
- a[8];
- gets(a);
- return 0;
- }
下面这段代码就是反汇编过后从中挑选出来的比较重要的几句代码:程序在将ebp于ebx存储到栈中后就开辟了一个20个字节的空间(第5行代码),然后第6行代码的意思是开辟数组的存储空间(开辟了8个字节,包括字符串结尾的null),首地址存放在ebx寄存器中。下面开始调用gets()函数。
- hello:
- pushl %ebp
- movl %esp,%ebp
- pushl %ebx
- subl $20,%esp
- leal -12(%ebp), %ebx
- movl %ebx,(%esp)
- call gets
- addl $20, %esp
- popl %ebx
- popl %ebp
- ret
可能看代码还不是很清楚,下面的是hello函数的帧栈示意图:
当我们输入的字符数不超过7个时,能够存放在a开辟的空间里,当超过时就会覆盖ebx中的甚至是ebp和返回地址的信息,破坏是积累的,随着输入字符数量的增加,破坏的状态越来越多。
如果破坏了返回地址的数据,则ret指令会使程序跳转到完全意想不到的地方。
缓冲区溢出攻击:
通常,输入给程序一个字符串,这个字符串包含一些可执行的代码的字节编码(攻击代码),还有一些就是会用一个指向攻击代码的指针覆盖返回地址,这样,当执行ret指令时程序就会跳转到攻击代码。