ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

初探缓冲区溢出

2021-12-11 17:31:49  阅读:218  来源: 互联网

标签:返回 函数 堆栈 地址 ebp 初探 缓冲区 溢出


初探缓冲区溢出

前言

理解此文章需要知道一些计算机工作原理,也就是堆栈相关的,我之前写了篇博客介绍了一些汇编与反汇编的基础知识,可参考汇编与反汇编入门-X86 AT&T汇编

一些基础知识

什么是缓冲区

缓冲区是一块连续的计算机内存区域,可保存相同数据类型的多个实例。缓
冲区可以是堆栈(自动变量)、堆(动态内存)和静态数据区(全局或静态)。在C/C++语言中,通常使用字符数组和malloc/new之类内存分配函数实现缓冲区。

什么叫缓冲区溢出

溢出指数据被添加到分配给该缓冲区的内存块之外。比如我们的这个缓冲区
只有5,你分配了长度为6的数据,当然就溢出了。栈帧结构的引入为高级语言中实现函数或过程调用提供直接的硬件支持,但由于将函数返回地址这样的重要数据保存在程序员可见的堆栈中,因此也给系统安全带来隐患。若将函数返回地址修改为指向一段精心安排的恶意代码,则可达到危害系统安全的目的。此外,堆栈的正确恢复依赖于压栈的EBP值的正确性,但EBP域邻近局部变量,若编程中有意无意地通过局部变量的地址偏移窜改EBP值,则程序的行为将变得非常危险。(参考自缓冲区溢出详解)

函数栈

在这里插入图片描述
这是一个函数栈,在调用函数时,首先函数的参数入栈,函数调用为了返回
现场会有返回地址,即返回地址入栈,但是如果buffer中的数据大于了buffer的容量,比如(入栈是向下减4字节,出栈是向上加4字节):
在这里插入图片描述
这里的缓冲区只能容纳8个元素的数组但是假如使用strcpy函数要赋值10个元素的数组给这个缓冲区,当然会溢出,可以看到是往高地址写入数据,所以可能会覆盖返回地址,这里的返回地址是很危险的,比如将返回地址改为JMP ESP语句的地址(ESP是栈顶指针,EBP是栈基指针),并将ShellCode放入参数开始处,则攻击者可能通过这段ShellCode获得root权限。以上可以看出填充Buffer的顺序和函数栈入栈顺序是相反的。我们常用的C语言是没有边界检查机制的,很容易引起缓冲区溢出。

注:char *strcpy(char *dest, const char *src) 意思是将src字符串复制给src字符串。

使用OllyDbg对程序进行分析

分析的代码

#include <stdio.h>
#include <string.h>

void cpyFrom(char*);

int main(){
   char* srcStr="123aaabbbcccdddeeefff";
   cpyFrom(srcStr);
   printf("welcome back!\n");
   return 0;
}
void cpyFrom(char* src){
   char dstStr[8];
   strcpy(dstStr,src);
   printf("%s\n",dstStr);
}

很明显,如果没有发生缓冲区溢出一定是可以打印welcome back且能正常退出的。

OllyDbg简介

OllyDbg是一个逆向分析工具,是免费的,是一个新的动态追踪工具,是将IDA与SoftICE结合起来的产物,Ring 3级调试器,非常容易上手,另外由于OllyDbg是一个通用的32位汇编分析调试器且操作界面非常直观简单,己代替SoftICE成为当今最为流行的调试解密工具了。同时OllyDbg还支持插件扩展功能,是目前最强大的调试工具。
在这里插入图片描述
请注意这是一个32位汇编分析调试器,可以看到反汇编代码,寄存器,十六进制和堆栈的情况。

分析过程

在DevC++中让其变成exe文件

在这里插入图片描述

运行那里要是32bit-release这点注意。点击Run后生成exe文件,将这个exe文件导入OllyDbg。

使用OllyDbg具体分析

在这里插入图片描述
那个黄色那一行就是函数的入口也就是main函数,如果你不放心,可以点击最上边的视图,可执行模块
在这里插入图片描述
点击第一个就好。
在这里插入图片描述
可以看到地址跳到了004014E0。可以知道缓冲区溢出一定是cpyFrom函数中的strcpy函数。

char dstStr[8];
strcpy(dstStr,src);

src复制到dstStr,但是dstStr只有8个字节空间,src可以覆盖掉dstStr造成缓冲区溢出。
在这里插入图片描述

在0040150E这个地址可以看到有一个ASCII码,所以我猜测0040150E-0040151A对应于C语言中的语句

char* srcStr="123aaabbbcccdddeeefff";

在0040151D地址处执行了call 00401535,根据C语言代码的顺序,猜测调用了cpyFrom函数,点击这行变为黄色,然后回车就到了cpyFrom函数的第一个语句。要设置断点才可以继续追踪,所以可以在00401535地址处点击右键然后断点-切换(或者快捷键F2,如果不行就Windows Fn+F2),这个地址变为红色表示设置断点成功。
在这里插入图片描述
然后在上面点击步入执行(快捷键为F7)进入函数体,接下来单步执行就好(快捷键F8),接下来来看看堆栈和寄存器变换情况。

  1. 在地址00101538处,语句为sub esp,28(特地解释一下,最左边那些地址我的理解是eip的地址,他们之间差值是不定的,不一定是4)
    在这里插入图片描述

这一句执行完了push ebp和mov ebp,esp。现在esp和ebp的值应该是一样的,观察右边寄存器那一栏可以看到都是0061FE78。堆栈情况如下:
在这里插入图片描述

2.执行到地址为0040153B,执行完语句sub esp,28,注意这里的28是十六进制化为二进制是0010 1000化为十进制是40。也就是向下移动了40个字节。寄存器esp的值由之前的0061FE78变为了0061FE50,堆栈由之前的0061FE78–0061FE7C变为了0061FE50–0061FE7C。
在这里插入图片描述

在这里插入图片描述
通过堆栈情况(最左边)可以看出地址之间差的是4个字节(32位汇编),同时这是往高地址走的。所以对于函数堆栈最上面是缓冲区,接着说ebp,函数返回地址,最后是参数。在地址0061FE7C处旁边有一点从返回demo2.00401535自demo2.00401522,这就是函数返回地址。
在这里插入图片描述
3.地址0040153B的汇编语句mov eax,dword ptr [ebp+8]我猜测等价于C语言
char dstStr[8];
在这里插入图片描述
下面这一段相当于是执行strcpy(dstStr,src); 可以看到函数参数入栈是反序的,比如这里src先入栈,dstStr再入栈,要调用strcpy函数。
在这里插入图片描述
4.以下来看看到call strcpy堆栈的情况(请主要看堆栈)
首先执行完0040153E地址处语句,情况如下:
在这里插入图片描述
在这里插入图片描述

执行完0040153E语句后堆栈中0061FE54地址处出现了ASCII码。
然后执行完00401542处语句,情况如下:
在这里插入图片描述
在这里插入图片描述

eax的值变化了。
执行完00401535地址处语句,情况如下:ng
在这里插入图片描述

  1. 调用strcpy函数
    在这里插入图片描述

在这里插入图片描述
这里发现执行了strcpy后返回地址被覆盖,之前在地址0061FE7C处的值是00401532也就是在cpyFrom函数return后会回到main函数的这个地址继续执行。但是现在值变为00400066。很明显继续执行已经回不去了。即发生了溢出,有意思的是从0061FE68地址的值是61333231(61是十六进制,化为二进制是0110 0001化为十进制是97,为a的ASCII码即61是a,同理33为3,32为2,31为1)对应为a321,0061FE6C的值为62626161(bbaa),00617E70的值为63636362(cccb),00617E74值为65646464(eddd),其实这一段(0061FE68-0061FE78)这16个字节的空间为缓冲区,0061FE78这个地址围为ebp,值被覆盖为66666565(ffee),在0061FE7C存放的是返回地址,之前存放的00401522因为f与\0的覆盖(字符串最后会有个\0结尾不要忘了)变为00400066,最后四位被覆盖。
上面文字可能不好理解,我画个堆栈图让大家理解下:
在这里插入图片描述

  1. 执行lea eax,[ebp-10]

在这里插入图片描述
在这里插入图片描述

  1. 执行mov dword ptr [esp],eax

在这里插入图片描述
在这里插入图片描述

  1. 执行call puts也就是printf函数,eax的值变为0
    在这里插入图片描述
    在这里插入图片描述

这里发现成功执行了printf,有输出
在这里插入图片描述

  1. 执行leave,之前学过leave相当于esp回退到ebp的位置然后抛掉ebp回到之前的ebp。

在这里插入图片描述
在这里插入图片描述

  1. 最后执行ret,发现已经回不去了,因为函数的返回地址已经被字符串覆盖了。
    在这里插入图片描述
    在这里插入图片描述
    因为回不去所以无法返回Welcome back!

调试程序2

这里把字符串长度变短,还是之前那段程序

 char* srcStr="123aaabbbcccdddeeef";

调试程序2分析

直接调到调用完strcpy函数,情况如下:
在这里插入图片描述
在这里插入图片描述
堆栈情况如下:
在这里插入图片描述
由于没有覆盖返回地址,所以一定能ret。让我们继续单步调试。回到main函数并执行完printf后堆栈情况如下:
在这里插入图片描述
在这里插入图片描述
输出Welcome back!
在这里插入图片描述
接着看main函数执行结束能不能成功退出。结果是无法执行最后的ret
在这里插入图片描述
在这里插入图片描述
为什么?因为函数栈中的ebp被覆盖了,这个ebp是以前的函数桢指针,也就是堆栈的正确恢复依赖于这个ebp,ebp被覆盖了会导致函数不能正常退出。

调试程序3

这里继续把字符串长度变短,还是之前那段程序

 char* srcStr="123aaabbbcccddd";

调试程序3分析

还是直接设断点,单步执行到执行完strcpy函数,观察堆栈情况:
在这里插入图片描述
在这里插入图片描述
堆栈情况画为图:
在这里插入图片描述
由于ebp和返回地址都没有覆盖,所以堆栈能成功恢复,所以应该可以成功输出Welcome back且能正常返回。现在来验证,执行完main函数。
在这里插入图片描述
发现可以执行完ret,不像之前那个的程序2直接跳过了ret。
在这里插入图片描述

注意

应该也可以使用gdb进行调试但请注意需要32汇编,最好关掉地址随机化以及关闭堆栈保护(在gcc中有相应命令,我记得是-fno-stack-protecter,可能记错了,所以最好查一下),既然是在strcpy函数那里溢出的,可以在那打断点,我试了一下,使用gdb调试比使用od要麻烦很多,od可以将反汇编语句,十六进制,堆栈情况和寄存器情况一目了然。对我这种新手更友好,当然我之前那篇博客有如何用gdb调试代码的,详见我这篇文章的详情。

总结

这次实验初探了缓冲区的溢出,理解了缓冲区和函数的栈,我们通过三次实验对比可以知道缓冲区空间只有那么大,如果加入的数据比缓冲区大会向高地址溢出,有可能会覆盖到ebp(以前的函数桢指针,堆栈能不能正确恢复取决于ebp是否被覆盖)以及返回地址。第一次实验字符覆盖了ebp和返回地址导致直接不能从cpyFrom函数返回到main函数,第二次实验字符只覆盖了ebp而没有覆盖返回地址,所以cpyFrom函数可以成功返回到main函数但是由于ebp被覆盖所以导致main函数不能成功返回,第三个实验是正常情况,缓冲区未溢出。
所以我的理解是覆盖ebp而不覆盖返回地址,这个函数可以回到之前调用它的函数但是之前那个函数无法返回,如果覆盖了返回地址则必然会覆盖ebp会导致当前函数都无法返回之前调用它的这个函数。
我举个例:f函数调用了g函数,g函数因缓冲区溢出导致函数栈中的ebp被覆盖而eip(返回地址)没有被覆盖,则g可以返回到f但是f不会正常返回。
g函数因缓冲区溢出导致函数栈中的eip被覆盖则ebp必被覆盖,会导致g无法返回f。

缓冲区溢出主要是C/C++等语言没有边界检查机制,但现在编译器等已经可以预防了。如果函数栈那个返回地址因缓冲区溢出导致被写为JMP ESP,在ESP处写入ShellCode,执行后会产生严重的后果,这个我们接下来继续讨论。

这篇文章花费了许多时间,欢迎大家批评指正。

标签:返回,函数,堆栈,地址,ebp,初探,缓冲区,溢出
来源: https://blog.csdn.net/weixin_44240981/article/details/121860644

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有