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