突破Android P(Preview 1)對調用隱藏API限制的方法

一.概要

本文基於對Android P(Preview 1)的源碼分析,實現了三種繞過對調用隱藏API限制的方法,有效性均已獲得驗證,可以成功調用系統隱藏API。java

二.限制原理

首先拋開Android P的具體實現過程,安卓系統要實現限制用戶代碼調用系統隱藏API,至少要作如下兩個區分:android

  1. 必須區分一個Method(或Field)對用戶代碼是隱藏的仍是公開的。只有隱藏的才須要進行限制。
  2. 必須區分調用者的身份:是用戶代碼調用的仍是系統代碼(例如Activity類)調用的。只有用戶代碼調用時才須要進行限制。

具體到Android P的代碼實現,它會在全部經過反射方式和JNI方式獲取Method和Field的地方調用如下函數判斷是否用戶代碼調用了系統的隱藏API(位於art/runtime/hidden_api.h),若是這個函數返回true,那麼說明用戶代碼調用了系統的隱藏API,Android P(Preview1)會經過log發出警告,用戶代碼仍然可以獲取到正確的Method或Field,在後續版本中獲取到的Method或Field極有可能爲空。api

那麼它是如何進行上述兩個區分的呢?app

  1. 每一個Method(或Field)都有一個對應的access_flags_(uint32_t類型),本來這個值經過一些特定位(bit)代表其屬性(public,private,static等),可是還有一些保留的未定義的位,Android P就利用未定義的幾個位,代表這個Method(或Field)是對用戶代碼隱藏的仍是公開的。
  2. 經過回溯調用棧找到調用者所在的Class,而後判斷這個Class的ClassLoader是否爲BootStrapClassLoader,若是是BoootStrapClassLoader那麼就認爲調用者是系統代碼,不然就認爲調用者是用戶代碼。fn_caller_in_boot就是一個函數指針,它用來判斷調用者是不是BootStrapClassLoader,反射調用和JNI調用時fn_caller_in_boot指向不一樣的函數,具體細節可查看源碼。

下面咱們以調用android.app.ActivityThread類的currentActivityThread這個隱藏方法爲例,講解繞過限制的方法。ide

三.繞過方法

繞過方法1

經過上面的論述結合源碼分析,咱們發現只有在經過反射方式和JNI方式獲取Method和Field時,系統纔有可能攔截對隱藏API的獲取,也就是說直接調用是能夠的!所以方法一的核心思想就是千方百計直接調用系統隱藏API。具體實現時須要用Provided方式提供Module或自定義android.jar。下面以一個例子說明實現過程。
咱們新建一個普通的android工程,在其MainActivity中直接調用ActivityThread.currentActivityThread();發現IDE提示找不到類ActivityThread,這是由於在sdk的android.jar(位於SDK/platforms/android-XX目錄下)中並無這個類的聲明,可是在實際運行時這個類是存在於系統中的。咱們的解決方法是以Provided方式提供一個Module,在此Module中提供須要的類(Provided方式是爲了編譯經過,這樣的Module的代碼並不會編譯到最終的apk中)。具體操做以下:
新建一個Module,其類型爲Java Library,命名爲libfakeandroid,而後在app的build.gradle中以Provided方式依賴libfakeandroid
函數


以後在libfakeandroid中新建一個類android.app.ActivityThread,並添加須要調用的隱藏API,以下

完成以上操做以後,MainActivity中就能直接調用ActivityThread.currentActivityThread();方法了。在Android P(Preview1)系統上運行不會出現警告log,成功!
注意:若是須要調用的隱藏API所在的類已經位於android.jar中,Provided方式再也不適用,此時須要自定義android.jar,將須要的Method或Field添加到android.jar中。
優勢:實現起來很是簡單方便,而且穩定性很好。
缺點:只能調用訪問權限爲public和default的Method和Field,不能直接調用protected和private的。工具

繞過方法2

如今回頭看"限制原理"中論述的兩個區分,其實只要咱們可以混淆任何一個區分點都可以成功繞過此限制。混淆第一個區分點,會讓系統錯誤地認爲本來隱藏的API是公開的;混淆第二個區分點,會讓系統錯誤地將用戶代碼調用識別爲系統代碼調用。方法二的核心思想就是混淆第二個區分點
關注第二個區分點,能夠發現,其實只要在BootStrapClassLoader加載的類中有任何一個幫助咱們進行反射的類就能繞過這個問題,那麼咱們可否將咱們apk中定義的類的ClassLoader改成BootStrapClassLoader呢?答案是確定的!查看art/runtime/mirror/class.h可知SetClassLoader函數能夠爲一個類指定ClassLoader,用IDA查看/system/lib/libart.so確認此函數位於導出符號表中。SetClassLoader的第一個參數類型爲ObjPtrmirror::Class,如何將jclass轉化爲此類型呢?經過在Android源碼中查找,在art/runtime/well_known_classes.h中有一個很是合適的函數ToClass可以完成此任務,其聲明以下
源碼分析


查看libart.so可知,ToClass函數也在其導出符號表中,所以ToClass函數是一個恰當的函數。方法二的具體實現代碼見下圖

其中,my_dlsym與dlsym相似,其功能是根據函數的導出符號尋找函數在進程中的地址。my_dlsym是咱們自定義的一個函數。
makeHiddenApiAccessable調用成功以後,使用com.test.hidefix.ReflectionHelper類反射尋找隱藏API,不會再出現log警告,成功!
實際工程中使用時能夠將ReflectionHelper類做爲一個工具類,代碼中全部反射尋找Method和Field的地方均使用ReflectionHelper處理。 注意:ReflectionHelper類只能調用系統類,不能調用本身app代碼中的任何類!不然會由於ClassLoader的全盤委託機制出現問題!
優勢:可以調用全部隱藏API;僅須要尋找兩個導出函數,適配性較好;沒有使用Hook,穩定性好
缺點:JNI方式獲取Method和Field時也須要轉到ReflectionHelper工具類完成

繞過方法3

方法三經過混淆第一個區分點突破限制gradle

只要修改被隱藏的Method或Field對應的access_flags_,去掉其隱藏屬性便可,下文爲了論述方便,只以獲取隱藏的Method爲例進行說明,Field同理。
實際上,只要獲取到一個jmethodID,將其強轉爲ArtMethod*類型,而後修改其access_flags_便可。可是後續版本中應用代碼沒法獲取隱藏Method的jmethodID,貌似陷入一個死循環了。可是查看源碼,咱們是有方法獲取ArtMethod*的:art/runtime/native/java_lang_Class.cc有如下函數:

此函數是Class.getDeclaredMethod方法在native的實現,注意到這裏是先獲取的result而後才判斷ShouldBlockAccessToMember,所以咱們能夠hook獲取result的mirror::Class::GetDeclaredMethodInternal這個函數,將獲得的ObjPtr mirror::Method類型的result想辦法轉換爲ArtMethod*類型便可。方法三具體實現代碼以下:

應用到實際工程中時還須要Hook另外的相似函數,這裏再也不一一列舉。
優勢:原有代碼無需修改,適用於原有代碼量較多的狀況。
缺點:須要使用Hook,實現難度較大

四.總結

本文提出並實現了三種在Android P上調用隱藏API的方法,分別有不一樣特色和適用範圍,工程中能夠根據實際狀況選用不一樣方法。ui

相關文章
相關標籤/搜索