老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注! html
Android是一個基於Linux內核的移動操做系統。Linux是一個支持多用戶的系統,系統中的文件的訪問權限是經過用戶ID(UID)和用戶組ID(GID)來控制的。換句話說,就是Linux的安全機制是基於UID和GID來實現的。Android在Linux內核提供的基於UID和GID的安全機制的基礎上,又實現了一套稱爲Permission的安全機制,如圖1所示: linux
圖1 Linux的UID/GID安全機制與Android的Permission安全機制 android
那麼,這兩個安全機制是如何對應起來的呢? shell
咱們首先看一下Linux基於UID和GID的安全機制,它包含三個基本角色:用戶、進程和文件,如圖2所示: 安全
圖2 Linux基於UID/GID的安全機制的三個角色 app
Linux中的每個用戶都分配有一個UID,而後全部的用戶又按組來進劃分,每個用戶組都分配有一個GID。注意,一個用戶能夠屬於多個用戶組,也就是說,一個UID能夠對應多個GID。在一個用戶所對應的用戶組中,其中有一個稱爲主用戶組,其它的稱爲補充用戶組。 socket
Linux中的每個文件都具備三種權限:Read、Write和Execute。這三種權限又按照用戶屬性劃分爲三組:Owner、Group和Other。如圖3所示: 函數
圖3 Linux的文件權限劃分 工具
從圖3就能夠看出文件acct:1. 全部者爲root,可讀可寫可執行;2. 全部者所屬的主用戶組爲root,在這個組中的其它用戶可讀可執行;3. 其他的用戶可讀可執行。 ui
Linux中的每個進程都關聯有一個用戶,也就是對應有一個UID,如圖4所示:
圖4 Linux的進程
因爲每個用戶都對應有一個主用戶組,以及若干個補充用戶組,所以,每個進程除了有一個對應的UID以外,還對應有一個主GID,以及若干個Supplementary GIDs。這些UID和GID就決定了一個進程所能訪問的文件或者所能調用的系統API。例如,在圖4中,PID爲340的進程通常來講,就只能訪問全部者爲u0_a19的文件。
一個進程的UID是怎麼來的呢?在默認狀況下,就等於建立它的進程的UID,也就是它的父進程的UID。Linux的第一個進程是init進程,它是由內核在啓動完成後建立的,它的UID是root。而後系統中的全部其它進程都是直接由init進程或者間接由init進程的子進程來建立。因此默認狀況下,系統的全部進程的UID都應該是root。可是實際狀況並不是如此,由於父進程在建立子進程以後,也就是在fork以後,能夠調用setuid來改變它的UID。例如,在PC中,init進程啓動以後,會先讓用戶登陸。用戶登陸成功後,就對應有一個shell進程。該shell進程的UID就會被setuid修改成所登陸的用戶。以後系統中建立的其他進程的UID爲所登陸的用戶。
進程的UID除了來自於父進程以外,還有另一種途徑。上面咱們說到,Linux的文件有三種權限,分別是Read、Wirte和Execute。其實還有另一個種權限,叫作SUID。例如,咱們對Android手機進行root的過程當中,會在裏面放置一個su文件。這個su文件就具備SUID權限,如圖5所示:
圖5 su的SUID和SGID
一個可執行文件一旦被設置了SUID位,那麼當它被一個進程經過exec加載以後,該進程的UID就會變成該可執行文件的全部者的UID。也就是說,當上述的su被執行的時候,它所運行在的進程的UID是root,因而它就具備最高級別的權限,想幹什麼就幹什麼。
與SUI相似,文件還有另一個稱爲SGID的權限,不過它描述的是用戶組。也就是說,一個可執行文件一旦被設置了GUID位,麼當它被一個進程經過exec加載以後,該進程的主UID就會變成該可執行文件的全部者的主UID。
如今,小夥伴們應該能夠理解Android手機的root原理了吧:一個普通的進程經過執行su,從而得到一個具備root權限的進程。有了這個具備root權限的進程以後,就能夠想幹什麼就幹什麼了。su所作的事情其實很簡單,它再fork另一個子進程來作真正的事情,也就是咱們在執行su的時候,後面所跟的那些參數。因爲su所運行在的進程的UID是root,所以由它fork出來的子進程的UID也是root。因而,子進程也能夠想幹什麼就幹什麼了。
不過呢,用來root手機的su還會配合另一個稱爲superuser的app來使用。su在fork子進程來作真正的事情以前,會將superuser啓動起來,詢問用戶是否容許fork一個UID是root的子進程。這樣就能夠對root權限進行控制,避免被惡意應用偷偷地使用。
這裏是su的源代碼,小夥伴們能夠根據上面所講的知識讀一讀:https://code.google.com/p/superuser/source/browse/trunk/su/su.c?r=2。
在傳統的UNIX以及類UNIX系統中,進程的權限只劃分兩種:特權和非特權。UID等於0的進程就是特權進程,它們能夠經過一切的權限檢查。UID不等於0的進程就非特權進程,它們在訪問一些敏感資源或者調用一個敏感API時,須要進行權限檢查。這種純粹經過UID來作權限檢查的安全機制來粗放了。因而,Linux從2.2開始,從進程的權限進行了細分,稱爲Capabilities。一個進程所具備Capabilities能夠經過capset和prctl等系統API來設置。也就是說,當一個進程調用一個敏感的系統API時,Linux內核除了考慮它的UID以外,還會考慮它是否具備對應的Capability。
這裏就是Linux所設計的Capabilities列表,有興趣的小夥伴能夠再讀一讀:http://man7.org/linux/man-pages/man7/capabilities.7.html。
以上就是Linux基於UID/GID的安全機制的核心內容。接下來咱們再看Android基於Permission的安全機制,它也有三個角色:apk、signature和permission,如圖6所示:
圖6 Android的Permission安全機制
Android的APK通過PackageManagerService安裝以後,就至關於Linux裏面的User,它們都會被分配到一個UID和一個主GID,而APK所申請的Permission就至關因而Linux裏面的Supplementary GID。
咱們知道,Android的APK都是運行在獨立的應用程序進程裏面的,而且這些應用程序進程都是Zygote進程fork出來的。Zygote進程又是由init進程fork出來的,而且它被init進程fork出來後,沒有被setuid降權,也就是它的uid仍然是root。按照咱們前面所說的,應用程序進程被Zygote進程fork出來的時候,它的UID也應當是root。可是,它們的UID會被setuid修改成所加載的APK被分配的UID。
參照Android應用程序進程啓動過程的源代碼分析一文的分析,ActivityManagerService在請求Zygote建立應用程序進程的時候,會將這個應用程序所加載的APK所分配獲得的UID和GID(包括主GID和Supplementary GID)都收集起來,而且將它們做爲參數傳遞給Zygote進程。Zygote進程經過執行函數來fork應用程序進程:
參數args[0]、args[1]和args[]保存的就是APK分配到的UID、主GID和Supplementary GID,它們分別經過setuid、setgid和setgroupsIntarray設置給當前fork出來的應用程序進程,因而應用程序進程就再也不具備root權限了。
那麼,Signature又充當什麼做用呢?兩個做用:1. 控制哪些APK能夠共享同一個UID;2. 控制哪些APK能夠申請哪些Permission。
咱們知道,若是要讓兩個APK共享同一個UID,那麼就須要在AndroidManifest中配置android:sharedUserId屬性。PackageManagerService在安裝APK的時候,若是發現兩個APK具備相同的android:sharedUserId屬性,那麼它們就會被分配到相同的UID。固然這有一個前提,就是這兩個APK必須具備相同的Signature。這很重要,不然的話,若是我知作別人的APK設置了android:sharedUserId屬性,那麼我也在本身的APK中設置相同的android:sharedUserId屬性,就能夠去訪問別人APK的數據了。
除了能夠經過android:sharedUserId屬性申請讓兩個APK共享同一個UID以外,咱們還能夠將android:sharedUserId屬性的值設置爲「android.uid.system」,從而讓一個APK的UID設置爲1000。UID是1000的用戶是system,系統的關鍵服務都是運行在的進程的UID就是它。它的權限雖然不等同於root,不過也足夠大了。咱們能夠經過Master Key漏洞來看一下有多大。
Master Key漏洞發佈時,曾轟動了整個Android界,它的具體狀況老羅就不分析了,網上不少,這裏是一篇官方的文章:http://bluebox.com/corporate-blog/bluebox-uncovers-android-master-key/。如今就簡單說說它是怎麼利用的:
1. 找到一個具備系統簽名的APP,而且這個APP經過android:sharedUserId屬性申請了android.uid.system這個UID。
2. 經過Master Key向這個APP注入惡意代碼。
3. 注入到這個APP的惡意代碼在運行時就得到了system用戶身份。
4. 修改/data/local.prop文件,將屬性ro.kernel.qemu的值設置爲1。
5. 重啓手機,因爲ro.kernel.qemu的值等於1,這時候手機裏面的adb進程不會被setuid剝奪掉root權限。
6. 經過具備root權限的adb進程就能夠向系統注入咱們熟悉的su和superuser.apk,因而整個root過程完成。
注意,第1步之因此要找一個具備系統簽名的APP,是由於經過android:sharedUserId屬性申請android.uid.system這個UID須要有系統簽名,也就是說不是誰能夠申請system這個UID的。另外,/data/local.prop文件的Owner是system,所以,只有得到了system這個UID的進程,才能夠對它進行修改。
再說說Signature與Permission的關係。有些Permission,例如INSTALL_PACKAGE,不是誰均可以申請的,必需要具備系統簽名才能夠,這樣就能夠控制Suppementary GID的分配,從而控制應用程序進程的權限。具備哪些Permission是具備系統簽名才能夠申請的,能夠參考官方文檔:http://developer.android.com/reference/android/Manifest.html,就是哪些標記爲「Not for use by third-party applications」的Permission。
瞭解了Android的Permission機制以後,咱們就能夠知道:
1. Android的APK就至關因而Linux的UID。
2. Android的Permission就至關因而Linux的GID。
3. Android的Signature就是用來控制APK的UID和GID分配的。
這就是Android基於Permission的安全機制與Linux基於UID/GID的安全機制的關係,歸納來講,咱們常說的應用程序沙箱就是這樣的:
圖7 Android的Application Sandbox
接下來咱們就終於能夠步入正題分析NDK在非root手機上調試APP的原理了。首先們須要知道的是,NDK是經過gdbclient和gdbserver來調試APP的。具體來講,就是經過gdbserver經過ptrace附加上目標APP進程去,而後gdbclient再經過socket或者pipe來連接gdbserver,而且向它發出命令來對APP進程進行調試。這個具體的過程能夠參考這篇文章,講得很詳細的了:http://ian-ni-lewis.blogspot.com/2011/05/ndk-debugging-without-root-access.html。老羅但願小夥伴們認真看完這篇文章再來看接下來的內容,由於接下來咱們只講這篇文章的關鍵點。
第一個關鍵點是每個須要調試的APK在打包的時候,都會帶上一個gdbserver。由於手機上面不帶有gdbserver這個工具。這個gdbserver就負責用來ptrace到要調度的APP進程去。
第二個關鍵點是ptrace的調用。通常來講,只有root權限的進程只能夠調用。例如,若是咱們想經過ptrace向目標進程注入一個SO,那麼就須要在root過的手機上經過向su申請root權限。可是,這不是絕對的。若是一個進程與目標進程的UID是相同的,那麼該進程就具備調用ptrace的權限。咱們能夠看看ptrace_attach函數的實現:
第三個關鍵點是如何讓gdbserver進程的UID與要調試的APP進程的UID同樣。由於在沒有root過的手機上,要想得到root權限是不可能的了,所以只能選擇以目標進程相同的UID運行這個方法。這就要用到另一個工具了:run-as。
runs-as實際上是一個與su相似的工具,它在設備上是自帶的,位於/system/bin目錄下,它的SUID位也是被設置了,而且它的全部者也是root,咱們能夠經過ls -l /system/bin/run-as來看到:
第四個關鍵點是被調試的APK在其AndroidManifext.xml裏必須將android:debuggable屬性設置爲true。這是爲何呢?原來,當一個進程具備ptrace到目標進程的權限時,還不可以對目標進程進行調試,還要求目標進程將本身設置爲可dumpable的。咱們再回過頭來進一步看看__ptrace_may_access的實現:
這下咱們就明白NDK在非root手機上調試APP的原理了:gdbserver經過run-as得到與目標進程相同的UID,而後就能夠ptrace到目標進程去調試了。
這一下就引出了run-as這個工具,貌似很強大的樣子,那咱們是否是也能夠利用它來作壞事呢?例如,咱們能夠在adb shell中運行run-as(run-as屬於shell組,所以能夠執行),而且指定run-as以某一個APK的UID運行,那麼不就是能夠讀取該APK的數據了嗎?從而突破了Android的應用程序沙箱。可是這是不可能作到的。
咱們能夠看一下run-as的源代碼:
1. 檢查自身是否是以shell或者root用戶運行。
2. 檢查指定的UID的值是不是在分配給APK範圍內的值,也就是隻能夠指定APK的UID,而不能夠指定像system這樣的UID。
3. 指定的UID所對應的APK的android:debuggable屬性必需要設置爲true。
綜合了以上三個條件以後,咱們才能夠成功地執行run-as。
這裏還有一點須要提一下的就是,咱們在運行run-as的時候,指定的參數實際上是一個package name。run-as經過這個package name到/data/system/packages.xml去得到對應的APK的安裝信息,包括它所分配的UID,以及它的android:debuggable屬性。文件/data/system/packages.xml的全部者是system,run-as在讀取這個文件的時候的身份是root,所以有權限對它進行讀取。
這下咱們也明白了,你想經過run-as來作壞事是不行的。同時,這也提醒咱們,在發佈APK的時候,必定不要將android:debuggable屬性的值設置爲true。不然的話,就提供了機會讓別人去讀取你的數據,或者對你進行ptrace了。
至些,咱們就經過NDK在非Root手機上的調試原理完成了Android安全機制的探討了,不知道各位小夥伴們理解了嗎?沒理解的不要緊,能夠關注老羅的新浪微博,上面有不少的乾貨分享:http://weibo.com/shengyangluo。