1. 程式人生 > >突破Android P(Preview 1)對呼叫隱藏API限制的方法

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

奇技指南

在之前的文章《Android P 呼叫隱藏API限制原理》中,我們分析了Android P Preview 1版本對隱藏API呼叫限制的原理,這次我們帶來了繞過限制的的方法。

本文首發於公眾號奇卓社,已授權轉載。

概要

本文基於對AndroidP(Preview 1)的原始碼分析,實現了三種繞過對呼叫隱藏API限制的方法,有效性均已得到驗證,能夠成功呼叫系統隱藏API。

PS :有興趣的加入Android工程師交流QQ群:752016839 主要針對Android開發人員提升自己,突破瓶頸,相信你來學習,會有提升和收穫。  

限制原理

首先拋開Android P的具體實現過程,安卓系統要實現限制使用者程式碼呼叫系統隱藏API ,至少要做以下兩個區分:

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極有可能為空。

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

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這個隱藏方法為例,講解繞過限制的方法。

繞過方法

繞過方法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,其型別為JavaLibrary ,命名為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的第一個引數型別為ObjPtr<mirror::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

方法三通過混淆第一個區分點突破限制 。只要修改被隱藏的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 ,實現難度較大

總結

本文提出並實現了三種在AndroidP上呼叫隱藏API的方法,分別有不同特點和適用範圍,工程中可以根據實際情況選用不同方法。