漫談漏洞挖掘
前言
說(shuō)到安全就不能不說(shuō)漏洞,而說(shuō)到漏洞就不可避免地會(huì)說(shuō)到三座大山:
?漏洞分析?漏洞利用?漏洞挖掘
從筆者個(gè)人的感覺(jué)上來(lái)看,這三者盡管通常水乳交融、相互依賴,但難度是不盡相同的。本文就這三者分別談?wù)勛约旱慕?jīng)驗(yàn)和想法。
漏洞分析
漏洞分析相對(duì)簡(jiǎn)單,通常公開(kāi)的漏洞中就有一兩句話描述了漏洞的成因,自己拉代碼下來(lái)看也就能了解個(gè)大概。對(duì)于一些自己發(fā)現(xiàn)的bug,從崩潰日志中一般也能比較輕松地進(jìn)行復(fù)現(xiàn)和調(diào)試。盡管有的bug排查起來(lái)相對(duì)繁瑣,但總歸是可以一步步減少范圍鎖定最終目標(biāo)的。因此,網(wǎng)上對(duì)于漏洞分析的文章很多,一方面分析起來(lái)有跡可循,另一方面分析的漏洞也不一定是自己的“原創(chuàng)”漏洞,素材來(lái)源更加廣泛。
漏洞分析雖然簡(jiǎn)單,卻是每個(gè)安全研究人員的必經(jīng)之路。就像練武中的扎馬步、站梅花樁一樣,是日積月累的基本功。之前研究?jī)?nèi)核時(shí)有段時(shí)間熱衷于寫(xiě)漏洞分析的文章,后來(lái)隨著日漸熟練,寫(xiě)文章記錄的速度已經(jīng)遠(yuǎn)遠(yuǎn)跟不上分析的進(jìn)度,所以現(xiàn)在往往懶得動(dòng)筆了。
基本功必不可少,但扎馬步扎得再穩(wěn)也不表示你能獨(dú)步武林。有大佬曾經(jīng)說(shuō)過(guò),如果他想的話,可以一天寫(xiě)好幾篇分析文章還不帶重樣的。畢竟,漏洞分析的目的是為了學(xué)習(xí)、吸收、轉(zhuǎn)化,以史為鑒,最終形成自己獨(dú)到的理解。
漏洞利用
漏洞利用就相對(duì)復(fù)雜一點(diǎn),尤其是對(duì)于二進(jìn)制漏洞,成功的利用需要精巧的內(nèi)存布局,因此需要對(duì)程序涉及到的數(shù)據(jù)結(jié)構(gòu)要相當(dāng)?shù)牧私?。而且并不是所有漏洞都能轉(zhuǎn)換為有效利用的,一般比較容易編寫(xiě)利用的漏洞,我們稱之為品相好。對(duì)于品相不好的漏洞,我更喜歡將其稱之為bug。當(dāng)然也有人認(rèn)為 bug 至少造成了程序崩潰,所以可以算一個(gè)DoS(拒絕服務(wù))漏洞。
當(dāng)然漏洞能否利用其實(shí)也是和人有關(guān)。對(duì)于復(fù)雜的系統(tǒng),你認(rèn)為無(wú)法利用的漏洞,大佬就能以一種你沒(méi)想到的方式利用成功。比如安卓CVE-2019-2025(水滴)[1]漏洞,屬于Binder中的一個(gè)條件競(jìng)爭(zhēng),競(jìng)爭(zhēng)窗口只有幾條匯編指令。漏洞品相相當(dāng)不好,連CVSS給出的可利用分?jǐn)?shù)(Exploitability Score)也只有1.8分,但360的大佬們也通過(guò)玩弄調(diào)度器進(jìn)行穩(wěn)定利用提權(quán)了。
因此,關(guān)于漏洞利用的文章也就少了很多。一方面處于負(fù)責(zé)任披露安全問(wèn)題的考慮,安全研究人員不會(huì)給出完整的利用細(xì)節(jié),以免腳本小子濫用;另一方面對(duì)于公開(kāi)的利用,你也總不能跟著寫(xiě)一篇文章灌水,畢竟利用思路很多時(shí)候是因人而異的,過(guò)于雷同就難免有炒冷飯的嫌疑,除非有一些獨(dú)到的思考補(bǔ)充,或者有新的利用思路。
很多時(shí)候漏洞利用的文章看著看著就變成了漏洞利用分析的文章,這也說(shuō)明了漏洞利用難度頗高,能獨(dú)立寫(xiě)出原創(chuàng)利用并進(jìn)行分享的人不多。就我的感覺(jué)而言,漏洞利用更像是另一種形式的軟件開(kāi)發(fā),首先通過(guò)漏洞構(gòu)造原語(yǔ),然后通過(guò)原語(yǔ)實(shí)現(xiàn)最終的利用程序。
漏洞挖掘
漏洞挖掘可以說(shuō)是安全研究人員向往的高地之一,不管你分析了多少漏洞,寫(xiě)了多少利用,如果你沒(méi)有自己挖掘出過(guò)原創(chuàng)的漏洞,那你的安全研究生涯就是不完整的。但是漏洞挖掘這事兒并不是確定性的。漏洞分析只要有漏洞肯定能分析清楚,只不過(guò)是時(shí)間問(wèn)題;漏洞利用只要不是明顯的無(wú)法利用,那至少也存在利用成功的可能性。
漏洞挖掘則不然,即便你盯著某個(gè)應(yīng)用使勁挖,也不能保證有結(jié)果,說(shuō)不定對(duì)方根本就沒(méi)有能觸發(fā)的漏洞。都說(shuō)世上沒(méi)有絕對(duì)安全的系統(tǒng),但是相對(duì)安全的系統(tǒng)一抓一大把,至少在出問(wèn)題之前,你是不知道的。
我們能看到各種安全會(huì)議中介紹各種新發(fā)現(xiàn)的漏洞和問(wèn)題,網(wǎng)上也有很多相關(guān)的文章,但更多是炫技式分享(show-off),很少有分享怎么挖洞的。因此筆者就先拋磚引玉,談?wù)勛约旱南敕ā?/p>
Fuzzing
自從 AFL 橫空出世之后,當(dāng)今漏洞挖掘言必稱 Fuzzing,仿佛這是李云龍他娘的意大利炮,不管三七二十一先來(lái)上一發(fā)就能轟出幾個(gè) 0day。當(dāng)然,我不是說(shuō) Fuzz 不好,只不過(guò)凡事都有其誕生和得以應(yīng)用的環(huán)境。
Fuzzing 即模糊測(cè)試,在早期是 QA 測(cè)試中的一項(xiàng)黑盒測(cè)試技術(shù),通過(guò)隨機(jī)變異的輸入來(lái)測(cè)試程序的魯棒性。在程序的崩潰被用于漏洞利用后,也就一躍成為一種漏洞挖掘方法。隨機(jī)變異輸入的效率相對(duì)低下,可能變異了半天連 main 函數(shù)里第一個(gè) if 都沒(méi)有跳進(jìn)去。AFL 率先提出并實(shí)現(xiàn)了根據(jù)路徑/覆蓋率等反饋來(lái)進(jìn)行輸入的變異,從而大大提升了測(cè)試用例的有效性。

自從 2013 年 AFL 提出以來(lái),各類 Fuzzer 百花齊放,在學(xué)術(shù)上有了爆發(fā)性的論文增長(zhǎng);開(kāi)源社區(qū)有 honggfuzz[2]、libFuzzer[3] 等優(yōu)秀的項(xiàng)目;在工程上有 OSS-Fuzz[4] 利用龐大機(jī)器集群進(jìn)行持續(xù)測(cè)試的應(yīng)用和 syzkaller[5] 這種年均發(fā)現(xiàn)幾千內(nèi)核漏洞的工具等。
從產(chǎn)出的漏洞數(shù)量來(lái)看,F(xiàn)uzzing 作為一種漏洞挖掘方法可謂一騎絕塵。當(dāng)時(shí),隨著時(shí)間的推移,通用模糊測(cè)試策略已經(jīng)越來(lái)越難發(fā)現(xiàn)新的漏洞,要么你有獨(dú)特的測(cè)試語(yǔ)料,要么你有領(lǐng)先的運(yùn)算資源。因此,一部分人就從通用 Fuzzer 轉(zhuǎn)回到了針對(duì)特定目標(biāo)變異的 Fuzzer(即 Structure Aware Fuzzer)。也就是說(shuō),只針對(duì)目標(biāo)程序接受的數(shù)據(jù)類型進(jìn)行特定變異,比如針對(duì) PDF 文件格式每個(gè)字段變異等等。
別忘了,AFL 這種通用 Fuzzer 的出現(xiàn),就是為了實(shí)現(xiàn)一次編碼,到處 Fuzz 的目的;而針對(duì)不同目標(biāo)去寫(xiě) Fuzzer,顯然有違初衷。另外,如果目標(biāo)接受的是已知格式的輸入還好,對(duì)于未知格式,還需要自己去分析和理解各個(gè)字段含義。在閱讀代碼和理解代碼邏輯的過(guò)程中,目標(biāo)程序的潛在問(wèn)題很可能已經(jīng)出現(xiàn)在你眼前了,再去編寫(xiě)一個(gè)不能復(fù)用的 Fuzzer,顯得有些多此一舉。這種發(fā)現(xiàn)漏洞的方法也就是下節(jié)所說(shuō)的——代碼審計(jì)。
代碼審計(jì)
代碼審計(jì),俗稱看代碼。有的人用 SourceInsight 看,有的人用 VIM 看,但不管怎么樣還是用眼睛看。既然大家都長(zhǎng)了兩只眼睛,為什么有的人就能一個(gè)月看出十幾個(gè)高危,有的人就只看了個(gè)寂寞?
看代碼也是有方法的。雖然我比較想聽(tīng)聽(tīng) @oldfresher 是怎么看代碼的,但他似乎不太愿意分享,所以我只能說(shuō)說(shuō)自己的方法。
要挖漏洞首先要對(duì)漏洞進(jìn)行分類,大致可以分為下面三種:
?設(shè)計(jì)漏洞?實(shí)現(xiàn)漏洞?操作漏洞
所謂設(shè)計(jì)漏洞就是軟件設(shè)計(jì)過(guò)程中就存在邏輯問(wèn)題,比如 WiFi 的 WEP,設(shè)計(jì)的問(wèn)題往往是比較嚴(yán)重的,而且修復(fù)周期長(zhǎng),所幸這類問(wèn)題不是太多;實(shí)現(xiàn)漏洞就是我們常見(jiàn)的軟件漏洞,內(nèi)存溢出、UAF、條件競(jìng)爭(zhēng)等都算在里面;操作漏洞和實(shí)現(xiàn)漏洞往往類似,只不過(guò)更多是指配置錯(cuò)誤而產(chǎn)生的漏洞,比如 nginx 配置錯(cuò)誤導(dǎo)致的目錄穿越問(wèn)題就屬于一種操作漏洞。
明確漏洞類型后也不是馬上開(kāi)始看代碼,而是先進(jìn)行初步的攻擊面分析,也就是常說(shuō)的威脅建模。主要分為下面幾步:
?信息收集: 收集所有相關(guān)的資料,尤其是設(shè)計(jì)文檔或者幫助手冊(cè)等?應(yīng)用架構(gòu)建模: 列舉應(yīng)用所含的組件以及狀態(tài)機(jī),注意各個(gè)組件之間隱含的信任關(guān)系?威脅鑒定: 在各個(gè)流程中列舉潛在的攻擊點(diǎn),并分別標(biāo)記危害等級(jí)?審計(jì)計(jì)劃: 按優(yōu)先級(jí)對(duì)實(shí)現(xiàn)的審計(jì)計(jì)劃進(jìn)行排序
代碼審計(jì)聽(tīng)起來(lái)是個(gè)讓人退縮任務(wù),因?yàn)槟忝鎸?duì)的是一堆你不熟悉的代碼,而且要求你快速地建立起自己的理解而且找到其中最深層的秘密(安全漏洞)。通常你不會(huì)有足夠的時(shí)間審計(jì)項(xiàng)目的每一行代碼,因此這其中的一個(gè)重點(diǎn)就是懂得如何分配精力,在最可能出現(xiàn)安全問(wèn)題的代碼中達(dá)到滿意的審計(jì)覆蓋率。
成功的代碼審計(jì)過(guò)程一定是務(wù)實(shí)、靈活且面向結(jié)果的。雖然講究方法,但并不意味著根據(jù)別人的方法就能成功。與很多人的認(rèn)知不同,代碼審計(jì)其實(shí)是一件需要?jiǎng)?chuàng)造力的技能。那位說(shuō)了,看別人代碼要什么創(chuàng)造力?要在應(yīng)用中找到漏洞,審計(jì)者需要將自己代入作者的思維中理解代碼,同時(shí)還需要跳出作者的思維,洞察到原作者沒(méi)有預(yù)料到的可能性;這種技能與知識(shí)相對(duì),像是騎車、游泳、彈琴一樣,是需要通過(guò)學(xué)習(xí)和練習(xí)去掌握的,而一旦掌握就像本能的一部分難以忘記。當(dāng)然代碼審計(jì)也有知識(shí)的部分,比如需要了解各種漏洞類型和場(chǎng)景等。
這些都這只是代碼審計(jì)中的冰山一角,篇幅原因無(wú)法面面俱到。比如代碼審計(jì)策略的抉擇,是深度優(yōu)先還是廣度優(yōu)先(看到某個(gè)函數(shù)調(diào)用是否需要追進(jìn)去看);以及使用現(xiàn)代的靜態(tài)分析工具來(lái)輔助審計(jì),比如 Fority、CodeQL 等,后續(xù)有時(shí)間再單獨(dú)進(jìn)行介紹吧。
后記
漏洞分析是技術(shù),漏洞利用是藝術(shù),漏洞挖掘是法術(shù)。關(guān)于漏洞利用和漏洞挖掘哪個(gè)“技術(shù)含量”更高,還存在一定爭(zhēng)議,但漏洞分析的基礎(chǔ)地位毫無(wú)疑問(wèn)。技術(shù)可以學(xué),藝術(shù)可以練,法術(shù)呢?其實(shí)同樣也可以通過(guò)練習(xí)提高自己的 漏洞挖掘水平。推薦一個(gè)忘了是什么地方的議題中提供的方法(也許是 CCC):
1.找到公開(kāi)的漏洞通告,根據(jù)標(biāo)題的內(nèi)容自己去相關(guān)模塊中審計(jì)看是否能發(fā)現(xiàn)該漏洞2.如果發(fā)現(xiàn)不了,就回頭接著看漏洞通告的細(xì)節(jié),反思自己的審計(jì)方法3.不斷重復(fù),直到讓自己可以認(rèn)為找漏洞只是時(shí)間問(wèn)題而不是能力問(wèn)題
任何行業(yè)都沒(méi)有捷徑,你看到的所謂魔法,很可能只是某人在某些事情上付出了你意想不到的時(shí)間。最后借用一句諺語(yǔ)作為結(jié)尾吧:
Ever tried. Ever failed. No matter.
Try again. Fail again. Fail better.
共勉。
引用鏈接
[1] CVE-2019-2025(水滴): hhttps://nvd.nist.gov/vuln/detail/CVE-2019-2025[2] honggfuzz: https://github.com/google/honggfuzz[3] libFuzzer: https://llvm.org/docs/LibFuzzer.html[4] OSS-Fuzz: https://github.com/google/oss-fuzz[5] syzkaller: https://github.com/google/syzkaller
