本故事根據Linux內核真實漏洞改編
nginx
夜幕降臨,喧囂褪去,繁忙的Linux帝國漸漸平靜了下來,誰也沒有想到,一場改變帝國命運的風暴正在悄然而至......數組
「咚咚!」,帝國安所有長辦公室的敲門聲,打破了夜晚的寧靜。安全
「部長,剛剛發現有線程在修改passwd文件」,原來是文件系統部門的小黑到訪。函數
「這有什麼大驚小怪的?只要有root權限,這是容許的嘛!」,安所有長沒有擡頭,繼續看着天天的系統日誌。線程
「部長,重點在於這線程不是從系統調用進入內核,而是從中斷入口進來的」設計
安所有長愣了一下,約莫0.2ms以後,放下了手裏的日誌,站了起來。3d
「你是說,他是經過中斷描述符表(IDT)進來的?」
小黑點了點頭。日誌
「小王,你趕忙跟他們過去IDT看一下,調查清楚速來報我」,部長對着一旁的助理說到。code
小王點了點頭,準備出發,剛走到門口,又被部長叫住了。對象
「等等!此事非同小可,我仍是親自去一趟吧」
安所有長隨即出發,來到IDT所在的地方,這裏一切如舊,未見有何異樣。
部長指着這一排門牆問道:「他是從哪道門進來的?」
「4號」,這時,看守IDT大門的白髮老頭聞訊走了過來回答到。
「奇怪了,IDT表中的函數入口,都是帝國安排好了的,講道理沒有哪個會去修改passwd文件纔對」,部長看着這些表項,低頭自語。
「部長,這我得跟您彙報一下,那小子進來以前,把第四項的入口地址高32位改爲了0x00000000,進來以後他纔給恢復成了0xFFFFFFFF」,老頭說完,拿出了IDT表項的結構圖展了開來:
部長聽完猛的一擡頭,「高32位變成了0x00000000,那整個函數入口地址不就指向了用戶態地址空間了?」
小黑和小王都不敢說話,你們都知道這後果有多嚴重,天知道那傢伙利用內核權限執行了用戶空間的什麼代碼。
「不對,在他進來以前,一個用戶空間的線程怎麼能改IDT的內容呢?他沒權限訪問纔對,我不信!」
「這個我卻是知道,他改的是時候,我特意留意了一下他的調用堆棧,不是在用戶空間,是從內核空間的函數——perf_swevent_init
方向來的」,老頭說到。
部長二話沒說,又帶着你們直奔perf_swevent_init
函數而去。
「老伯,您可還記得具體是哪一個位置?」,部長問到。
「就是從那個19行那個static_key_slow_inc
函數過來的」
「讓我看一下」,小王擠到前面來,想在部長面前露一手。
「嗯,這個static_key_slow_inc
作的事情是把一個整數執行了原子+1操做。不過它操做的是perf_swevent_enabled
數組,跟IDT八杆子打不到一起去,怎麼能修改到IDT呢?」,小王摸了摸頭,日後退了兩步,瞧着是沒看出什麼問題。
「不見得!」,部長仍然是緊鎖着眉頭,開口說到,「大家看,它是經過event_id
這個數字做爲下標來訪問數組元素,要是這個event_id出錯訪問越界,指向IDT,也不是沒有可能啊!」
小王趕忙掃了一眼event_id
,隨後便露出了失望的表情,「不會的,第9行有檢查,你看,超過8之後就會通不過檢查」
線索在這裏被切斷了,原本期望在perf_swevent_init
這個函數這裏尋找IDT被修改之謎,看來要無功而返了。
不知不覺,時間已經很晚了,部長一行決定先回去,再從長計議。
部長走了幾步,見小王沒有跟上來,便回頭叫了他一聲。
「部長請留步,我好像感受哪裏不太對勁」,小王此刻也皺起了眉頭。
「你發現了什麼?」,部長和小黑他們又走了回來。
「部長,你看第3行,這個event_id
是一個int
型的變量,也就是說這是一個有符號數。」,小王說到。
「有符號數怎麼了?」,小黑也忍不住開口問了。
「若是······」
「若是event_id
變成了一個負數,它將能越界訪問數組,而且還能經過第9行的大小檢查!」,沒等小王說完,部長道破了玄機!
衆人再一次將目光彙集在了這個event_id
上,打算看一下第三行給它賦值的event->attr.config
是個什麼來頭。
首先是perf_event
中的attr
成員變量:
struct perf_event { // ... struct perf_event_attr attr; // ... };
接着是perf_event_attr
中的config
成員變量:
struct perf_event_attr { // ... __u64 config; // ... };
看到最後,部長和小王都倒吸了一口涼氣,這config
居然是個64位無符號整數,把它賦值給一個int
型變量不出問題就怪了!
見你們都不說話,小黑撓了撓頭,弱弱的問到:「怎麼了,大家怎麼都不說話,這有什麼問題嗎?」
小王把小黑拉到一邊,「問題大了,你看我要是把一個值爲0xFFFFFFFF的config
賦值給event_id
,event_id
會變成什麼?」
「負,負,負1?」
「沒錯,有符號數的最高位是用來標記正負的,若是這個config
最高位爲1,後面的位通過精心設計,不只能瞞天過海騙過那裏第9行的驗證,還能將某個位置的數字進行一個原子+1操做。」,小王繼續說道。
「不錯嘛小王,有進步!」,不知什麼時候部長也走了過來,被部長這麼一誇,小王有些很差意思了。
「聽了半天,不就是越界把某個地方的數加了1嘛,有什麼大不了的?」,小黑一臉不屑的樣子。
小王一聽連連搖頭,「你可不要小瞧了這個加1的行爲,要是加在某些敏感的地方,那但是要出大事的!「
小黑有些疑惑,「好比說呢?」
「好比記錄中斷和異常的處理函數的IDT
,又好比記錄系統調用的sys_call_table
,這些表中的函數地址都位於帝國內核空間,要是這個加1,加的不是別人,而是這些表中的函數地址,那可就麻煩了。」,小王繼續說到。
「我聽明白了,但是就算加個1,也應該不是什麼大問題吧?」
小王嘆了口氣,「看來你仍是不明白,我以此次被修改的IDT表爲例,給你們再看一下表中的表項——中斷描述符的格式」
「IDT中的中斷/異常處理函數的地址不是一個完整的64位,而是拆成了幾部分,其中高32位我給你們紅色標示出來了,在64位Linux帝國,內核空間的地址高32位都是 0xFFFFFFFF
,若是······」
「若是利用前面的event_id
數組下標越界訪問,把這個地方原子+1,那就變成了0,對不對?」,小黑總算明白了。
安所有長爲小王的精彩分析鼓起了掌,「不錯不錯,你們都很聰明!事到現在,咱們來複盤一下吧!」
- 第一步:精心設計一個config值,從應用層傳入內核空間的
perf_swevent_init
函數- 第二步:利用帝國內核漏洞,把一個64位無符號數賦值給一個int型變量,致使變量溢出爲一個負數。
- 第三步:利用溢出的
event_id
越界訪問perf_swevent_enabled
,指向IDT的表項,將第四項中斷處理函數的高32位進行原子+1- 第四步:修改後的中斷處理函數指向了用戶空間,提早在此安排惡意代碼
- 第五步:應用層執行
int 4
彙編指令,觸發4號中斷,線程將進入內核空間,以致高權限執行提早安排的惡意代碼。
事情總算是水落石出,安所有長回去以後便上報帝國總部,修復了此漏洞,將event_id
的類型從int
修正爲u64
。
即使如此,部長的心情卻並無輕鬆多少,未知的敵人已經闖入帝國,它們是誰?作了什麼?如今藏在哪裏?一個又一個的問題還在不斷在腦中閃現······
未完待續······
一個悶熱的下午,風扇飛速的旋轉,熱得人喘不過氣。
部長的辦公室出現了一個熟悉的身影,走近一看原來是小馬哥。
「部長,nginx公司又出事了」
預知後事如何,請關係後續精彩······
Python一鍵轉Jar包,Java調用Python新姿式!