简体   繁体   English

这个缓冲区溢出的后果?

[英]Consequences of this buffer overflow?

So here I believe I have a small buffer overflow problem I found when reviewing someone else's code. 所以在这里我相信我在查看其他人的代码时发现了一个小的缓冲区溢出问题。 It immediately struck me as incorrect, and potentially dangerous, but admittedly I couldn't explain the ACTUAL consequences of this "mistake", if any. 它立刻让我觉得不正确,而且有潜在危险,但不可否认,我无法解释这个“错误”的实际后果,如果有的话。

I had written up a test app to demonstrate the error, but found (to my dismay) that it seems to run correctly regardless of the overflow. 我写了一个测试应用程序来演示错误,但发现(令我沮丧的是)无论溢出如何,它似乎都能正常运行。 I want to believe that this is just by chance, but wanted some feedback to determine if my thinking were wrong, or if there truly is a problem here that just isn't showing its head in my test app. 我想相信这只是偶然的,但是需要一些反馈来确定我的想法是否错误,或者是否真的存在问题,而不是在我的测试应用中显示出来。

The problem code (I think it is, anyway): 问题代码(我认为无论如何):

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

Now, the reason this stood out to me and I want to flag it as a possible buffer overflow is because of the first strlen . 现在,这对我来说很突出,我想把它标记为可能的缓冲区溢出是因为第一个strlen Due to pointer arithmetic, the 'incorrect' placement of the + 1 will cause the strlen to return 26 instead of 27 (taking the length of "his string is 27 char long"). 由于指针运算, + 1的'不正确'位置将导致strlen返回26而不是27 (取“他的字符串长度为27 char”)。 sprintf , I believe, then prints 27 char into the buffer and has caused a buffer overflow. 我相信sprintf然后将27个字符打印到缓冲区并导致缓冲区溢出。

Is that a correct assessment? 这是正确的评估吗?

I wrote a test app to demonstrate this for the person who's code I was looking at, and found that even in the debugger the string will print correctly. 我编写了一个测试应用程序来为我正在查看的代码演示这个,并发现即使在调试器中字符串也会正确打印。 I also attempting putting other variables on the stack and heap before and after this code to see if I could affect neighboring areas of memory, but was still receiving correct output. 我还尝试在此代码之前和之后将其他变量放在堆栈和堆上,以查看是否可以影响邻近的内存区域,但仍然收到正确的输出。 I realize that my newly allocated heap memory might not be adjacent, which would explain the lack of useful overflow, but I just really wanted to confirm with others' opinions if this is in fact an issue. 我意识到我新分配的堆内存可能不相邻,这可以解释缺乏有用的溢出,但我真的想确认其他人的意见,如果这实际上是一个问题。

Since this is a pretty simple "question", it'd be nice if you could support your answer with some sort of reference as well. 由于这是一个非常简单的“问题”,如果你能通过某种参考来支持你的答案,那就太好了。 While I value and welcome your input, I'm not going to accept "yes it is" as the final answer. 虽然我重视并欢迎您的意见,但我不会接受“是的”作为最终答案。 Thank you kindly in advance. 提前谢谢你。




Update: Many good answers with a lot of additional insight. 更新:许多有很多额外见解的好答案。 Unfortunately, I can't accept them all. 不幸的是,我无法接受所有这些。 Thank you for sharing your knowledge and for being my 'second opinion'. 感谢您分享您的知识并成为我的“第二意见”。 I appreciate the help. 我很感激帮助。

Your assessment is correct. 您的评估是正确的。 [edit] with the addition of the correction mentioned by James Curran.[/edit] [编辑]加上James Curran提到的修正。[/ edit]

Likely, your test app didn't show the problem because the allocation is rounded up to the next multiple of 4, 8 or 16 (which are common allocation granularities). 可能,您的测试应用程序没有显示问题,因为分配被四舍五入到下一个4,8或16的倍数(这是常见的分配粒度)。

This means you should be able to demonstrate with a 31 character long string. 这意味着您应该能够使用31个字符长的字符串进行演示。

Alternatively, use an "instrumenting" native memory profiler that can place guard bytes closely around such an allocation. 或者,使用“仪器化”本机内存分析器,它可以将保护字节紧密围绕这样的分配。

You assessment is correct, except that the springf will put 28 characters in the buffer counting the end-of-string NUL at the end (That's why you needed the misplaced "+1" in the first place) 您的评估是正确的,除了springf将在缓冲区中放入28个字符,计算结束时字符串结尾的NUL(这就是为什么你首先需要放错位置的“+1”)

Note that in my experiences, if something fails outside of a debugger, but works with stepping through in the debugger, in 100% of the time, you've overrun a local buffer. 请注意,根据我的经验,如果某些内容在调试器之外失败,但在调试器中逐步执行,则在100%的时间内,您已超出本地缓冲区。 Debuggers push a lot more onto the stack, so it's less likely the something important was overwritten. 调试器将更多内容推送到堆栈上,因此重写的内容不太可能被覆盖。

The problem is that you are writing somewhere in the memory, but not on the stack. 问题是你在内存中的某处写入,但不在堆栈中。 Therefore, it's hard to actually see what's wrong. 因此,实际上很难看出什么是错的。 If you want to see the damages, try allocating the string on the stack 如果您想查看损坏,请尝试在堆栈上分配字符串

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

and the write past it. 写过去。 Other variables will be written, you can also add some code to be executed if you really know how the binary works. 如果你真的知道二进制文件是如何工作的,你还可以添加一些代码来执行。

To exploit a buffer overflow like that, you need to write the data you want, then find a way to "jump" to this data to be executed. 要利用这样的缓冲区溢出,您需要编写所需的数据,然后找到一种“跳转”到要执行的数据的方法。

Yes, you are correct. 是的,你是对的。 The buffer allocated will be 2 bytes too small to hold the string. 分配的缓冲区将是2个字节,太小而无法容纳字符串。

Since this is being allocated on the heap, it would be possible for this to result in a heap corruption. 由于这是在堆上分配的,因此可能会导致堆损坏。 However, the liklihood of that depends on the what other allocations and releases of memory have occurred prior to this point and also on heap manager being used. 但是,这种可能性取决于在此之前发生的其他内存分配和释放以及使用的堆管理器。 See Heap Overflow for more. 有关更多信息,请参阅堆溢出

You are correct that pointer arithmetic in this example would produce an incorrect (shorter) length passed to new. 你是对的,这个例子中的指针算术会产生传递给new的不正确(较短)长度。 The most probable reason why you are not able to make this crash is because there is some uncertainty as to how much buffer space is actually provided by the memory allocation. 您无法实现此崩溃的最可能原因是因为内存分配实际提供了多少缓冲区空间存在一些不确定性。

The library is allowed to provide a larger buffer than was requested. 允许该库提供比请求的更大的缓冲区。 Furthermore, it is also possible that whatever follows your buffer is prefixed by an allocation header that is subject to machine word alignment rules. 此外,您的缓冲区后面的任何内容都可能以受机器字对齐规则约束的分配标头作为前缀。 This means there could be up to three padding bytes (depending on platform) before the very next allocation header. 这意味着在下一个分配头之前最多可以有三个填充字节(取决于平台)。

Even if you overwrote the next allocation header (which is used to manage the allocated memory blocks) it would not manifest itself as a problem until the owner of that next block attempted to return it to the heap. 即使你覆盖了下一个分配头(用于管理分配的内存块),它也不会表现为问题,直到下一个块的所有者试图将其返回到堆中。

Many historic malloc implementations put bookkeeping data immediately before and/or after the allocated block. 许多历史性的malloc实现在分配的块之前和/或之后立即存储簿记数据。 It's possible that you're overwriting such data, in which case you would not see any error/crash until you try to free the memory (or perhaps free whatever the next block happens to be). 你可能会覆盖这样的数据,在这种情况下你不会看到任何错误/崩溃,直到你试图释放内存(或者可能释放下一个块恰好是什么)。 Likewise, it's possible that the bookkeeping information for a subsequent allocation will later overwrite your string. 同样,后续分配的簿记信息可能会在以后覆盖您的字符串。

I suspect modern malloc implementations make some effort to protect against heap corruption by padding allocations with integrity-check data, so if you're lucky, nothing bad will happen or you might get a warning message during a later allocation/free operation. 我怀疑现代malloc实现通过使用完整性检查数据填充分配来做出一些努力来防止堆损坏,所以如果你很幸运,没有什么不好的事情发生,或者你可能会在以后的分配/自由操作中收到警告消息。

I tried it with heap allocations, variables are not continuous in memory in this case. 我尝试使用堆分配,在这种情况下,变量在内存中不连续。 That is why it is hard to make buffer overflow in this case. 这就是为什么在这种情况下很难使缓冲区溢出的原因。

Buy try it with stack overflow 购买堆栈溢出尝试它

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

You will see that Y is corrupted. 您将看到Y已损坏。

As stated by others, you are completely correct in assuming that this is no good, and the reason you don't see this is padding. 正如其他人所说,你完全正确地认为这是不好的,你没有看到这是填充的原因。 Try valgrind on this, this should definitively find that error. 尝试valgrind ,这应该明确地找到错误。

Your real problem is that you're writing 你真正的问题是你正在写作

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

instead of 代替

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

Meaning that on the first one you're giving strlen() an address which isn't the beginning of your string . 这意味着在第一个上你给strlen()一个不是字符串开头的地址

Try this code: 试试这段代码:

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

The reason the string is printing fine in the debugger is that as part of the sprintf, the trailing NULL character is being written to memory (in this case beyond the buffer you allocated) and when it comes to reading the string the NULL character is present to terminate the string as expected. 字符串在调试器中打印正常的原因是,作为sprintf的一部分,尾随的NULL字符被写入内存(在这种情况下超出了您分配的缓冲区),并且当读取字符串时,存在NULL字符按预期终止字符串。

The problem is that the byte containing the NULL character hasn't been allocated as part of the original new and so could be used for a different allocation later. 问题是包含NULL字符的字节尚未作为原始new字符的一部分进行分配,因此可以在以后用于不同的分配。 In this case, when you come to read the string afterwards you will likely get your original string with garbage appended. 在这种情况下,当您以后读取字符串时,您可能会获得附加了垃圾的原始字符串。

Correct statement. 正确的陈述。 Since you are passing address of the second character of the string to strlen(), you are getting the length one character less as a result. 由于您将字符串的第二个字符的地址传递给strlen(),因此您得到的长度减去一个字符。 Aside from that, the main problem is with sprintf(), that's one of the reasons that it's not safe. 除此之外,主要问题是sprintf(),这是它不安全的原因之一。

Even this compiles and executes (may also crash). 即便如此编译和执行(也可能崩溃)。

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

In order to avoid this dangerous issue, you should use safe versions of this function like snprintf() or asprintf() under GCC or sprintf_s() under MSVC. 为了避免这个危险的问题,您应该使用此功能的安全版本,如GCC下的snprintf()或asprintf()或MSVC下的sprintf_s()。

As references, please have a look at The GNU C Library documentation in this regard and also security note of MSDN's sprintf() article. 作为参考,请查看这方面的GNU C库文档以及MSDN的sprintf()文章的安全说明。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM