缓冲区溢出困惑

时间:2014-05-06 12:25:36

标签: c security buffer-overflow

所以,我试图在这段代码上执行缓冲区溢出,目的是将变量target更改为'Y'。问题是,我似乎无法让缓冲区溢出到足以产生运行时错误。谁能帮助我理解为什么?

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

enum {SIZE = 50};

char target = 'Z';
char name[SIZE];
FILE *f;

void read(char *s) {
   char buffer[SIZE];
   int i = 0; 
   int c;

   for (;;) 
   {
      c = getchar();
      if ((c == EOF) || (c == '\n')) 
         break;
      buffer[i] = c;
      i++;
   }
   buffer[i] = '\0';

   for (i = 0; i < SIZE; i++) 
      s[i] = buffer[i];
}


int main(void) 
{
   read(name);

   if (strcmp(name, "ABCD") == 0) 
      target = 'Y';

   printf("%s\n", name);
   printf("%c\n", target);

   exit(0);
}

2 个答案:

答案 0 :(得分:1)

如果您尝试使用更改的返回地址执行此操作,则需要使用所需的特定地址覆盖它。在这种情况下,您可以使用代码target = 'Y';

的地址覆盖所需的结果

根据堆栈的组织方式,您可以直接从read()中删除返回地址,这将使您返回到调用它的第一行main之后。如果做不到这一点,你就有机会破坏i,这会让你随意写入内存。编写这些字节时需要小心,因为它会影响索引。

我将假装这是在ARM上完成的,因为我更熟悉它们的堆栈布局和调用约定。所以,你的堆栈看起来像这样:

STACKPTR-60: buffer[0]
 |   |   |
STACKPTR-11: buffer[49]
STACKPTR-10: padding
STACKPTR -9: padding
STACKPTR -8: i (bits 7..0)
 |   |   |
STACKPTR -5: i (bits 31..24)
STACKPTR -4: c (bits 7..0)
 |   |   |
STACKPTR -1: c (bits 31..24)
STACKPTR +0: link register (return address) (bits 7..0)
 |   |   |
STACKPTR +3: link register (return address) (bits 31..24)

因此,使用此堆栈布局,当您溢出buffer时,您将首先填充两个填充字节。之后,填写i的最低有效字节。在这种情况下,您可以跳过一个巨大的溢出,只需修改索引以指向堆叠链接寄存器。您希望i=60开始写入返回地址,但是在您编写它之后,它将递增,因此您真的想要i=59,然后它将递增到i=60。此时,您可以将所需的退货地址从最不重要到最重要,然后是EOF\n

非常重要的是要注意这一切都非常具体,特定于编译器。编译器设置将确定int的大小以及是否填充堆栈以对齐变量,以及变量在堆栈中的显示顺序。该体系结构将确定您的堆栈帧的外观。

答案 1 :(得分:0)

试试这个&#39;不崩溃&#39;您的代码版本,它会覆盖name数组的边界,以获得适当大小的输入字符串:

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

enum {SIZE = 50};

char target_1 = 'Z';
char name[SIZE] = "Help";
char target_2 = 'Z';

static void read(char *s)
{
   char buffer[2*SIZE];
   int i = 0; 
   int c;

   while ((c = getchar()) != EOF && c != '\n')
      buffer[i++] = c;
   buffer[i] = '\0';

   for (int j = 0; j < i; j++) 
      s[j] = buffer[j];
}


int main(void) 
{
   printf("Data-1: %c %s %c\n", target_1, name, target_2);
   read(name);
   printf("Data-2: %c %s %c\n", target_1, name, target_2);
   return(0);
}

示例运行:

$ for len in 48 49 50 51 52 53 54 ; do echo $len; perl -e "print 'a' x $len" | ./bo; done
48
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
49
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
50
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZ Z
51
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
52
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
53
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
54
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
$

您可以看到target_1的内存地址高于name,并且它会被大小为52或更大的输入覆盖。

当我说'崩溃&#39;时,我的意思是,对于长度足以溢出name缓冲区但不会溢出的输入行,它不会崩溃buffer缓冲区&#39; (大致长度为50到100)。使用较长的输入字符串,程序会因分段错误而崩溃,但有趣的是它在打印第二行输出后崩溃,而不是从read()返回时崩溃。

140
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
Segmentation fault: 11

在Mac OS X 10.9.2 Mavericks上测试,使用GCC 4.9.0。