栈溢出攻击的一次简单尝试

1.栈溢出的一个简单实例

下面程序可能是那些接触C不久之后,可能会犯的一个数组越界导致缓冲区溢出的一个小例子

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void func(void)
{
    int a = 23456;
    int b[2];
    printf("b[2] = %d\n",b[2]);    
}

int main(int argc,char **argv,char **env)
{
    func();
    return 0;
}

这里写图片描述
如果你仔细观察的话会惊讶的发现越界了的b[2]的值是你变量a的值
那么问题原因是什么呢?对没错就是栈溢出!那么栈溢出究竟是什么?它仅仅是让你的程序产生错误么?只要你认真读接下来的文章我保证会让你对栈溢出有一个更高层次的认识!

2.进程地址空间

图一
这里写图片描述
当一个程序在运行时(即一个进程)其虚拟地址空间如上图所示,我们自下往上分析

(1)文本段:该区域保存了我们上面test.c的经编译链接之后的可执行程序文本段
(2)数据段:该段保存了如我们test.c中的data1和data2等全局变量(当然局部或全局静态变量也会在此保存)
(3)堆:当我们调用malloc分配内存时,其分配的内存就是从该段获取的其增长方向由下向上
(4)栈:下面着重讲
(5)argv:这些保存的就是main函数的参数
(6)内核段:内核所占用的内存

好了接下来给大家重点介绍我们的重头戏栈区(段)
为了介绍之后的知识方便我们给上面程序中的函数加俩个参数来介绍函数调用时,进程栈的变化

改变后的程序变成下面这样

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void func(m,n)//加了未使用的参数为了全面的解释进程栈的变化
{
    int a = 23456;
    int b[2];
    printf("b[2] = %d\n",b[2]);    
}

int main(int argc,char **argv,char **env)
{
    func(2,1);
    return 0;
}

当我们调用func时保存其进程地址空间的栈是如何变化的呢?
图二
这里写图片描述
介绍之前我们首先来给大家介绍个概念栈帧

栈帧:栈帧的主要概念或是作用就是用来保存当前函数调用时所需的所有信息以及调用其函数的函数指针

栈帧的概念介绍完了我们接着回到上面的问题
当一个函数代码执行之前该函数所用到的诸如形参,非静态局部变量等都是咋么入栈的呢?

(1)首先入栈的就是当前函数的形参如上图所示,该函数的俩个形参入栈的顺序为反序的第二个参数n先入栈,第一个参数n后出栈,其实这很好理解(后进先出么)
(2)接着需要保存的就是调用该函数的母一个函数(即谁调用了它的函数)的地址在这个程序里就是main函数的地址了,你可能会觉得当前函数的栈帧中保存个这搞啥,不要急,(3)中我会为你解释为什么
(3)该处叫做EBP它保存的是调用func函数的栈帧的底部(此处为main函数的底部)。好了我们结合(2)(3)来一起解释(2)中为什么要保存func的调用函数地址。首先当我们的func函数执行完之后,是不是程序就退出了?当然不会,程序会接着执行调用func函数的函数(这个例子为main函数)那么系统要运行这个函数首先得知道这个函数地址吧,知道了函数地址之后是不还得知道执行该函数的栈帧。好了,现在EBP中保存的就是上级函数的栈帧,而(2)中又保存了函数的地址,这样当func函数退出时,我们就具备了继续执行其调用函数的条件了
(4)这里保存的是func里的非静态局部数据,由于栈是由高地址向低地址增长的,所以首先变量a会被压入栈中,接着是数组b,我们都知道b[1]的地址高于b[0]所以根据栈的增长方向,我们不难判断b[1]会先入栈,接着是b[0]
(5)esp是指向当前栈顶的指针,正在执行的函数就是通过该指针来获得其对应的栈帧信息
好了介绍到这我我想如果你真正理解的话我本篇博客开头的那个奇怪的bug你是否能轻松说出原因?很简单&b[2]其实就是a的地址。。。

3.简单的栈溢出攻击

还是开头的那个例子,如果我给b[2]赋值为0那么a还会是23456么?当然不是了因为&b[2]和&a是一样的地址,改变b[2]对应的a也就改变了,那么如果我依次类推给b[3],b[4],b[5]…依次赋个值呢?哈哈,这就是本篇博文要介绍的核心,这个例子我就不试了,有兴趣的同学可以自己去试一下

不多说直接上我的栈溢出攻击的简单实例
测试机器为64(即指针为8字节)

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

void func1(char *s)
{
    char a[4];
    memcpy(a,s,20);   
}

void func2(void)
{
    printf("hello,word\n");
}

void func3(char *s)
{
    func1(s);
}

int main(void)
{
    void (*p)(void);
    char a[20];
    char *s = a;
    bzero(s,sizeof(s));
    p = &func2;
    s = s + 12;
    memcpy(s,p,sizeof(p));
    func3(s);
    return 0;
}

当我们调用func3而func3又调用func1时此时用户根据我们上面的讨论栈存了些什么呢?
这里写图片描述
如上图我感觉我已经画的够清楚了吧?那么接下来的问题是我们如何利用我们前面所介绍的缓冲区溢出的知识能够让func1执行完后不去接着执行func3而是去执行func2呢?
其实讲到这里已经很好想出来的了,我们可以给func1的局部变量拷贝一个比它大的字符串,例如我们上面实例程序中的p[20]将其memcpy拷贝给a字符数组会咋么样呢?对照着图,首先p[0]~p[3]的数据会覆盖调func1局部变量a数组的所有信息,接着对照着图继续往上溢出,p[4]~p[11]会溢出掉func1的EPB在往上依次类推,我们会发现到最后func1栈帧中的调用其的函数指针会被p[12]~p[19]给取代了,关于该函数指针的作用我前面有详细介绍过
即这里
这里写图片描述
所以只要我们让p[11]~p[19]这段空间内保存了func2的函数地址,再将p像上面实例中一样将其在func1中memcpy给a,就可以达到我们最开始想要达到的目的,在执行完func1()之后不会在执行func3了,而会去执行func2!

4.总结

今天这个小程序是我在看完栈帧这个概念后就想着能否将我刚学到的东西有所用处,所以当我花几小时弄出来后,我一点都不觉得类,反而觉得这样的学习真的像游戏一样很好玩的。果然兴趣其实才是我们学习最好的引导因素之一吧!

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值