Linux堆栈溢出的经典问题

时间:2022-07-19 15:35:00


声明:本文章只是我学习的一个记录,有关引用的地方,我都会给出链接的。如果侵犯作者权益,请通知我及时删除。


看了一下,Linux一站式学习的函数调用 ,搞的我热血沸腾,立马找你一下这方面的资料看看。终于看到了一个有关Linux堆栈溢出的文章,就拿来试试手了。参考 我的学习linux笔记(一)堆栈  

下面来贴一下我的代码吧。基本和上篇文章一样的。

[c-sharp] view plaincopy
  1. #include<stdio.h>  
  2. void attack()  
  3. {  
  4. int attack=1;  
  5. printf("hi,attacked!/n");  
  6. }  
  7. void yaya()  
  8. {  
  9.  int yaya=1;  
  10. printf("hi,yaya is my wife/n");  
  11. }  
  12. void foo()  
  13. {  
  14.  int c_foo=1;  
  15.   *(& c_foo +2)=(int)attack;  
  16. }  
  17. void main(){  
  18. int a_main=1;  
  19. i=(int)yaya;  
  20. foo();  
  21. }   

BTW,说明一下,我的平台是redhat 9,2.4的核。

直接上图吧:

先来看一下各个函数的反汇编:

main函数的:

Linux堆栈溢出的经典问题

yaya函数的:

Linux堆栈溢出的经典问题

attack函数的:

Linux堆栈溢出的经典问题

foo函数的哦:

Linux堆栈溢出的经典问题

好了,下面一一细说了:

函数main: *(& a_main -1)=(int)yaya;这条语句非常关键,因为 (& a_main -1)地址换成了yaya函数的入口地址,那么 (& a_main -1)原来的是什么呢?

请看图:

Linux堆栈溢出的经典问题

我们知道,GCC指针都是4字节对齐的,那么 (& a_main -1)就是地址0xbfffe2d0,里面什么也没有。只是GCC为了16字节对齐原则而空着的。具体可见:X86汇编语言学习手记

顺便说一下, 在执行程序时,操作系统为进程分配一块栈空间来保存函数栈帧,在每个函数的栈帧中,ebp 指向栈底,而esp 指向栈顶,在x86平台上这个栈是从高地址向低地址增长的,我们知道每次调用一个函数都要分配一个栈帧来保存参数和局部变量。( Linux一站式学习的函数调用 

下面执行foo函数。

foo函数 : 关键在*(& c_foo +2)=(int)attack;

它的意思就是  来0xbfffe2cc存放的是main返回的地址0x0804839f,但是改为attack函数的入口地址0x08048328。 这样就会导致main函数返回时,跑到attack函数继续执行。

Linux堆栈溢出的经典问题

好了,foo函数执行完了,ESP依次出栈,就到了 attack函数的入口地址0x08048328。

Linux堆栈溢出的经典问题

就attack函数执行完毕以后,此时的ESP就为 地址0xbfffe2d0,回想一下,这 地址0xbfffe2d0里面是什么?

对了,就是yaya函数的入口地址。

那么接下来,就是执行yaya函数了。

那么yaya函数执行完以后,接下来,该怎么办么?

由于main函数丢失了返回地址,导致main函数无法返回,GCC就出现段错误!

 

 

总结:

1 从上面一个小小的例子,就可以看到,Linux堆栈的分配情况,由于可以看出,如果对内存指针进行不小心操作,很可能会导致程序崩溃,甚至会导致溢出攻击。

2 至于 0xbfffe2d0为什么会是空的,ESP为什么要减0x8,那是因为: 至于为什么会相差 4 字节,那是因为对于用户程序的入口函数,即: main()  gcc 一般作 16 bytes 对齐。其它函数,不作 16 bytes 对齐。

3 最后参考的文献,在什么都有链接,下面就不一一列举了。

最后上个堆栈效果图吧:

Linux堆栈溢出的经典问题

4 最后有人要问了,这样的main函数无法正常返回,那么如何修改使得main函数可以正常返回呢?

其实很简单,就是在ESP指针处指向main函数返回的地址,就OK了。

具体,参见上面的堆栈示意图,可以把存储a_main的地址修改为main函数返回的地址。

代码见下:

[c-sharp] view plaincopy
  1. void foo()  
  2. {  
  3.  int c_foo=1;  
  4. int main_ret=(int)*(& c_foo +2);  
  5.   *(& c_foo +2)=(int)attack;  
  6. *(& c_foo +4)= (int) main_ret;  
  7. }  

这样不会返回段错误了,函数也都执行了。