[英]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
所述n
在strncpy
並不意味着一樣n
在strncat
或snprintf
; strncpy
是為處理固定大小的緩沖區字符串(例如目錄項)而生的,因此它最多復制n個字符,並用NUL填充未使用的字符( =字節0 = '\\0'
=空字符= ... ),但是如果存在沒有多余的,它不會添加任何NUL。 因此, strncpy
的目標不一定要以NUL終止,因此,如果您嘗試將其作為C字符串進行操作,您會感到有些意外。
這正是在這種情況下發生的情況。 您的a
和b
緩沖區的長度與您要在其中復制的字符串的長度完全相同; strncpy
不會以NUL終止它們,因此,當第三個strncpy
或更高版本的printf
嘗試從它們讀取時,結果是任何人的猜測(讀取:這是未定義的行為,因此可能發生任何事情),因為沒有NUL阻止它們繼續在無關的內存中讀取。
至於您獲得的特定輸出,取決於a
, b
和c
在內存中的布局方式(實際上,在我的機器上,我得到不同的結果), strncpy
的確切編寫方式(因為它不一定要被調用)以及重疊的字符串)以及優化器決定如何處理代碼的方式(請記住:在邊界之外讀取是未定義的行為,因此允許優化器假定它在重新排列代碼時從未發生)。
您所看到的實際行為的可能解釋是c
, a
和b
以此順序連續排列在內存中,而堆棧的其余部分恰好是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
繼續讀取c
, a
和b
全長(其末尾是NUL,它將停止打印),打印64個字符。 請記住:這只是對您顯示的輸出的可能解釋。 這不一定是正確的,當然也不是契約性的(在我的機器上,根據編譯標志,甚至在不同的運行情況下,根據啟動時堆棧上的內容,我都會獲得不同的輸出)。
超出字符串末尾的讀取是未定義的行為(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.