一文读懂|栈溢出攻击

什么是栈

简单来说, 是一种 LIFO(Last In Frist Out,后进先出) 形式的数据结构。栈一般是从高地址向低地址增长,并且栈支持 push(入栈) 和 pop(出栈) 两个操作。如下图所示:

2638640a074149d29c832e3dcc171c62.jpeg

push 操作先将 栈顶(sp指针) 向下移动一个位置,然后将数据写入到新的栈顶;而 pop 操作会从 栈顶 读取数据,并且将 栈顶(sp指针) 向上移动一个位置。

例如,将 0x100 压入栈,过程如下图所示:

74353373e8a0fc5eb1493284cd6fc627.jpeg

我们再来看看 出栈 操作,如下图所示:

7d020240f4aa58f26fb280531df7d296.jpeg

栈帧

栈帧,也就是 Sack Frame,其本质就是一种栈,只是这种栈专门用于保存函数调用过程中的各种信息(参数,返回地址,本地变量等)。

栈帧 有 栈顶 和 栈底 之分,其中栈顶的地址最低,栈底的地址最高。SP(栈指针) 就是一直指向栈顶的。在 x86 的 32 位 CPU 中,我们用 %ebp 寄存器指向栈底,也就是基址指针;用 %esp 寄存器指向栈顶,也就是栈指针。下面是一个栈帧的示意图:

2105cfc004c4b9506a887cf52d007d8c.jpeg

一般来说,我们将 %ebp 到 %esp 之间区域当做栈帧。并不是整个栈空间只有一个栈帧,每调用一个函数,就会生成一个新的栈帧。

在函数调用过程中,我们将调用函数的函数称为:调用者(caller),将被调用的函数称为:被调用者(callee)。在这个过程中:

  • 调用者 需要知道在哪里获取 被调用者 返回的值(一般存放到 %eax 寄存器)。

  • 被调用者 需要知道传入的参数在哪里和调用完后的返回地址在哪里。

  • 我们需要保证在 被调用者 返回后,%ebp 和 %esp 寄存器的值应该和调用前一致。

函数调用

现在,我们来看看函数调用时,栈帧是如何变化的。

我们以一个函数调用的实例来解说,代码如下:

// stack.cint add_func(int a, int b)
{int c, d;c = a;d = b;return c + d;
}int main(int argc, char *argv[])
{int total;total = add_func(1, 2);return 0;
}

我们使用命令 gcc -S -m32 stack.c 来编译上面的代码,获取的汇编代码如下所示(去掉一些无关紧要的信息):

add_func:pushl   %ebp                // 保存ebp寄存器到栈movl    %esp, %ebp          // 把ebp进程设置为esp的值subl    $16, %esp           // 为局部变量申请空间movl    8(%ebp), %eax       // 把参数a保存到eax寄存器中movl    %eax, -8(%ebp)      // 把eax寄存器的值保存到局部变量c中(c = a)movl    12(%ebp), %eax      // 把参数b保存到eax寄存器中movl    %eax, -4(%ebp)      // 把eax寄存器到值保存到局部变量d中(d = b)movl    -8(%ebp), %edx      // 把d的值保存到edx寄存器中movl    -4(%ebp), %eax      // 把c的值保存到eax寄存器中addl    %edx, %eax          // 将eax寄存器与edx寄存器的值相加,保存到eax中(返回值)leaveret                         // 函数返回...

可能汇编代码比较难看懂,我们用下面的插图来说明这个调用过程:

eee2296db05c1260cb42642429077fad.jpeg

如上图所示,调用过程如下:

  • 在 main() 函数调用 add_func() 函数前,先将调用 add_func() 函数的参数压栈。

  • 在调用 add_func() 函数时,会将 返回地址 压栈,接着进入 add_func() 函数。

  • add_func() 函数执行时,会将原来的 ebp寄存器 的值压栈,然后把 ebp寄存器 的设置为 esp寄存器 的值。

  • 接着 add_func() 函数会为局部变量申请空间,也就是将 esp寄存器 向下移动。

  • 然后把局部变量 c 设置为参数 a 的值,局部变量 d 设置为 参数 b 的值。

  • 最后将局部变量 c 和 d 的值相加,放置到 eax寄存器 中(C语言规定以 eax寄存器 传递返回值),然后调用 ret 指令返回到 main() 函数。

函数返回

上面介绍了 函数调用 的过程,现在我们来介绍一下函数调用完毕后,从被调用函数返回到原来的函数过程是如何处理的。

从 add_func() 函数的汇编代码可以看到,当被调用函数执行完毕返回到调用函数前,会执行 leave 指令,这条指令等价于:

movl %ebp, %esp
popl %ebp

这两条汇编指令的意思是,将 esp寄存器 和 ebp寄存器 恢复到调用函数前的值。

然后,调用 ret 指令返回到原来的函数。ret 指令会从栈顶获取 返回地址,然后跳转到(jmp指令)此地址继续执行。这时的 栈帧 的结构如下图所示:

b44423b1dff9012449b073409686cb9c.jpeg

栈溢出攻击

前面说了那么,都是为了 栈溢出攻击 这节作铺垫的。通过前面的学习,我们知道调用函数的 参数 、执行完函数后的 返回地址 和被调用函数的 局部变量 都是存放在栈中的。

如果在调用函数时,不小心将 返回地址 覆盖了,那么调用完函数后,将不会跳转到原来的函数继续执行,而是跳转到覆盖后的地址执行。如下图所示:

97a3e08c6b29d436f843e22b05c81e72.jpeg

那么,怎样才能把 返回地址 覆盖呢?我们可以通过下面的例子来说明:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>#define PTR_SIZE 8   // 指针的大小
#define EBP_SIZE 8   // ebp寄存器的大小void inject_callback()
{printf("inject_callback called...\n");exit(0);
}void func_call(char *addr, int len)
{char tmpBuf[16] = {0xff};memcpy(tmpBuf + 16 + EBP_SIZE, addr, len);printf("func_call called...\n");
}int main(int argc, char** argv)
{uint64_t injectPtr = (uint64_t)&inject_callback;func_call(&injectPtr, PTR_SIZE);printf("main exited...\n");return 0;
}

我们使用以下命令编译上面代码,并且执行:

$ gcc stack-overflow.c -fno-stack-protector -o stack-overflow
$ ./stack-overflow
func_call called...
inject_callback called...

在编译上面程序时,一定要加上 -fno-stack-protector 参数,否则将会触发栈溢出保护,导致执行失败。

在上面的代码中,我们并没有直接调用 inject_callback() 函数,而是通过把 inject_callback() 函数的地址复制到 func_call() 函数的局部变量 tmpBuf 中。

由于局部变量 tmpBuf 的类型为字符串数组,而且大小为 16 个字节。但我们复制数据是从 24(16 + 8)处开始复制,已经超出了局部变量 tmpBuf 的大小,如下图所示:

f1d5a5a9842abc72bbe1b874b0617c8f.jpeg

从上图可以看出,func_call() 函数在调用 memcpy() 函数复制数据时,由于不小心用 inject_callback() 函数的地址覆盖了返回地址,导致 func_call() 函数执行完毕后,跳转到 inject_callback() 函数处执行。

这就是 栈溢出攻击 的原理,而导致 栈溢出攻击 的原因就是:调用 memcpy()strcpy() 等函数复制数据时,没有对数据的长度进行验证,从而 返回地址 被复制的数据覆盖了。

黑客可以利用 栈溢出攻击 来把函数的返回地址修改成入侵代码的地址,从而实现攻击的目的。

aae9994b4e4428cfd17667c41b6985bf.jpeg

2a4ff1f2c03a2110ebc34d7c0b6aebe8.jpeg

0a5089ebbee3a59253526e78ada37307.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/463615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用来做 favicon 的站点

这个站点是最好用的 http://converticon.com/转载于:https://www.cnblogs.com/johnsonshu/archive/2012/06/28/2568530.html

Codeforces Round #533 (Div. 2) C.思维dp D. 多源BFS

题目链接&#xff1a;https://codeforces.com/contest/1105 C. Ayoub and Lost Array 题目大意&#xff1a;一个长度为n的数组&#xff0c;数组的元素都在[L,R]之间&#xff0c;并且数组全部元素的和可以被3整除&#xff0c;问有多少种方法构建出该数组。答案模1000000007 例 输…

c++并发编程之原子操作的实现原理

原子(atomic)本意是”不能被进一步分割的最小粒子”,而原子操作(atomic operation)意为”不可被中断的一个或一系列操作”. 处理器如何实现原子操作 (1) 使用总线锁保证原子性 如果多个处理器同时对共享变量进行读写操作,那么共享变量就会被多个处理器同时进行操作,这样读写…

Sqlserver 通用存储过程(二) 联合主键

CREATEPROCP_public_ViewPage /**//**//**//* no_mIss 通用分页存储过程 2007.3.1 QQ:34813284 适用于联合主键/单主键/存在能确定唯一行列/存在能确定唯一行的多列 (用英文,隔开) 调用&#xff1a; 第一页查询时返回总记录和总页数及第一…

PHP定时执行任务的实现

此代码只要运行一次后关闭浏览器即可。 ignore_user_abort();//关掉浏览器&#xff0c;PHP脚本也可以继续执行.set_time_limit(0);// 通过set_time_limit(0)可以让程序无限制的执行下去$interval60*30;// 每隔半小时运行do{//这里是你要执行的代码sleep($interval);// 等待5分钟…

预检请求

不久前在公司写了一个基于 Hapijs 的后端项目&#xff0c;感觉这个框架很有自己的特点&#xff0c;跟 Express 和 Koa 的区别比较大&#xff0c;体现了配置大于编码的思想。用起来很方便&#xff0c;据说 Walmart 团队用这个框架扛住了黑五的流量&#xff0c;看起来在实际项目中…

linux编译动态库之-fPIC

在生成动态库时&#xff0c;常常习惯性的加上fPIC选项&#xff0c;fPIC有什么作用和意义&#xff0c;加不加有什么区别&#xff0c;这里做下小结&#xff1a; fPIC的全称是 Position Independent Code&#xff0c; 用于生成位置无关代码。什么是位置无关代码&#xff0c;个人理…

深入理解golang 的栈

线程栈(thread stacks)介绍 先回顾下linux的内存空间布局 简书_stack02.png当启动一个C实现的thread时&#xff0c;C标准库会负责分配一块内存作为这个线程的栈。标准库分配这块内存&#xff0c;告诉内核它的位置并让内核处理这个线程 的执行。在linux系统中&#xff0c;可通过…

const和define的区别

今天查看以前的代码&#xff0c;进行优化&#xff0c;回想到const和define一些区别&#xff0c;记录下来。 1.const是关键字&#xff0c;define不是关键字。 2.const定义的是只读变量&#xff0c;不是常量&#xff0c;define宏定义的是常量&#xff0c;变量不能作为定义数组的维…

理解ALSA

最近处理音频的问题&#xff0c;所以看了一些不错的文章&#xff0c;整理一些有用的资料出来&#xff0c;有需要的可以收藏。ALSA的框架图&#xff1a;这个图可以说是我目前看到最不错的&#xff0c;我发现很多应用开发的&#xff0c;一出现解决不了的问题&#xff0c;或者奇怪…

Xshell 6如何设置多个session显示在同一个窗口

刚才安装了Xshell 6之后&#xff0c;发现在同一个窗口只能显示4个session&#xff0c;网上查找了一些资料但是都不是想要的结果&#xff0c;经过几分钟的查找&#xff0c;终于找到了设置在同一个窗口session的个数&#xff0c;因此记录下来&#xff0c;或者给与他人帮助。以下以…

appium+java(五)微信小程序自动化测试实践

前言&#xff1a; 上一篇《appiumjava&#xff08;四&#xff09;微信公众号自动化测试实践》中&#xff0c;尝试使用appium实现微信公众号自动化测试&#xff0c;接着尝试小程序自动化&#xff0c;以学院小程序为例 准备工作 1、java-client 3.4.16依赖包 2、微信应用版本7.0.…

blockUI应用到Asp.Net页面时服务器控件(Button等)失效的问题

问题&#xff1a;在Asp.Net页面中用blockUI这个控件实现弹出窗口的效果&#xff0c;弹出页面内容为页面中某个Panel中的内容&#xff0c;包含TextBox、Button等服务器控件。使用时就简单的设置message属性。问题出来了&#xff0c;当显示这个弹出页面后&#xff0c;所有Button等…

android DatePicker

为什么80%的码农都做不了架构师&#xff1f;>>> public class DatePicker extends FrameLayout java.lang.Object android.view.View android.view.ViewGroup android.widget.FrameLayout android.widget.DatePicker DatePicker 一个选择年月日的日历布局视图 公…

一次限制进程的 CPU 用量的实操过程

大家好&#xff0c;我是飞哥&#xff01;给大家分享一个事情。背景是这样的&#xff0c;我们要测试某个第三方 SDK 运行性能&#xff0c;这是个 CPU 密集型的服务。我想评估一下它运行一遍到底有多吃 CPU&#xff0c;以便评估上线后我们需要部署多少台服务器。我们是在一台 16 …

map与unordered_map的区别

set/map底层实现的机制是红黑树。红黑树是一种近似于平衡的二叉查找树&#xff0c;默认是按升序排序的。在红黑树上做查找、插入、删除操作的时间复杂度为O(logN)。 红黑树的缺点&#xff1a;空间占用率高&#xff0c;每一个节点都需要额外保存父节点、孩子节点和红/黑性质&am…

navicat不同数据库数据传输

复制fo的t_fo_account表结构和数据到base库 结果 转载于:https://www.cnblogs.com/feifeicui/p/10307646.html

Win2003 IIS下,ASP.NET无法访问数据库和网页

1.Win2003 IIS下,ASP.NET无法访问网页 将IIS的 允许ASP 设置为允许. 2.Win2003 IIS下,ASP.NET无法访问数据库(这里我用的是Oracle9i) 1)将网站的虚拟目录 添加 ASP.NET 和 NETWORK_SERVICE用户. 2)oracle目录下ora92目录的Authenticateduser用户 去掉勾中的权限 再勾上权限. 最…

FTP自动上传日期命名文件

说明&#xff1a;此文章是从http://177048.blog.51cto.com/167048/919374转载过来的&#xff0c;若有侵权之处&#xff0c;请联系本人&#xff0c;及时删除&#xff0c;谢谢&#xff01; 需求&#xff1a;将每天备份的数据以当天日期命名&#xff0c;并定时上传到FTP服务器上。…

收藏了两年的嵌入式AI资源学习笔记,今天全分享给大家(附代码/资料/视频/学习规划)...

当前乃至未来5-10年&#xff0c;嵌入式开发者还有哪些风口&#xff1f;”画外音&#xff1a;风口的本质&#xff0c;其实就是一段时间的人才供需不平衡。说白了就是由于行业突变&#xff0c;敏锐的资本快速进入&#xff0c;导致短时间内行业大量扩张&#xff0c;需要大量开发者…