本文來自微信公眾號:編程技術宇宙 (ID:xuanyuancoding),作者:軒轅之風
本故事根據 Linux 內核真實漏洞改編
帝國危機
夜幕降臨,喧囂褪去,繁忙的 Linux 帝國漸漸平靜了下來,誰也沒有想到,一場改變帝國命運的風暴正在悄然而至......
“咚咚!”,帝國安全部長辦公室的敲門聲,打破了夜晚的寧靜。
“部長,剛剛發(fā)現(xiàn)有線程在修改 passwd 文件”,原來是文件系統(tǒng)部門的小黑到訪。
“這有什么大驚小怪的?只要有 root 權限,這是允許的嘛!”,安全部長沒有抬頭,繼續(xù)看著每天的系統(tǒng)日志。
“部長,重點在于這線程不是從系統(tǒng)調用進入內核,而是從中斷入口進來的”
安全部長愣了一下,約莫 0.2ms 之后,放下了手里的日志,站了起來。
“你是說,他是通過中斷描述符表(IDT)進來的?”
小黑點了點頭。
“小王,你趕緊跟他過去 IDT 看一下,調查清楚速來報我”,部長對著一旁的助理說到。
小王點了點頭,準備出發(fā),剛走到門口,又被部長叫住了。
“等等!此事非同小可,我還是親自去一趟吧”
IDT 修改謎案
安全部長隨即出發(fā),來到 IDT 所在的地方,這里一切如舊,未見有何異樣。
部長指著這一排門墻問道:“他是從哪道門進來的?”
“4 號”,這時,看守 IDT 大門的白發(fā)老頭聞訊走了過來回答到。
“奇怪了,IDT 表中的函數(shù)入口,都是帝國安排好了的,講道理沒有哪一個會去修改 passwd 文件才對”,部長看著這些表項,低頭自語。
“部長,這我得跟您匯報一下,那小子進來之前,把第四項的入口地址高 32 位改成了 0x00000000,進來之后他才給恢復成了 0xFFFFFFFF”,老頭說完,拿出了 IDT 表項的結構圖展了開來:
部長聽完猛的一抬頭,“高 32 位變成了 0x00000000,那整個函數(shù)入口地址不就指向了用戶態(tài)地址空間了?”
小黑和小王都不敢說話,大家都知道這后果有多嚴重,天知道那家伙利用內核權限執(zhí)行了用戶空間的什么代碼。
“不對,在他進來之前,一個用戶空間的線程怎么能改 IDT 的內容呢?他沒權限訪問才對,我不信!”
“這個我倒是知道,他改的是時候,我特地留意了一下他的調用堆棧,不是在用戶空間,是從內核空間的函數(shù) ——perf_swevent_init 方向來的”,老頭說到。
整數(shù) + 1 的悲劇
部長二話沒說,又帶著大家直奔 perf_swevent_init 函數(shù)而去。
“老伯,您可還記得具體是哪個位置?”,部長問到。
“就是從那個 19 行那個 static_key_slow_inc 函數(shù)過來的”
“讓我看一下”,小王擠到前面來,想在部長面前露一手。
“嗯,這個 static_key_slow_inc 做的事情是把一個整數(shù)執(zhí)行了原子 + 1 操作。不過它操作的是 perf_swevent_enabled 數(shù)組,跟 IDT 八桿子打不到一塊兒去,怎么能修改到 IDT 呢?”,小王摸了摸頭,往后退了兩步,瞧著是沒看出什么問題。
“不見得!”,部長仍然是緊鎖著眉頭,開口說到,“你們看,它是通過 event_id 這個數(shù)字作為下標來訪問數(shù)組元素,要是這個 event_id 出錯訪問越界,指向 IDT,也不是沒有可能??!”
小王趕緊掃了一眼 event_id,隨后便露出了失望的表情,“不會的,第 9 行有檢查,你看,超過 8 以后就會通不過檢查”
線索在這里被切斷了,本來指望在 perf_swevent_init 這個函數(shù)這里尋找 IDT 被修改之謎,看來要無功而返了。
不知不覺,時間已經很晚了,部長一行決定先回去,再從長計議。
部長走了幾步,見小王沒有跟上來,便回頭叫了他一聲。
“部長請留步,我好像感覺哪里不太對勁”,小王此刻也皺起了眉頭。
“你發(fā)現(xiàn)了什么?”,部長和小黑他們又走了回來。
“部長,你看第 3 行,這個 event_id 是一個 int 型的變量,也就是說這是一個有符號數(shù)。”,小王說到。
“有符號數(shù)怎么了?”,小黑也忍不住開口問了。
“如果??????”
“如果 event_id 變成了一個負數(shù),它將能越界訪問數(shù)組,并且還能通過第 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 位無符號整數(shù),把它賦值給一個 int 型變量不出問題就怪了!
見大家都不說話,小黑撓了撓頭,弱弱的問到:“怎么了,你們怎么都不說話,這有什么問題嗎?”
小王把小黑拉到一邊,“問題大了,你看我要是把一個值為 0xFFFFFFFF 的 config 賦值給 event_id,event_id 會變成什么?”
“負,負,負 1?”
“沒錯,有符號數(shù)的最高位是用來標記正負的,如果這個 config 最高位為 1,后面的位經過精心設計,不僅能瞞天過海騙過那里第 9 行的驗證,還能將某個位置的數(shù)字進行一個原子 + 1 操作?!?,小王繼續(xù)說道。
“不錯嘛小王,有進步!”,不知何時部長也走了過來,被部長這么一夸,小王有些不好意思了。
“聽了半天,不就是越界把某個地方的數(shù)加了 1 嘛,有什么大不了的?”,小黑一臉不屑的樣子。
小王一聽連連搖頭,“你可不要小瞧了這個加 1 的行為,要是加在某些敏感的地方,那可是要出大事的!“
小黑有些疑惑,“比如說呢?”
“比如記錄中斷和異常的處理函數(shù)的 IDT,又比如記錄系統(tǒng)調用的 sys_call_table,這些表中的函數(shù)地址都位于帝國內核空間,要是這個加 1,加的不是別人,而是這些表中的函數(shù)地址,那可就麻煩了?!?,小王繼續(xù)說到。
“我聽明白了,可是就算加個 1,也應該不是什么大問題吧?”
小王嘆了口氣,“看來你還是不明白,我以這次被修改的 IDT 表為例,給大家再看一下表中的表項 —— 中斷描述符的格式”
“IDT 中的中斷 / 異常處理函數(shù)的地址不是一個完整的 64 位,而是拆成了幾部分,其中高 32 位我給大家紅色標示出來了,在 64 位 Linux 帝國,內核空間的地址高 32 位都是 0xFFFFFFFF,如果??????”
“如果利用前面的 event_id 數(shù)組下標越界訪問,把這個地方原子 + 1,那就變成了 0,對不對?”,小黑總算明白了。
真相大白
安全部長為小王的精彩分析鼓起了掌,“不錯不錯,大家都很聰明!事到如今,我們來復盤一下吧!”
第一步:精心設計一個 config 值,從應用層傳入內核空間的 perf_swevent_init 函數(shù)
第二步:利用帝國內核漏洞,把一個 64 位無符號數(shù)賦值給一個 int 型變量,導致變量溢出為一個負數(shù)。
第三步:利用溢出的 event_id 越界訪問 perf_swevent_enabled,指向 IDT 的表項,將第四項中斷處理函數(shù)的高 32 位進行原子 + 1
第四步:修改后的中斷處理函數(shù)指向了用戶空間,提前在此安排惡意代碼
第五步:應用層執(zhí)行 int 4 匯編指令,觸發(fā) 4 號中斷,線程將進入內核空間,以至高權限執(zhí)行提前安排的惡意代碼。
事情總算是水落石出,安全部長回去之后便上報帝國總部,修復了此漏洞,將 event_id 的類型從 int 修正為 u64。
即便如此,部長的心情卻并沒有輕松多少,未知的敵人已經闖入帝國,它們是誰?做了什么?現(xiàn)在藏在哪里?一個又一個的問題還在不斷在腦中閃現(xiàn)??????
未完待續(xù)??????
廣告聲明:文內含有的對外跳轉鏈接(包括不限于超鏈接、二維碼、口令等形式),用于傳遞更多信息,節(jié)省甄選時間,結果僅供參考,IT之家所有文章均包含本聲明。