这个缓冲区溢出的后果?

时间:2010-07-20 14:16:03

标签: c++ c printf buffer-overflow

所以在这里我相信我在查看其他人的代码时发现了一个小的缓冲区溢出问题。它立刻让我觉得不正确,而且有潜在危险,但不可否认,我无法解释这个“错误”的实际后果,如果有的话。

我已经编写了一个测试应用程序来演示错误,但发现(令我沮丧的是)它似乎无论溢出都能正常运行。我想相信这只是偶然的,但是想要一些反馈来确定我的想法是否错误,或者是否真的存在问题,而不是在我的测试应用程序中显示它的头部。

问题代码(我认为无论如何):

char* buffer = new char[strlen("This string is 27 char long" + 1)];
sprintf(buffer, "This string is 27 char long");

现在,这对我来说很突出,我想把它标记为可能的缓冲区溢出是因为第一个strlen。由于指针运算,+ 1的'不正确'位置将导致strlen返回26而不是27(取“他的字符串为27个字符的长度”长”)。我相信sprintf然后将27个字符打印到缓冲区并导致缓冲区溢出。

这是正确的评估吗?

我编写了一个测试应用程序来为我正在查看的代码演示这个,并发现即使在调试器中字符串也会正确打印。我还尝试在此代码之前和之后将其他变量放在堆栈和堆上,以查看是否可以影响邻近的内存区域,但仍然收到正确的输出。我意识到我新分配的堆内存可能不相邻,这可以解释缺乏有用的溢出,但我真的想确认其他人的意见,如果这实际上是一个问题。

由于这是一个非常简单的“问题”,如果你能通过某种参考来支持你的答案,那就太好了。虽然我重视并欢迎您的意见,但我不会接受“是的”作为最终答案。提前谢谢你。




更新:许多有很多额外见解的好答案。不幸的是,我无法接受所有这些。感谢您分享您的知识并成为我的“第二意见”。我很感激帮助。

11 个答案:

答案 0 :(得分:14)

您的评估是正确的。 [编辑]加上James Curran提到的修正。[/ edit]

可能,您的测试应用没有显示问题,因为分配被四舍五入到下一个4,8或16的倍数(这是常见的分配粒度)。

这意味着您应该能够使用31个字符长的字符串进行演示。

或者,使用“仪器化”本机内存分析器,可以将保护字节紧密围绕这样的分配。

答案 1 :(得分:6)

你的评估是正确的,除了springf会在缓冲区中放入28个字符,最后计算字符串末尾的NUL(这就是为什么你首先需要放错位置的“+1”)

请注意,根据我的经验,如果某些内容在调试器之外失败,但在调试器中逐步执行,则在100%的时间内,您已超出本地缓冲区。调试器将更多内容推送到堆栈上,因此重写的内容不太可能被覆盖。

答案 2 :(得分:3)

问题是你在内存中的某处写入,但不在堆栈中。 因此,实际上很难看出什么是错的。 如果你想看到损坏,请尝试在堆栈上分配字符串

char buffer[strlen("This string is 27 char long" + 1)];

并写过去。 如果你真的知道二进制文件是如何工作的,你还可以编写其他变量来编写。

要利用这样的缓冲区溢出,您需要编写所需的数据,然后找到一种“跳转”到要执行的数据的方法。

答案 3 :(得分:1)

是的,你是对的。分配的缓冲区将是2个字节,太小而无法容纳字符串。

由于这是在堆上分配的,因此可能会导致堆损坏。但是,这种可能性取决于在此之前发生的其他内存分配和释放以及使用的堆管理器。有关详情,请参阅Heap Overflow

答案 4 :(得分:1)

这是正确的,本例中的指针算法会产生传递给new的不正确(较短)长度。您无法实现此崩溃的最可能原因是因为内存分配实际提供了多少缓冲区空间存在一些不确定性。

允许库提供比请求的更大的缓冲区。此外,您的缓冲区后面的任何内容都可能以受机器字对齐规则约束的分配标头作为前缀。这意味着在下一个分配头之前最多可以有三个填充字节(取决于平台)。

即使您覆盖了下一个分配标头(用于管理分配的内存块),在下一个块的所有者尝试将其返回到堆之前,它也不会表现为问题。

答案 5 :(得分:1)

许多历史malloc实施都会在分配的块之前和/或之后立即存储簿记数据。你可能会覆盖这样的数据,在这种情况下你不会看到任何错误/崩溃,直到你试图释放内存(或者可能释放下一个块恰好是什么)。同样,后续分配的簿记信息可能会在以后覆盖您的字符串。

我怀疑现代malloc实现通过使用完整性检查数据填充分配来做出一些努力来防止堆损坏,所以如果你很幸运,不会发生任何不好的事情,或者你可能会在以后收到警告消息分配/免费运营。

答案 6 :(得分:1)

我尝试使用堆分配,在这种情况下,变量在内存中不连续。这就是为什么在这种情况下很难使缓冲区溢出的原因。

购买堆栈溢出试试

#include "stdio.h"
#include "string.h"

int main()
{
     unsigned int  y      = (0xFFFFFFFF);
     char buffer[strlen("This string is 27 char long" + 1)];
      unsigned int  x      = (0xFFFFFFFF);
      sprintf(buffer, "This string is 27 char long");

      printf("X (%#x) is %#x, Y (%#x) is %#x, buffer '%s' (%#x) \n", &x, x,&y, y, buffer, buffer);
      return 0;
  }

你会看到Y已损坏。

答案 7 :(得分:0)

正如其他人所说,你完全正确地认为这是不好的,你没有看到这是填充的原因。在此尝试valgrind,这应该明确地发现错误。

答案 8 :(得分:0)

你真正的问题是你在写

char* buffer = new char[strlen("This string is 27 char long" + 1)];

而不是

char* buffer = new char[strlen("This string is 27 char long") + 1];

意思是在第一个上你给strlen()一个不是字符串开头的地址

试试这段代码:

const char szText[] = "This string is 27 char long";
char* buffer = new char[strlen(szText) + 1];
sprintf(buffer, szText);

答案 9 :(得分:0)

字符串在调试器中正常打印的原因是作为sprintf的一部分,尾随的NULL字符被写入内存(在这种情况下超出了您分配的缓冲区)以及何时读取字符串NULL字符存在以按预期终止字符串。

问题是包含NULL字符的字节尚未作为原始new的一部分进行分配,因此可以在以后用于不同的分配。在这种情况下,当您来之后阅读该字符串时,您可能会获得带有垃圾的原始字符串。

答案 10 :(得分:0)

正确的陈述。由于您将字符串的第二个字符的地址传递给strlen(),因此您得到的长度减去一个字符。除此之外,主要问题是sprintf(),这是它不安全的原因之一。

即使这样编译并执行(也可能会崩溃)。

    char* x = new char;
    sprintf(x, "This is way longer than one character");
    printf("%s", x);

为了避免这个危险的问题,您应该使用此函数的安全版本,如GCC下的snprintf()或asprintf()或MSVC下的sprintf_s()。

作为参考,请在这方面查看The GNU C Library documentation以及MSDN的sprintf()文章的安全说明。

相关问题