Linux內(nèi)核爆安全漏洞 可通過exploit獲取root權(quán)限
最近Linux 內(nèi)核爆出了一個嚴重的安全漏洞,非root用戶可以通過該漏洞的exploit 獲取root權(quán)限。這并不罕見,值得一提的是這個補丁看起來如此平常以至于我們絕大多數(shù)人都不會以為這是安全問題。
先看這個問題的補丁,就是下面這個:
static int perf_swevent_init(struct perf_event *event) {- int event_id = event->attr.config;+ u64 event_id = event->attr.config; if (event->attr.type != PERF_TYPE_SOFTWARE) return -ENOENT;
我們第一眼的感覺就是這大概只是修復(fù)了編譯器報的一個小警告吧,怎么會引起如此嚴重的安全問題呢?
在沒打補丁的代碼中 event_id 是個帶符號的整型,而且就在下面不遠處的兩行代碼中只檢查了其上界:
if (event_id>= PERF_COUNT_SW_MAX) return -ENOENT;
而如果傳遞進來的 event->attr.config 值正好設(shè)置了符號位,那么 event_id 就會變成負值,而且能躲過上面的檢查。
負值意味著什么呢?再繼續(xù)看后面的代碼:
if (!event->parent) { int err; err = swevent_hlist_get(event); if (err) return err; atomic_inc(&perf_swevent_enabled[event_id]); event->destroy = sw_perf_event_destroy; }
意味著數(shù)組越界!這時你應(yīng)該身上開始冒冷汗了。繼續(xù),數(shù)組 perf_swevent_enabled[] 在 RHEL6 上的定義是:
atomic_t perf_swevent_enabled[PERF_COUNT_SW_MAX];
而 atomic_t 基本上就是int,也就是說 perf_swevent_enabled[] 是整型數(shù)組,那么用 event_id 訪問該數(shù)組時會把 event_id 的值乘以4再加上數(shù)組的起始地址。很簡單哈!
好,通過 System.map 文件我們可以得到 perf_swevent_enabled 的地址:
ffffffff81f360c0 B perf_swevent_enabled
那么當 event->attr.config == 0xffffffff (即有符號的-1)時,在 x86_64 上面我們最終會得到:
0xffffffffffffffff * 4 + 0xffffffff81f360c0 == 0xFFFFFFFF81F360BC
同理,當 event->attr.config == 0xfffffffe 時我們得到:
0xfffffffffffffffe * 4 + 0xffffffff81f360c0 == 0xFFFFFFFF81F360B8
所以上述的 atomic_inc() 其實增加的是前面兩個地址中存放的值,而這倆地址都指向內(nèi)核空間(參見 Documentation/x86/x86_64/mm.txt)!這時你應(yīng)該感到緊張了。。。
后面更有趣的事情發(fā)生在 sw_perf_event_destroy() 函數(shù)中,它是在 perf_event_open() 返回的 fd 被關(guān)閉時被調(diào)用,RHEL6 上其定義如下:
static void sw_perf_event_destroy(struct perf_event *event){ u64 event_id = event->attr.config; WARN_ON(event->parent); atomic_dec(&perf_swevent_enabled[event_id]); swevent_hlist_put(event);}
很明顯的不同是,event_id 這次是無符號的類型。那么,同上,當 event->attr.config == 0xffffffff 時我們得到:
0xffffffff * 4 + 0xffffffff81f360c0 == 0x0000000381F360BC
當 event->attr.config == 0xfffffffe 時我們得到:
0xfffffffe * 4 + 0xffffffff81f360c0 == 0x0000000381F360B8
所以這里的 atomic_dec() 實際上減小的是用戶空間地址內(nèi)的值。
上面是“基礎(chǔ)知識”,帶著這些知識我們看 exploit 代碼究竟做了什么,代碼片段如下:
#define BASE 0x380000000
#define SIZE 0x010000000
assert((map = mmap((void*)BASE, SIZE, 3, 0x32, 0,0)) == (void*)BASE);
memset(map, 0, SIZE);
sheep(-1); sheep(-2); // sheep will just invoke perf_event_open
// syscall with attr.config set to the param
for (i = 0; i
assert(map[i+1]);
break;
}
它首先會 mmap() 起始地址是 0×380000000 的一塊內(nèi)存區(qū)域。然后分別以 attr.config 為 -1 和 -2 調(diào)用兩次 perf_event_open()。根據(jù)前面的計算,它實際上分別增加了 0xFFFFFFFF81F360BC 和 0xFFFFFFFF81F360B8 兩處內(nèi)存的值,減少了 0x0000000381F360BC 和 0x0000000381F360B8 的值。后面的 for 循環(huán)則是找出被減少的內(nèi)存地址,這樣一來也就可以算出 perf_swevent_enabled[] 數(shù)組的地址(System.map 并不總是存在,如果存在而且可讀我們當然可以直接去讀這個值)。
知道這個地址我們就可以操縱內(nèi)核中某處的32bit的值,把其值加一。正因為如此,作者巧妙地選擇了中斷描述符表——一個16字節(jié)描述符的數(shù)組,它的地址可以通過 sidt 指令獲取。它其中的描述符結(jié)構(gòu)定義如下:
Offset Size Description
0 2 Offset low bits (0..15)
2 2 Selector (Code segment selector)
4 1 Zero
5 1 Type and Attributes (same as before)
6 2 Offset middle bits (16..31)
8 4 Offset high bits (32..63)
12 4 Zero
這里最有趣的是 offset 為8 的地方,在 x86_64 上面其值為 0xffffffff。作者選擇的中斷描述符是 0×4,所以相對于中斷描述符表它的偏移實際上是 0×48?,F(xiàn)在的任務(wù)就成了通過 perf_swevent_enabled[] 來計算出該中斷描述符中偏移為8的內(nèi)存地址,并對其加一!下面的代碼就是做的這個工作:
sheep(-i + (((idt.addr&0xffffffff)-0x80000000)/4) + 16);
i 是我們前面在 for 循環(huán)中搜到的 perf_swevent_enabled[] 的一個偏移,idt.addr 是中斷描述符表的絕對內(nèi)核地址,取其低32位并減去 0×80000000 是為了得到低28位作為偏移,除以4是因為數(shù)組是int,最后加的16就是 0×4 中斷描述符中的偏移(4已經(jīng)除去了),所以最終sheep()里面的參數(shù)就是我們想要的偏移,這樣以來內(nèi)核就替我們把 0×4 中斷描述符中的偏移為 8 的 0xffffffff 加上了1,也就成了0,也就成了用戶空間的地址!所以后面的 int 0×4 其實就會跳轉(zhuǎn)到用戶空間早已經(jīng)設(shè)置好的代碼!!!
而這段代碼比較生澀,但其意思就是更改當前進程的 uid/gid 為0來提升權(quán)限,所以最終取得一個有 root 權(quán)限的 shell!整個攻擊大功告成!
注:上面的鏈接可能不能用,exploit 代碼也可以在這里看到:https://gist.github.com/onemouth/5625174