簡體   English   中英

c字符串漏洞

[英]c-String vulnerabilities

我在閱讀有關C語言字符串中的漏洞的信息,然后遇到了這段代碼。 誰能給我一個解釋為什么發生這種情況? 提前致謝。

int main (int argc, char* argv[]) {
  char a[16];
  char b[16];
  char c[32];
  strncpy(a, "0123456789abcdef", sizeof(a));
  strncpy(b, "0123456789abcdef", sizeof(b));
  strncpy(c, a, sizeof(c));

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}

輸出:

a = 0123456789abcdef0123456789abcdef
b = 0123456789abcdef
c = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

所述nstrncpy並不意味着一樣nstrncatsnprintf ; strncpy是為處理固定大小的緩沖區字符串(例如目錄項)而生的,因此它最多復制n個字符,並用NUL填充未使用的字符( =字節0 = '\\0' =空字符= ... ),但是如果存在沒有多余的,它不會添加任何NUL。 因此, strncpy的目標不一定要以NUL終止,因此,如果您嘗試將其作為C字符串進行操作,您會感到有些意外。

這正是在這種情況下發生的情況。 您的ab緩沖區的長度與您要在其中復制的字符串的長度完全相同; strncpy不會以NUL終止它們,因此,當第三個strncpy或更高版本的printf嘗試從它們讀取時,結果是任何人的猜測(讀取:這是未定義的行為,因此可能發生任何事情),因為沒有NUL阻止它們繼續在無關的內存中讀取。

至於您獲得的特定輸出,取決於abc在內存中的布局方式(實際上,在我的機器上,我得到不同的結果), strncpy的確切編寫方式(因為它不一定要被調用)以及重疊的字符串)以及優化器決定如何處理代碼的方式(請記住:在邊界之外讀取是未定義的行為,因此允許優化器假定它在重新排列代碼時從未發生)。

您所看到的實際行為的可能解釋是cab以此順序連續排列在內存中,而堆棧的其余部分恰好是NUL填充的(這里我使用°作為占位符對於NUL 1 ):

°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°...
c                               a               b               ?

所以發生的事情應該是這樣的:

  • 0123456789abcdef在復制a ,而沒有任何NUL終止,因為它達到允許的最大的字符(16)。

     °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef°°°°°°°°°°°°°°°°°°°°... cab ? 
  • 0123456789abcdef復制到b ,沒有任何NUL終止(與以前相同)。

     °°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°0123456789abcdef0123456789abcdef°°°°... cab ? 
  • a復制到c 由於a並非NUL終止, strncpy繼續愉快地直接讀取b的空間,復制了允許復制的32個完整字符。 當達到32個字符時,不會寫入任何NUL。

     0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef°°°°... cab ? 
  • a已打印; 因為它沒有NUL終止,所以printf繼續讀取后面的內存b ,從而打印32個字符。

  • b已打印; 它沒有被明確地終止為NUL,但是它之后的內存恰好包含一個NUL,因此它在復制了16個字符后停止。
  • c已打印; 因為它不是NUL終止的,所以printf繼續讀取cab全長(其末尾是NUL,它將停止打印),打印64個字符。

請記住:這只是對您顯示的輸出的可能解釋。 這不一定是正確的,當然也不是契約性的(在我的機器上,根據編譯標志,甚至在不同的運行情況下,根據啟動時堆棧上的內容,我都會獲得不同的輸出)。


  1. 我很想使用␀符號,但通常即使在代碼塊中也以非固定寬度呈現它,從而破壞了ASCII藝術。

超出字符串末尾的讀取是未定義的行為(UB)。 使用UB不能保證代碼以一種或另一種方式運行。 在不同的系統,編譯器,鏈接器,編譯/鏈接標志上,此行為可能有所不同,具體取決於(看似)不相關的代碼以及上述所有版本。

在許多系統上,變量以相反的順序連續地位於堆棧上。 將您的printf替換為:

printf("a (%p) = %s\n", a, a);
printf("b (%p) = %s\n", b, b);
printf("c (%p) = %s\n", c, c);

它打印數組的地址:

a (0x7fff559adad0) = 0123456789abcdef<F0>ښU<FF>
b (0x7fff559adac0) = 0123456789abcdef0123456789abcdef<F0>ښU<FF>
c (0x7fff559adaa0) = 0123456789abcdef<F0>ښU<FF>

從地址可以明顯看出,打印輸出b開始於地址0x7fff559adac0但繼續順利進入的地址a (其開始之后開始16個字節b )。

另請注意,字符串末尾有垃圾。 只是因為字符串中缺少'\\ 0'終止符,而printf繼續讀取以下垃圾(UB本身就是這樣)。

發生這種情況是因為:

strncpy(a, "0123456789abcdef", sizeof(a));

設置a []使其所有字節等於“ 0123456789abcdef”而沒有空終止符。 沒有'\\ 0'printf時,不知道從哪里停止,這將導致UB。

strncpy(b, "0123456789abcdef", sizeof(b));

還設置b []使其所有字節等於“ 0123456789abcdef”而沒有空終止符。 同樣在這里,任何printf都會導致UB。 但是這次,它不是讀取隨機垃圾,而是讀取下一個字符串。

為了加重侮辱的傷害,行

 strncpy(c, a, sizeof(c));

從16字節數組中讀取32字節。 這也是UB。 在你(和我)系統,它讀取a后和大量的垃圾。 從理論上講,這可能會使您的程序因訪問沖突或分段錯誤而崩潰。

一些病毒和蠕蟲使用此類溢出來讀取或寫入不應有的數據。

strncpy是一個危險的函數,因為它僅在剩余空間時才添加空終止。 這就是代碼中發生的情況,您恰好復制了16個字節

實際上, strncpy函數從不打算用於C字符串,而是用於不使用空終止的古老Unix字符串格式。 對於大多數目的,應避免使用此功能。 特別是,它不是 “ strcpy的安全版本”,而是比strcpy更危險的功能,正如我們從此處的錯誤中可以看到的那樣。

解決方法是在復制之前先檢查要復制的尺寸。 然后使用strcpy。 例如:

char a[16];
const char to_copy[] = "0123456789abcdef";
_Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
strcpy(a, to_copy);

要修復當前程序,您需要為空終止符分配空間,如下所示:

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

int main (void)
{
  char a[16+1];
  char b[16+1];
  char c[32+1];
  const char to_copy[] = "0123456789abcdef";
  _Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
  _Static_assert(sizeof(to_copy) <= sizeof(b), "to_copy is too big");

  strcpy(a, to_copy);
  strcpy(b, to_copy);
  strcpy(c, a);

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}

暫無
暫無

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

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