zoukankan      html  css  js  c++  java
  • 缓存溢出问题简述

    缓存溢出

     

    缓存溢出(Buffer overflow),是指在存在缓存溢出安全漏洞的计算机中,攻击者能够用超出常规长度的字符来填满一个域,一般是内存区地址。这篇文章就是解说简单的缓存溢出问题。文章以x86_32 linux 系统平台为蓝本。为了介绍缓存溢出,数据的存储地址、基本的汇编指令、重要的寄存器等内容都要解说。

     

    1.      变量存储

    C语言中,变量属性有非常多中,可是对于缓存溢出问题,我们主要关心的数据的存储位置,或存储空间。因此,这里我们主要关心全局变量,局部变量和静态变量。在C语言中,通常全局变量和静态变量被分配于数据段(data section)中,可是局部变量分配在栈(stack)中。另外,大家清楚,栈空间中还存储CSIP等一些列与指令地址相关的关键数据,因此,在对局部变量进行数据拷贝的时候,假设拷贝数据块过大,就可能将IPCS等寄存器存放的数据空间覆盖掉,写入一些非法的数据,当从设IP值时,计算机就跳转到新的非法数据代码空间了。这里有一个简单的C文件,overflow1.c

    int data = 0x66666666;

     

    int func1(void)

    {

            static int sdata = 0x55555555;

            int ret = 0;

            return ret;

    }

     

    int main(int argc, char *argvs[])

    {

            func1();

            return 0;

    }

     

    我们首先对它进行编译,然后查看编译后的信息。

     

    # gcc –o overflow1.o –c overflow1.c

    # objdump –t overflow1.o

     

    over3.o:     file format elf32-i386

     

    SYMBOL TABLE:

    00000000 l    df *ABS*  00000000 over3.c

    00000000 l    d  .text  00000000 .text

    00000000 l    d  .data  00000000 .data

    00000000 l    d  .bss   00000000 .bss

    00000004 l     O .data  00000004 sdata.1280

    00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack

    00000000 l    d  .comment       00000000 .comment

    00000000 g     O .data  00000004 data

    00000000 g     F .text  00000012 func1

    00000012 g     F .text  0000001e main

     

           从结果中我们非常easy找到data sdata变量的size, section等信息。假设想获取变量存储的地址,能够连接后在运行该命令。可是对于局部ret,恐怕你不easy找到它的存放地址,那么ret的空间在哪里呢? 不要着急,我们继续。

           # objdump –d overflow1.o

     

    over3.o:     file format elf32-i386

     

    Disassembly of section .text:

     

    00000000 <func1>:

       0:   55                      push   %ebp

       1:   89 e5                    mov    %esp,%ebp

       3:   83 ec 10                  sub    $0x10,%esp

       6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

       d:   8b 45 fc                  mov    -0x4(%ebp),%eax

      10:   c9                       leave 

      11:   c3                       ret   

     

    00000012 <main>:

      12:   8d 4c 24 04             lea    0x4(%esp),%ecx

      16:   83 e4 f0                and    $0xfffffff0,%esp

      19:   ff 71 fc                 pushl  -0x4(%ecx)

      1c:   55                      push   %ebp

      1d:   89 e5                   mov    %esp,%ebp

      1f:   51                      push   %ecx

      20:   e8 fc ff ff ff              call   21 <main+0xf>

      25:   b8 00 00 00 00           mov    $0x0,%eax

      2a:   59                      pop    %ecx

      2b:   5d                      pop    %ebp

      2c:   8d 61 fc                lea    -0x4(%ecx),%esp

      2f:   c3                      ret   

     

    从汇编代码里面恐怕还是不明确ret在哪里存储吧?不用操心,我们回头看看C源文件,在func1函数里有一个 int ret = 0 的声明,聪明的你如今是不是找到相应的汇编语句了,你猜对了,就是以下这句话:

       6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

     

    也就是说,ret的空间分配在-0x4%(ebp)指向的空间中。从

    1:   89 e5                    mov    %esp,%ebp

    你会发现ebp存放的是esp的内容,也就说-0x4%(ebp)指向的是栈空间地址,而ret就存放在哪里。

     

    如今明确了变量的存放空间了,以下我们要继续解说关于栈中其他的信息。

     

    2.      重要指令和寄存器

    为了更好的了解缓存溢出,就要了解与其相关的指令和寄存器。我在这部分内容中会解说这些信息。

    IPCSebpesp是我们要讲的寄存器。CS:IP指向将要运行的指令的存储地址。一般的函数跳转指令和函数调用指令就是通过改动CS:IP的值来达到跳转目的。当指令段发生改变时,CS寄存器的数值才改变,在同一个指令段中,通常仅仅改变IP的数值就能够了。我们今天介绍的默认仅仅是通过改动IP的值而达到跳转目的。Esp寄存器存放的当前栈顶地址,而ebp作为一个备份寄存器,保存着进入新函数后esp的值。

     

    在重要的指令中,主要有push, pop, call, ret, leave. Push pop是一对栈操作指令,push完毕入栈操作,将数据写入栈中,并更新esp的内容,而pop指令与其相反,它将数据从栈中取出,并更新esp的内容。 当中 call指令是函数调用指令,它主要完毕指令计数器寄存器(IP) 的入栈操作(为了简单期间,这里不考虑CS入栈问题,有兴趣的同学能够去查看引用文献),相似于指令 “push ip”。而ret指令与call指令相反,是一个函数返回指令,将IP的值从栈中弹出,相似”pop ip”Leave也能够看成一个符合指令,它相似于 “mov ebp, esp; pop ebp” 两个指令的效果。

     

    了解了上面的基本知识,我们来分析一下汇编代码。为了清楚期间,我们将overflow1.o 进行连接,然后查看连接后的重要汇编代码片段。

    # gcc –o overflow1 overflow.o

    # objdump –d overflow1

    …..

    08048324 <func1>:

     8048324:       55                      push   %ebp

     8048325:       89 e5                    mov    %esp,%ebp

     8048327:       83 ec 10                 sub    $0x10,%esp

     804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

     8048331:       8b 45 fc                  mov    -0x4(%ebp),%eax

     8048334:       c9                       leave 

     8048335:       c3                       ret   

     

    08048336 <main>:

     8048336:       8d 4c 24 04             lea    0x4(%esp),%ecx

     804833a:       83 e4 f0                and    $0xfffffff0,%esp

     804833d:       ff 71 fc                pushl  -0x4(%ecx)

     8048340:       55                      push   %ebp

     8048341:       89 e5                   mov    %esp,%ebp

     8048343:       51                      push   %ecx

     8048344:       e8 db ff ff ff          call   8048324 <func1>

     8048349:       b8 00 00 00 00          mov    $0x0,%eax

     804834e:       59                      pop    %ecx

     804834f:       5d                      pop    %ebp

     8048350:       8d 61 fc                lea    -0x4(%ecx),%esp

    ….

     

    从对func1函数開始调用開始,我们跟踪栈里面的内容。当call指令运行后,计算机会跳到func1函数处继续运行,如今就将这些指令合并:

    8048344:       e8 db ff ff ff          call   8048324 <func1>

    8048324:       55                      push   %ebp

    8048325:       89 e5                    mov    %esp,%ebp

    8048327:       83 ec 10                 sub    $0x10,%esp

    804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

    运行后的结果是什么呢?

           第一条指令是将IP压栈;

           第二条指令将ebp压栈

           第三条是将esp的值保存到ebp

           第四条指令更新esp的值,向前16bytes

           第五条指令给ret赋初值0,而且能够确定ret的地址是%ebp - 4

     

    因此,我们得到当前栈的值

                         IP                  0x88-0x8B      ;;High address 

    Ebp->   oldEBP           0x84-0x87      ;; ebp = 0x84

            ret                 0x80-0x83

    Nil                 0x7c-0x7f

    Nil                 0x78-0x7b

    Esp->   Nil                 0x74-0x77      ;; esp = 0x74

       ….                 ….                 ;;low address

     

    而且ebp会作为备份寄存器保留老的esp寄存器的值,当函数返回时,还原espebp,以及IP。缓存溢出就是在还原之前首先将栈中IP的值改动成其余的数值,从而是CPU跳转到一个错误地址或无效地址。

    假设按照上面栈的地址,那么ret变量的地址应该是0x80, 而老的IP数据的存储地址应该是0x88,假设在向ret进行数据拷贝时,数据过长,将会覆盖oldebpIP的地址,从而导致程序在返回时,将错误的IP值弹出到指令计数器中,CPU将会跳转到该错误地址进行代码运行。以下提供了两个案例程序,给大家參考。

    Examples

     

    Overflow2中是一个典型的内存溢出,作者通过向一个局部变量数组中写入过长数据,使程序无条件跳转的my_func() 一个非法函数中。

     

    #include <stdio.h>

    #include <string.h>

     

    char strs[32] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, /

                     0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08, /

                      0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08} ;

    /*my_func的地址是0x080483c4*/

    int my_func(void)

    {

            printf("in My Func!/n");

            return 87;

    }

             

    int print(void)

    {        

            int tmp = 0x33;

            int ret = 0x22;

            char str[4];

            char *data;

            strncpy(str, strs, 24);

     

            return ret;

    }

     

    int main(int argc, char *argvs[])

    {

            int ret = print();

     

            printf("ret = %x/n", ret);

     

            return 0;

    }

     

     

    Overflow3.c 是一个不通过函数调用,强制跳转到my_func()函数,并成功返回到主函数。

     

    #include <stdio.h>

    #include <string.h>

     

    int my_func(void)

    {

            printf("in My Func!/n");

            return 87;

    }

     

    int print(void)

    {

            int ret = 0x22;

            int str[4];

     

            asm (                           /

                    "mov 0(%%ebp), %%ebx;   /

                     mov %%eax, 0(%%ebp);   /

                     push %%eax;            /

                     sub $4, %%ebp;         /

                     mov %%ebx, 0(%%ebp)"   /

                    :               /

                    : "a"(my_func));

     

            return ret;

    }

     

    int main(int argc, char *argvs[])

    {

            int ret = print();

            printf("ret = %x/n", ret);

     

            return 0;

    }

     

     

    Reference

  • 相关阅读:
    python类的__repr__方法
    元素定位之css选择器(1)
    selenium-find_element相关内容
    selenium-模块概述(1)
    元素定位之css选择器(2)
    css笔记
    html笔记
    html、css、javascript之间的关系
    去除提示“Chrome正在受到自动软件的控制”
    Python3+RobotFramework+pycharm环境搭建
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/3850559.html
Copyright © 2011-2022 走看看