//weibo: @少仲
0x0 漏洞信息
https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-0728
0x1 漏洞描述
cve-2016-0728是一個linux平臺上的UAF漏洞.漏洞主要的原因是由於keyrings組件當中的引用計數問題導致的.它使用一個32位的無符號整數做引用計數,但是在計數器出現溢出的時候沒有進行合理的處理.當對象的引用計數達到最大時會變成0,因此釋放對象的內存空間.而此時程序還保留對引用對象的引用,所以形成了UAF漏洞.
0x2 代碼分析
long join_session_keyring(const char *name)
{
const struct cred *old;
struct cred *new;
struct key *keyring;
long ret, serial;
new = prepare_creds();
if (!new)
return -ENOMEM;
old = current_cred();
/* if no name is provided, install an anonymous keyring */
if (!name) {
ret = install_session_keyring_to_cred(new, NULL);
if (ret < 0)
goto error;
serial = new->session_keyring->serial;
ret = commit_creds(new);
if (ret == 0)
ret = serial;
goto okay;
}
/* allow the user to join or create a named keyring */
mutex_lock(&key_session_mutex);
/* look for an existing keyring of this name */
keyring = find_keyring_by_name(name, false); //key->usage + 1
if (PTR_ERR(keyring) == -ENOKEY) {
/* not found - try and create a new one */
keyring = keyring_alloc(name, old->uid, old->gid, old,
KEY_ALLOC_IN_QUOTA, NULL);
if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
}
} else if (IS_ERR(keyring)) {
ret = PTR_ERR(keyring);
goto error2;
} else if (keyring == new->session_keyring) { //keyname == 當前cred中的key name
ret = 0; //直接返回,繞過key_put
goto error2;
}
/* we've got a keyring - now to install it */
ret = install_session_keyring_to_cred(new, keyring);
if (ret < 0)
goto error2;
commit_creds(new);
mutex_unlock(&key_session_mutex);
ret = keyring->serial;
key_put(keyring);
okay:
return ret;
error2:
mutex_unlock(&key_session_mutex);
error:
abort_creds(new);
return ret;
}
通過以上代碼可以得出只要進程使用當前正在使用的keyring名,程序就會跳過kref_put(keyring),造成引用計數的只增不減.由於引用計數是無符號整形,可以通過循環調用,整數溢出的方法來將它置0.
0x3 如何利用
1. 造成引用計數溢出,int所能保存最大值爲232 - 1.所以只要進行4294967295次循環,就可以將它清零.
2. 釋放引用計數爲0的keyring對象
3. 使用slab機制分配內核對象來覆蓋之前已經釋放的keyring對象
4. 獲得內核代碼的執行權限.使用keyctl(KEY_REVOKE,key_name)這個API來調用revoke().當我們覆蓋keyring對象的時候,我們可以控制指向revoke()的函數指針,讓其指向我們準備好的提權代碼.
0x4 POC問題主要分析
1. 漏洞代碼所在的內核版本應該是3.8以後.但是大部分安卓5.0的設備都運行在3.4的內核版本之中.
2. 網上的poc沒有成功的原因是因爲沒有獲得commit_creds和prepare_kernel_cred的地址,攻擊者需要得到root權限,將kptrstrict標誌置爲0後,才能通過/proc/kallsyms來查看符號的地址.
3. 在用戶空間的提權代碼則需要commit_creds(prepare_kernel_cred(0));函數將cred結構置爲0,從而獲取root權限.
4. 使用ret2usr來在用戶態調用提權代碼需要考慮pxn的問題.在沒有pxn防護的手機中可以在內核空間執行用戶態代碼.然而在有pxn防護的情況下,則需要考慮使用ROP來尋找內核gadget來繞過保護.
5. 需要調用4294967295次,時間過長有可能導致shell反彈超時.所以很難利用.
6. 出現漏洞的代碼存在於linux內核中的keyring服務.但是該服務必須在內核編譯時啓用CONFIG_KEYS 選項.在AOSP內核的config文件中,並沒有發現CONFIG_KEYS被啓用.所以說android設備並沒有包含漏洞相關的代碼,因此就是說android設備沒有被該漏洞所影響.
7. 在安卓4.4版本以後,強制開啓了SELinux的策略.SELinux減少了Linux內核的攻擊面.SELinux的策略也限制了ASOP在設備上通過非授信app調用exp的權限.比如當一個app嘗試去執行keyctl系統調用去創建一個keyring的對象時,系統調用會被拒絕. 開源的poc代碼使用了SysV IPC (msgget) 來分配內存傳遞漏洞利用代碼.安卓5.0的SELinux策略限制了SysV IPC因此阻止了包含利用的代碼.
0x5 實測
1. 首先去掉get_symbol的函數,root掉手機以後,直接將kptrstrict置爲0,讀取kallsyms的全部信息.獲取相關符號地址
2. arm版的通過使用syscall來取代keyctl調用.
3. 通過添加一個計算來顯示循環的百分比
在我的設備上測試的時間爲12:00 – 16:46 ,進度顯示爲0.1%.
所以大約要執行166天左右才能跑完…