zoukankan      html  css  js  c++  java
  • 内存越界引用与缓冲区的溢出攻击

    转载自:https://wdxtub.com/2016/04/16/thin-csapp-2/

    缓冲区溢出

    这一节是机器代码的最后一部分,主要说说由缓冲区溢出引起的攻防大战。我们先来看看程序在内存中是如何组织的(x86-64 Linux):

    最上面是运行时栈,有 8MB 的大小限制,一般用来保存局部变量。然后是堆,动态的内存分配会在这里处理,例如 malloc(), calloc(), new() 等。

    然后是数据,指的是静态分配的数据,比如说全局变量,静态变量,常量字符串。最后是共享库等可执行的机器指令,这一部分是只读的。

    可以见到,栈在最上面,也就是说,栈再往上就是另一个程序的内存范围了,这种时候我们就可以通过这种方式修改内存的其他部分了。

    举个例子:

    typedef struct 
    {
        int a[2];
        double d;
    } struct_t;
    double fun(int i) { volatile struct_t s; s.d = 3.14; s.a[i] = 1073741824; // 可能会越界 return s.d; }

    不同的 i 可能的执行结果是:

    • fun(0) -> 3.14
    • fun(1) -> 3.14
    • fun(2) -> 3.1399998664856
    • fun(3) -> 2.00000061035156
    • fun(4) -> 3.14
    • fun(6) -> Segmentation fault

    之所以会产生这种错误,是因为访问内存的时候跨过了数组本身的界限从而修改了 d 的值。(结构体中a比d先定义,所以d在内存中被分配在a[2]后面的相邻位置)

    数组元素可以使用指针来访问,所以对数组的引用没有边界约束。

    你没看错,这是个大问题!如果不检查输入字符串的长度,就很容易出现这种问题,尤其是针对在栈上有界限的字符数组。

    在 Unix 中,gets() 函数的实现是这样的:

    // 从 stdin 中获取输入
    char *gets(char *dest)
    {
        int c = getchar();
        char *p = dest;
        while (c != EOF && c != '
    ')
        {
            *p++ = c;
            c = getchar();
        }
        *p = '';
        return dest;
    }

    可以看到并没有去检测最多能读入多少字符(于是很容易出问题),类似的情况还在 strcpy, strcat, scanf, fscanf, sscanf 中出现。比如说

    void echo() {
        char buf[4]; // 太小
        gets(buf);
        puts(buf);
    }
    void call_echo() {
        echo();
    }

    我们来测试一下这个函数,可能的结果是:

    unix> ./echodemo
     Input: 012345678901234567890123
    Output: 012345678901234567890123
    unix> ./echodemo
     Input: 0123456789012345678901234
    Segmentation Fault

    为什么明明在 echo() 中声明 buf 为 4 个 char,居然一开始输入这么多都没问题?我们到汇编代码里去看看:

    00000000004006cf <echo>:
        4006cf: 48 83 ec 18         sub   $0x18, %rsp
        4006d3: 48 89 e7            mov   %rsp, %rdi
        4006d6: e8 a5 ff ff ff      callq 400680 <gets>
        4006db: 48 89 e7            mov   %rsp, %rdi
        4006de: e8 3d fe ff ff      callq 400520 <puts@plt>
        4006e3: 48 83 c4 18         add   $0x18, %rsp
        4006e7: c3                  retq
    # call_echo 部分
        4006e8: 48 83 ec 08         sub   $0x8, %rsp
        4006ec: b8 00 00 00 00      mov   $0x0, %eax
        4006f1: e8 d9 ff ff ff      callq 4006cf <echo>
        4006f6: 48 83 c4 08         add   $0x8, %rsp
        4006fa: c3                  retq

    我们看 4006cf 这一行,可以发现实际上给 %rsp 分配了 0x18 的空间,所以可以容纳不止 4 个 char。

    在调用 gets 函数之前(第 4006d6 行),内存中栈帧示意图为:

    结合上面代码可以看到,call_echo 栈帧中保存着调用之前执行指令的地址 4006f6,用于返回之后继续执行。

    我们输入字符串 01234567890123456789012 之后,栈帧中缓冲区被填充,如下:

    虽然缓冲区溢出了,但是并没有损害当前的状态,程序还是可以继续运行(也就是没有出现段错误),

    但是如果再多一点的话,也就是输入 0123456789012345678901234,内存中的情况是这样的:

    就把返回地址给覆盖掉了,当 echo 执行完成要回到 call_echo 函数时,就跳转到 0x400034 这个内容未知的地址中了。

    也就是说,通过缓冲区溢出,我们可以在程序返回时跳转到任何我们想要跳转到的地方!攻击者可以利用这种方式来执行恶意代码!

  • 相关阅读:
    AngularJs学习笔记(一)----------关于数据绑定
    水平垂直居中常见方式总结
    左边固定,右边自适应常见方式总结
    关于JavaScript的设计模式--笔记(1)
    SQL 分组后获取其中一个字段最大值的整条记录
    .NET交流 259868462
    C#可以自动在后台为属性创建字段
    委托的一个实例
    encodeURIComponent()对js参数进行编码,防止错误值
    相同的sql 分页查询结果
  • 原文地址:https://www.cnblogs.com/FengZeng666/p/9646807.html
Copyright © 2011-2022 走看看