簡體   English   中英

這個緩沖區溢出的后果?

[英]Consequences of this 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 char”)。 我相信sprintf然后將27個字符打印到緩沖區並導致緩沖區溢出。

這是正確的評估嗎?

我編寫了一個測試應用程序來為我正在查看的代碼演示這個,並發現即使在調試器中字符串也會正確打印。 我還嘗試在此代碼之前和之后將其他變量放在堆棧和堆上,以查看是否可以影響鄰近的內存區域,但仍然收到正確的輸出。 我意識到我新分配的堆內存可能不相鄰,這可以解釋缺乏有用的溢出,但我真的想確認其他人的意見,如果這實際上是一個問題。

由於這是一個非常簡單的“問題”,如果你能通過某種參考來支持你的答案,那就太好了。 雖然我重視並歡迎您的意見,但我不會接受“是的”作為最終答案。 提前謝謝你。




更新:許多有很多額外見解的好答案。 不幸的是,我無法接受所有這些。 感謝您分享您的知識並成為我的“第二意見”。 我很感激幫助。

您的評估是正確的。 [編輯]加上James Curran提到的修正。[/ edit]

可能,您的測試應用程序沒有顯示問題,因為分配被四舍五入到下一個4,8或16的倍數(這是常見的分配粒度)。

這意味着您應該能夠使用31個字符長的字符串進行演示。

或者,使用“儀器化”本機內存分析器,它可以將保護字節緊密圍繞這樣的分配。

您的評估是正確的,除了springf將在緩沖區中放入28個字符,計算結束時字符串結尾的NUL(這就是為什么你首先需要放錯位置的“+1”)

請注意,根據我的經驗,如果某些內容在調試器之外失敗,但在調試器中逐步執行,則在100%的時間內,您已超出本地緩沖區。 調試器將更多內容推送到堆棧上,因此重寫的內容不太可能被覆蓋。

問題是你在內存中的某處寫入,但不在堆棧中。 因此,實際上很難看出什么是錯的。 如果您想查看損壞,請嘗試在堆棧上分配字符串

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

寫過去。 如果你真的知道二進制文件是如何工作的,你還可以添加一些代碼來執行。

要利用這樣的緩沖區溢出,您需要編寫所需的數據,然后找到一種“跳轉”到要執行的數據的方法。

是的,你是對的。 分配的緩沖區將是2個字節,太小而無法容納字符串。

由於這是在堆上分配的,因此可能會導致堆損壞。 但是,這種可能性取決於在此之前發生的其他內存分配和釋放以及使用的堆管理器。 有關更多信息,請參閱堆溢出

你是對的,這個例子中的指針算術會產生傳遞給new的不正確(較短)長度。 您無法實現此崩潰的最可能原因是因為內存分配實際提供了多少緩沖區空間存在一些不確定性。

允許該庫提供比請求的更大的緩沖區。 此外,您的緩沖區后面的任何內容都可能以受機器字對齊規則約束的分配標頭作為前綴。 這意味着在下一個分配頭之前最多可以有三個填充字節(取決於平台)。

即使你覆蓋了下一個分配頭(用於管理分配的內存塊),它也不會表現為問題,直到下一個塊的所有者試圖將其返回到堆中。

許多歷史性的malloc實現在分配的塊之前和/或之后立即存儲簿記數據。 你可能會覆蓋這樣的數據,在這種情況下你不會看到任何錯誤/崩潰,直到你試圖釋放內存(或者可能釋放下一個塊恰好是什么)。 同樣,后續分配的簿記信息可能會在以后覆蓋您的字符串。

我懷疑現代malloc實現通過使用完整性檢查數據填充分配來做出一些努力來防止堆損壞,所以如果你很幸運,沒有什么不好的事情發生,或者你可能會在以后的分配/自由操作中收到警告消息。

我嘗試使用堆分配,在這種情況下,變量在內存中不連續。 這就是為什么在這種情況下很難使緩沖區溢出的原因。

購買堆棧溢出嘗試它

#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已損壞。

正如其他人所說,你完全正確地認為這是不好的,你沒有看到這是填充的原因。 嘗試valgrind ,這應該明確地找到錯誤。

你真正的問題是你正在寫作

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);

字符串在調試器中打印正常的原因是,作為sprintf的一部分,尾隨的NULL字符被寫入內存(在這種情況下超出了您分配的緩沖區),並且當讀取字符串時,存在NULL字符按預期終止字符串。

問題是包含NULL字符的字節尚未作為原始new字符的一部分進行分配,因此可以在以后用於不同的分配。 在這種情況下,當您以后讀取字符串時,您可能會獲得附加了垃圾的原始字符串。

正確的陳述。 由於您將字符串的第二個字符的地址傳遞給strlen(),因此您得到的長度減去一個字符。 除此之外,主要問題是sprintf(),這是它不安全的原因之一。

即便如此編譯和執行(也可能崩潰)。

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

為了避免這個危險的問題,您應該使用此功能的安全版本,如GCC下的snprintf()或asprintf()或MSVC下的sprintf_s()。

作為參考,請查看這方面的GNU C庫文檔以及MSDN的sprintf()文章的安全說明。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM