[Android] adb setuid提權漏洞的分析

原來我看到一篇關於分析RageAgainstTheCage源碼的很不錯的文章,後來發現原連接失效了,只能從網頁的緩存副本中找尋蛛絲馬跡。下面就是我找到的內容,保存到本身的博客中,來保證之後能找到。html

去年的Android adb setuid提權漏洞被用於各種root刷機,漏洞發現人Sebastian Krahmer公佈的利用工具RageAgainstTheCage(rageagainstthecage-arm5.bin)被用於z4root等提權工具、Trojan.Android.Rootcager等惡意代碼之中。下面咱們來分析這一漏洞的產生緣由。android

The Android Exploid Crew小組在後來發布了一份PoC代碼:rageagainstthecage.c。從這份代碼開始着手。程序員

在main(:72)函數中,首先獲取了RLIMIT_NPROC的值(:83),這個值是Linux內核中定義的每一個用戶能夠運行的最大進程數。shell

而後,調用find_adb()函數(:94)來搜索Android系統中adb進程的PID,具體而言,該函數讀取每一個進程對應的文件的/proc/<pid>/cmdline,根據其是否等於」/sbin/adb」來判斷是否adb進程。緩存

接下來,fork了一個新的進程(:109),父進程退出,而子進程繼續。接下來,在113行建立一個管道。安全

if (fork() > 0)
		exit(0);

	setsid();
	pipe(pepe);

重頭戲發生在下面的122到138行,代碼以下:app

if (fork() == 0) {
		close(pepe[0]);
		for (;;) {
			if ((p = fork()) == 0) {
				exit(0);
			} else if (p < 0) {
				if (new_pids) {
					printf("\n[+] Forked %d childs.\n", pids);
					new_pids = 0;
					write(pepe[1], &c, 1);
					close(pepe[1]);
				}
			} else {
				++pids;
			}
		}
	}

新建一個進程後,在子進程之中,exploit代碼不斷地fork()(:125),而新的子進程不斷退出,從而產生大量的殭屍進程(佔據shell用戶的進程數)。最終,進程數達到上限,fork()返回小於0,因而打印當前已經建立多少子進程,並向管道輸入一個字符(:131)。ionic

在這裏,管道的做用是和(:122)fork出來的父進程同步,該進程在141行read這一管道,於是阻塞直至殭屍進程已經達到上限(:131)。函數

進一步的,exploit殺掉adb進程,並在系統檢測到這一現象並重啓一個adb以前,再一次fork(),將前一個adb留下的進程空位佔據。最後,在152行,exploit調用wait_for_root_adb(),等待系統重啓一個adb,這個新建的adb就會具備root權限。工具

爲何在shell用戶的進程數達到上限RLIMIT_NPROC之後,新建的adb會具備root權限?咱們來看adb的源碼。

在<android_src>/system/core/adb/adb.c的第918行,咱們能夠看到以下代碼:

/* then switch user and group to "shell" */
        if (setgid(AID_SHELL) != 0) {
            exit(1);
        }
        if (setuid(AID_SHELL) != 0) {
            exit(1);
        }

這已是漏洞修補之後的代碼。在漏洞最初被發現時,代碼以下:

/* then switch user and group to "shell" */
        setgid(AID_SHELL);
        setuid(AID_SHELL);

簡而言之,原來沒有檢查setuid()函數的返回值。事實上,在此以前,adb.c中的代碼都是以root權限運行,以完成部分初始化工做。在這一行,經過調用setuid()將用戶從root切換回shell,但setuid()在shell用戶進程數達到上限RLIMIT_NPROC時,會失敗,所以adb.c繼續以root身份運行,而沒有報錯。

咱們來看setuid()的man手冊(man 2 setuid),其中有以下說明:

RETURN VALUE
       On  success,  zero is returned.  On error, -1 is returned, and errno is
       set appropriately.

ERRORS
       EAGAIN The uid does not match the current uid and  uid  brings  process
              over its RLIMIT_NPROC resource limit.

能夠看到,setuid是可能發生錯誤的,而且在uid的進程數超過RLIMIT_NPROC極限時,發生EAGAIN錯誤。

在android的源碼中,setuid()定義於<android_src>/bionic/libc/unistd/setuid.c,實際上引用了一個外部符號__setuid,這個符號在<android_src>/bionic/libc/arch_xxx/syscalls/__setuid.S中定義,最終是一個%eax=$__NR_setuid32,%ebx=uid的int 0×80中斷。

由於只是要分析原理,咱們再也不鏖戰於Android,轉而看向Linux內核。

在最新的kernel2.6中,setuid()位於kernel/sys.c的682行,其中,在697行,一切正常的狀況下,它會調用set_user()來完成用戶切換。

set_user()實現於同一文件的587行,其中一部分代碼以下:

if (atomic_read(&new_user->processes) >= rlimit(RLIMIT_NPROC) &&
			new_user != INIT_USER) {
		free_uid(new_user);
		return -EAGAIN;
	}

含義很明顯,當目標用戶的進程數達到上限,那系統就不能再將一個進程分配給它,於是返回-EAGEIN。而後再setuid()中,直接跳事後面的代碼,而返回錯誤。

至此,整個漏洞的原理已經分析完畢。整理以下:

一、在Android的shell用戶下,製造大量的殭屍進程,直至達到shell用戶的進程數上限RLIMIT_NPROC;

二、kill當前系統中的adb進程,並再次佔據其進程位置以保持達到上限;

三、系統會在一段時間後重啓一個adb進程,該進程最初是root用戶,在完成少量初始化工做後,調用setuid()切換至shell用戶;

四、此時shell用戶的進程數已經達到上限,因此setuid()失敗,返回-1,而且用戶更換沒有完成,adb仍是root權限;

五、adb沒有檢查setuid()的返回值,繼續後續的工做,所以產生了一個具備root權限的adb進程,能夠被用於與用戶的下一步交互。

實際上,setuid在目標用戶進程數達到RLIMIT_NPROC極限時返回錯誤,這一問題可能產生的安全隱患最先能夠追溯到2000年。而在2006年,出現了真正利用這一編碼問題的漏洞(CVE-2006-2607)。

所以,這並非一個全新的漏洞。咱們能夠得出幾點結論:

一、函數返回值一直是忽略的對象,由於getuid()永遠不會失敗,程序員可能會認爲setuid()也不會失敗——至少沒有遇到過,所以忽略了對返回值的檢查。檢查一個系統函數是否調用失敗是一個常識,但又是很麻煩的事,若是爲了省事而忽略,問題就可能產生了。

二、Android下的安全問題,不少並不是全新的,並且我的判斷未來還會有大量漏洞、惡意代碼產生於傳統思路,而做用於新的平臺。面對這一新的平臺,咱們是否能搶先於攻擊者作好防範準備,是一個須要咱們思考和實踐的問題。

相關文章
相關標籤/搜索