許多漏洞發生的根本原因是對整數的漏洞處理。標準 int 類型可以從 0x7FFFFFFF 一直到 -0x80000000(注意負數),并帶有整數溢出。或者,它可以被截斷并將數字從正數更改為負數。整數在C語言中可能是一個噩夢,并且多年來造成了許多內存攻擊漏洞。
整數溢出漏洞(integer overflow):在計算機中,整數分為無符號整數以及有符號整數兩種。其中有符號整數會在最高位用0表示正數,用1表示負數,而無符號整數則沒有這種限制。另外,我們常見的整數類型有8位(單字節字符、布爾類型)、16位(短整型)、32位(長整型)等。關于整數溢出,其實它與其它類型的溢出一樣,都是將數據放入了比它本身小的存儲空間中,從而出現了溢出。由此引發的一切程序漏洞都可以成為整數溢出漏洞。
最近有一些發現引起了我的注意,它們屬于整數漏洞處理引起的漏洞。整數溢出也是一種常見的軟件漏洞,由此引發的漏洞可能比格式化字符串缺陷和緩沖區溢出缺陷更難于發現。溢出類型及表現:
1、溢出。只有符號的數才會發生溢出,對于signed整型的溢出,C的規范定義是“undefined behavior”,也就是說,編譯器愛怎么實現就怎么實現。對于大多數編譯器來說,仍然是回繞。
2、回繞。無符號數會回繞(常繞過一些判斷語句)。
3、截斷。將一個較大寬度的數存入一個寬度小的操作數中,高位發生截斷。
簡單了解整數溢出的危害:
1、整數回繞之后,會導致索引越界,取到不確定的數據。
2、 或者判斷失效,形成死循環。
3、回繞之后,導致分配超大內存。
需要指出的是 Qualys 發現的 Linux 內核中的整數截斷漏洞、GHSL 的 BSD 管理程序中的符號轉換漏洞以及 Checkpoint 發現的 Kindle 中緩沖區分配大小的整數溢出。然后,我的伙伴 seiraib 發現了一個整數溢出漏洞模糊測試,但我們無法找到溢出實際發生的位置。我想知道,這漏洞會在編譯時被發現嗎?還是會在運行時立即崩潰?“
GCC 和 Clang 有編譯標志,可以在編譯時查找幾個漏洞類。此外,還有一些運行時保護可能會導致崩潰,從而使 root 操作更容易。本文是關于通過編譯器警告和動態檢測發現漏洞的。所有帶有額外示例的源代碼片段都可以在 mdulin2/integer_compile_flags 中找到。
漏洞類
目標是以一種程序預期不會導致內存損壞的方式更改數字。C 語言中將重點關注三個數量(但主要是整數)漏洞類:
溢出/下溢:如果超過c中該類型的最大值或最小值,整數將簡單地環繞。例如,有符號整數的溢出將從0x7FFFFFFF一直到-0x80000000。下溢以相反的方向進行,是在減去數值時發生的。
截斷:縮小數字的存儲容量。例如,從 uint64_t 到 uint32_t 會將數字的存儲容量減少一半。這有可能徹底改變數字的值和符號。
有符號類型(signedness)轉換:數字要么是有符號的,要么是無符號的(例如 unsigned int 和 int)。當從負有符號數轉換為無符號數或從非常大的無符號數轉換為有符號數時,在這些之間進行轉換可能會產生可怕的后果。例如,將 0xFFFFFFFF 的無符號整數轉換為有符號整數將是 -1 而不是非常大的數字。
靜態分析
其中兩個漏洞類可以在編譯時通過特定的編譯標志來確定,盡管可能會出現大量不可利用的漏洞,但還是有一些很好的線索可以用來發現漏洞。
截斷
截斷代碼
有符號類型轉換代碼
在編譯期間使用Wconversion標志將輸出警告”可能改變值的隱式轉換“。這直接引用了截斷和轉換漏洞!
例如,截斷情況的代碼可以在圖 1 中看到。該代碼有一種正在轉換為 int 的 long long 類型。由于這會將存儲容量從 64 位更改為 32 位,因此可能會導致損壞。當使用Wconversion標志編譯此代碼時,將出現一條警告消息,指出截斷問題!這個警告可以在下圖中看到。當執行從size_t (unsigned long long)到int的轉換時,可以使用這個確切的漏洞消息來查找上面提到的Linux Kernel內核漏洞。
截斷警告消息
另一個需要考慮的有趣事項是float和double的情況。因為double是float大小的2倍,所以相同類型的截斷可以通過相同的編譯標志檢測到,此處顯示了代碼中的一個示例。
有符號類型
Wconversion標志也可以用于檢測符號轉換漏洞。當從有符號轉換為無符號或從無符號整數轉換為有符號整數時,就會發生這種情況,這兩個問題都顯示在上圖中。下圖顯示了編譯時漏洞消息的一個示例。
有符號類型轉換警告消息
靜態分析總結
這些標志只檢查隱式轉換。有時,需要將一個值從無符號整數轉換為有符號整數,以便進行一些數學計算,這是有效的和預期的C語言。當一個數字被顯式轉換時,這些漏洞消息將不會顯示,因此。為了找到顯式的強制類型轉換,可以使用類似于CodeQL的東西、手動檢查或動態測試。
盡管到目前為止我們只使用 Wconversion 進行靜態分析,但實際上構成了這個單一標志的標志過多。例如,Wsign-conversion 標志警告可能會更改整數值符號的隱式轉換。有關這些的更多信息,請訪問 GCC 文檔。
動態儀表
靜態分析很可能會得到大量誤報,不過其中還是有一些真正的漏洞。然而,如果可以觸發代碼路徑,動態分析總是能找到真正的漏洞。下面,我們將展示在與整數相關的漏洞上崩潰/通知的檢測選項。我們將討論GCC和Clang中的一些特定標志,而不是討論漏洞類。
ftrapv
此選項為加法、減法和乘法運算的有符號溢出生成漏洞。同樣,由于這是動態測試,因此每當發生這種情況時都會導致程序崩潰!可以在下面看到此代碼的示例:
當行a = a + 1時,上面的代碼將導致整數溢出;,因為C中整數的最大大小是0x7FFFFFFF。會發生什么呢?程序被終止,并且永遠不會到達print語句。通過在發生溢出時強制崩潰,我們可以保證檢測到漏洞,并且更容易找到溢出的原因。這在模糊測試和嘗試找到崩潰根本原因時非常有用。
還應該注意的是,該標志也可以檢測有符號整數下溢。
fsanitize=integer
用于查找整數漏洞的 UBSAN(未初始化行為清理器)非常棒!此標志特定于 Clang,用于與整數相關的漏洞。
雖然ftrapv只捕獲有符號整數溢出,但fsanitize=integer將在無符號整數和有符號整數溢出(有符號整數溢出和無符號整數溢出)時崩潰。這意味著所有整數溢出,無論符號如何,通過加法、減法或乘法都將在運行時被捕獲。
除了發現程序中的溢出/下溢外,我們還可以找到用這個標志提到的另外兩個漏洞類:截斷(隱式有符號整數截斷和隱式無符號整數截斷)和轉換(隱式整數符號改變)。與上面的靜態方法不同,必須發生一個糟糕的數學運算才能觸發UBSAN使程序崩潰。
不過,讓我們看看它的實際效果!我們將使用原始的符號問題。當運行代碼時,值為LONG_MAX (0x7FFFFFFFFFFFFFFF)的long long將由于轉換為整數而被分為兩半(被截斷)。因此,這將是0xFFFFFFFF或-1作為有符號整數。由于添加了額外的檢測,程序在截斷發生時崩潰。這樣,設備就不需要改變標志,它檢查long long中的值是否適合int。這個崩潰可以在下圖中看到。
截斷轉換崩潰
我們已經提到了對整數溢出/下溢、整數截斷和符號轉換問題的動態和靜態檢查。但是,動態檢測缺少一件事:浮點數學運算。
當浮點數學運算在 C 中溢出時,它會變為無窮大或 inf。奇怪的是,由于浮點數學處理精度的方式,浮點數從未真正發生環繞;它只是趨近于無窮大!可以在此處查看此溢出的示例。
另一個未捕獲的漏洞是浮動截斷。例如,在運行時不會捕獲從 double 到 float 的轉換。有一個誤導性的 UBSAN 標志 (-fsanitize=float-cast-overflow),它只能發現漏洞的雙精度/浮點數到整數的轉換,但不會發現浮點數之間的截斷漏洞。這方面的一個例子可以在這里看到。
了解浮點數變為 inf 和 NaN 可能很有用,例如 Unreal 游戲引擎中的 Jack Bakers NaN 傳播漏洞確實會發生。但是,目前沒有檢測到 C 語言中浮點數在運行時的溢出、下溢、截斷或 NaN/Inf 使用。
總結
在嘗試查找漏洞時,任何來自自動化工具或儀器的幫助都非常有用。除了上面提到的整數漏洞類之外,還有許多其他有趣的標志和工具,例如眾所周知的 ASAN(地址清理器),還有許多其他編譯標志和檢測選項可以幫助查找特定的漏洞類,本文只關注查找與整數相關的漏洞類。