ART學習系列: Android 9 Get Method 過程分析及 訪問限制突破

語言: CN / TW / HK

ART學習系列: Android 9 Get Method 過程分析及 訪問限制突破

背景:

從 Android 9(API 級別 28)開始,Android 平臺對應用能使用的非 SDK 介面實施了限制

api.png

但是 VSA 要完成一些功能,必須得去呼叫隱藏的Api,所以要進行限制突破。現在就是把這個過程 總結起來,跟大家一起學習。

參考:https://developer.android.com/guide/app-compatibility/restrictions-non-sdk-interfaces?hl=zh-cn

學習過程:

我們先了解一下,反射呼叫一個方法,在虛擬機器裡怎麼實現的

前提基礎:

1.瞭解Java 中的 反射如何使用。

2.瞭解基本的Classloader 相關知識。

Java 層 getDeclareMethod 過程分析

通常情況下,我們通過反射去呼叫某個類的方法時,在Java層我們會這麼實現。

try {    Class loadedApk = Class.forName("android.app.LoadedApk");    Method getApplication = loadedApk.getDeclaredMethod("getApplication",null); } catch (Throwable e) {    e.printStackTrace(); }

17:15:03.461 9062-9062/com.araon.demo W/com.araon.demo: Accessing hidden method Landroid/app/LoadedApk;->getApplication()Landroid/app/Application; (dark greylist, reflection) 17:15:03.462 9062-9062/com.araon.demo W/System.err: java.lang.NoSuchMethodException: getApplication []

在Android9 的手機上會打印出這樣一條日誌,意味著LoadedApk類的,getApplication 方法是被列入禁止呼叫的灰名單裡的。

Class 類的 getDeclaredMethod 方法根據傳入方法名字和簽名 找到對應的Method結構。

下面我們就分析一下getDeclaredMethod 如何實現。

先以Android 9上為例項:

getDeclaredMethod 在Class.java 實現如下

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)    throws NoSuchMethodException, SecurityException {    return getMethod(name, parameterTypes, false); }

看getMethod的實現:

private Method getMethod(String name, Class<?>[] parameterTypes, boolean recursivePublicMethods)        throws NoSuchMethodException {    if (name == null) {        throw new NullPointerException("name == null");   }    if (parameterTypes == null) {        parameterTypes = EmptyArray.CLASS;   }    for (Class<?> c : parameterTypes) {        if (c == null) {            throw new NoSuchMethodException("parameter type is null");       }   }    Method result = recursivePublicMethods ? getPublicMethodRecursive(name, parameterTypes)                                           : getDeclaredMethodInternal(name, parameterTypes);    // Fail if we didn't find the method or it was expected to be public.    if (result == null ||       (recursivePublicMethods && !Modifier.isPublic(result.getAccessFlags()))) {        throw new NoSuchMethodException(name + " " + Arrays.toString(parameterTypes));   }    return result; }

recursivePublicMethods 的值在getMethod(name, parameterTypes, false); 被傳了false, 所以此時呼叫getDeclaredMethodInternal

private native Method getDeclaredMethodInternal(String name, Class<?>[] args);

此時看到getDeclaredMethodInternal 為native 方法。那我們接下來去native 查詢該方法。

/art/runtime/native/java_lang_Class.cc

static jobject Class_getDeclaredMethodInternal(JNIEnv* env, jobject javaThis, 564                                                 jstring name, jobjectArray args) { 565    ScopedFastNativeObjectAccess soa(env); 566    StackHandleScope<1> hs(soa.Self()); 567    DCHECK_EQ(Runtime::Current()->GetClassLinker()->GetImagePointerSize(), kRuntimePointerSize); 568    DCHECK(!Runtime::Current()->IsActiveTransaction()); 569    Handle<mirror::Method> result = hs.NewHandle( 570        mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize, false>( 571            soa.Self(), 572            DecodeClass(soa, javaThis), 573            soa.Decode<mirror::String>(name), 574            soa.Decode<mirror::ObjectArray<mirror::Class>>(args))); 575    if (result == nullptr || ShouldBlockAccessToMember(result->GetArtMethod(), soa.Self())) { 576      return nullptr; 577   } 578    return soa.AddLocalReference<jobject>(result.Get()); 579 }

同樣在Native層 也是呼叫Class的GetDeclaredMethodInternal 方法,同樣我們在返回結果處發現了 ShouldBlockAccessToMember 方法。

9f20662aa4b3df0da4f273e1ad916454.jpeg

我們先要把每一個方法分析一下:

先分析GetDeclaredMethodInternal 方法。

template <PointerSize kPointerSize, bool kTransactionActive>

ObjPtr<Method> Class::GetDeclaredMethodInternal( 1266      Thread* self, 1267      ObjPtr<Class> klass, 1268      ObjPtr<String> name, 1269      ObjPtr<ObjectArray<Class>> args) { 1270    // Covariant return types permit the class to define multiple 1271    // methods with the same name and parameter types. Prefer to 1272    // return a non-synthetic method in such situations. We may 1273    // still return a synthetic method to handle situations like 1274    // escalated visibility. We never return miranda methods that 1275    // were synthesized by the runtime. //.... 1284    ArtMethod* result = nullptr; 1285    for (auto& m : h_klass->GetDeclaredVirtualMethods(kPointerSize)) { 1286      auto* np_method = m.GetInterfaceMethodIfProxy(kPointerSize); 1287      // May cause thread suspension. 1288      ObjPtr<String> np_name = np_method->GetNameAsString(self); 1289      if (!np_name->Equals(h_method_name.Get()) || !np_method->EqualParameters(h_args)) { // ... 1293        continue; 1294     } 1295      if (!m.IsMiranda()) { 1296        if (!m.IsSynthetic()) { 1297          return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m); 1298       } 1299        result = &m;  // Remember as potential result if it's not a miranda method. 1300     } 1301   } 1302    if (result == nullptr) { 1303      for (auto& m : h_klass->GetDirectMethods(kPointerSize)) { 1304        auto modifiers = m.GetAccessFlags(); 1305        if ((modifiers & kAccConstructor) != 0) { 1306          continue; 1307       } 1308        auto* np_method = m.GetInterfaceMethodIfProxy(kPointerSize); 1309        // May cause thread suspension. 1310        ObjPtr<String> np_name = np_method->GetNameAsString(self); // ... 1315        if (!np_name->Equals(h_method_name.Get()) || !np_method->EqualParameters(h_args)) { //.... 1319          continue; 1320       } 1321        DCHECK(!m.IsMiranda());  // Direct methods cannot be miranda methods. 1322        if ((modifiers & kAccSynthetic) == 0) { 1323          return Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, &m); 1324       } 1325        result = &m;  // Remember as potential result. 1326     } 1327   } 1328    return result != nullptr 1329        ? Method::CreateFromArtMethod<kPointerSize, kTransactionActive>(self, result) 1330       : nullptr; 1331 }

從上面看GetDeclaredMethodInternal 方法內部先是去找 虛方法,在找類自身的正常方法,再去找介面方法。

返回的物件是Method

看一下具體的方法實現過程中,有無特殊可發現的點。

149  template<VerifyObjectFlags kVerifyFlags> 150  inline ArraySlice<ArtMethod> Class::GetDeclaredVirtualMethodsSlice(PointerSize pointer_size) { 151    DCHECK(IsLoaded() || IsErroneous()); 152    return GetDeclaredVirtualMethodsSliceUnchecked(pointer_size); 153 } 154   155  inline ArraySlice<ArtMethod> Class::GetDeclaredVirtualMethodsSliceUnchecked( 156      PointerSize pointer_size) { 157    return GetMethodsSliceRangeUnchecked(GetMethodsPtr(), 158                                         pointer_size, 159                                         GetVirtualMethodsStartOffset(), 160                                         GetCopiedMethodsStartOffset());

203  inline ArraySlice<ArtMethod> Class::GetMethodsSliceRangeUnchecked( 204      LengthPrefixedArray<ArtMethod>* methods, 205      PointerSize pointer_size, 206      uint32_t start_offset, 207      uint32_t end_offset) { 208    DCHECK_LE(start_offset, end_offset); 209    DCHECK_LE(end_offset, NumMethods(methods)); 210    uint32_t size = end_offset - start_offset; 211    if (size == 0u) { 212      return ArraySlice<ArtMethod>(); 213   } 214    DCHECK(methods != nullptr); 215    DCHECK_LE(end_offset, methods->size()); 216    size_t method_size = ArtMethod::Size(pointer_size); 217    size_t method_alignment = ArtMethod::Alignment(pointer_size); 218    ArraySlice<ArtMethod> slice(&methods->At(0u, method_size, method_alignment), 219                                methods->size(), 220                                method_size); 221    return slice.SubArray(start_offset, size);

從這個步驟看,虛方法還是標準方法都是 根據偏移找到對應的ArtMethod 結構。

為什麼會限制方法呼叫原理

接下來分析ShouldBlockAccessToMember這個方法。上一步從getDeclaredMethod 獲取到ART Method 的結構體後,會再次判斷是否能夠返回真是的方法。

ALWAYS_INLINE static bool ShouldBlockAccessToMember(T* member, Thread* self) 117      REQUIRES_SHARED(Locks::mutator_lock_) { 118    hiddenapi::Action action = hiddenapi::GetMemberAction( 119        member, self, IsCallerTrusted, hiddenapi::kReflection); 120    if (action != hiddenapi::kAllow) { 121      hiddenapi::NotifyHiddenApiListener(member); 122   } 123   124    return action == hiddenapi::kDeny; 125 }

主要是看GetMemberAction 返回的Action是不是Allow。

inline Action GetMemberAction(T* member, 199                                Thread* self, 200                                std::function<bool(Thread*)> fn_caller_is_trusted, 201                                AccessMethod access_method) 202      REQUIRES_SHARED(Locks::mutator_lock_) { 203    DCHECK(member != nullptr); 204   205    // Decode hidden API access flags. 206    // NB Multiple threads might try to access (and overwrite) these simultaneously, 207    // causing a race. We only do that if access has not been denied, so the race 208    // cannot change Java semantics. We should, however, decode the access flags 209    // once and use it throughout this function, otherwise we may get inconsistent 210    // results, e.g. print whitelist warnings (b/78327881). 211    HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags(); 212   213    Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()); 214    if (action == kAllow) { 215      // Nothing to do. 216      return action; 217   } 218   219    // Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access. 220    // This can be *very* expensive. Save it for last. 221    if (fn_caller_is_trusted(self)) { 222      // Caller is trusted. Exit. 223      return kAllow; 224   } 225   226    // Member is hidden and caller is not in the platform. 227    return detail::GetMemberActionImpl(member, api_list, action, access_method); 228 }

第一步先判斷方法的Flag 是不是訪問限制。

inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) { 72    if (api_list == HiddenApiAccessFlags::kWhitelist) { 73      return kAllow; 74   } 75   76    EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy(); 77    if (policy == EnforcementPolicy::kNoChecks) { 78      // Exit early. Nothing to enforce. 79      return kAllow; 80   } 81   82    // if policy is "just warn", always warn. We returned above for whitelist APIs. 83    if (policy == EnforcementPolicy::kJustWarn) { 84      return kAllowButWarn; 85   } 86    DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList); 87    // The logic below relies on equality of values in the enums EnforcementPolicy and 88    // HiddenApiAccessFlags::ApiList, and their ordering. Assertions are in hidden_api.cc. 89    if (static_cast<int>(policy) > static_cast<int>(api_list)) { 90      return api_list == HiddenApiAccessFlags::kDarkGreylist 91          ? kAllowButWarnAndToast 92         : kAllowButWarn; 93   } else { 94      return kDeny; 95   } 96 }

在GetActionFromAccessFlags方法裡, 先判斷是否要開啟訪問限制的檢查。並判斷方法屬於那個限制名單裡還是直接拒絕。

9aee4a7a77c86f2f426f43f1870ec089.jpeg

在GetMemberAction裡如果是方法拒絕訪問會執行detail::GetMemberActionImpl(member, api_list, action, access_method);

``` 201  Action GetMemberActionImpl(T member, 202                             HiddenApiAccessFlags::ApiList api_list, 203                             Action action, 204                             AccessMethod access_method) { 205    DCHECK_NE(action, kAllow); 206   207    // Get the signature, we need it later. 208    MemberSignature member_signature(member); 209   210    Runtime runtime = Runtime::Current(); 211   212    // Check for an exemption first. Exempted APIs are treated as white list. 213    // We only do this if we're about to deny, or if the app is debuggable. This is because: 214    // - we only print a warning for light greylist violations for debuggable apps 215    // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs. 216    // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever 217    //   possible. 218    const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable(); 219    if (shouldWarn || action == kDeny) { 220      if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) { 221        action = kAllow; 222        // Avoid re-examining the exemption list next time. 223        // Note this results in no warning for the member, which seems like what one would expect. 224        // Exemptions effectively adds new members to the whitelist. 225        MaybeWhitelistMember(runtime, member); 226        return kAllow; 227     } ​ // .....

246    if (action == kDeny) { 247      // Block access 248      return action; 249   } 250   //.... 265   266    return action; 267 } ```

在這個方法裡 我們看到了不一樣的地方。方法先去獲取到了runtime 的例項物件,呼叫GetHiddenApiExemptions 方法。

這個 GetHiddenApiExemptions 會返回一個列表是被列為特殊可訪問的方法裡的陣列。

也就是說檢查訪問許可權,即使是限制名單裡的,也還需要最後有個允許方法的白名單。

所以經過一系列查詢和許可權訪問判斷後 返回是否能拿到Method方法物件。

突破限制方法呼叫:

Android P 上增加隱藏API的訪問限制。如果想突破限制訪問到隱藏Api, 可以從兩個點著手。

(1)Runtime::Current()->GetHiddenApiEnforcementPolicy(); Runtime 中的這個方法,返回是否需要限制。

可以從這個點Hook。Runtime的GetHiddenApiEnforcementPolicy 方法取消限制判斷。

(2)if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {} 有這樣一個判斷。

Runtime 裡有一個白名單,在白名單的即使是隱藏Api也可以訪問。

對這個我們在繼續分析一下。

543    void SetHiddenApiExemptions(const std::vector<std::string>& exemptions) { 544      hidden_api_exemptions_ = exemptions; 545   } 546   547    const std::vector<std::string>& GetHiddenApiExemptions() { 548      return hidden_api_exemptions_; 549   }

我們發現 在GetHiddenApiExemptions 方法上面還有一個 SetHiddenApiExemptions 的方法。

81  static void VMRuntime_setHiddenApiExemptions(JNIEnv* env, 82                                              jclass, 83                                              jobjectArray exemptions) { 92   93    Runtime::Current()->SetHiddenApiExemptions(exemptions_vec);

NATIVE_METHOD(VMRuntime, setHiddenApiExemptions, "([Ljava/lang/String;)V"),

/art/runtime/native/dalvik_system_VMRuntime.cc

setHiddenApiExemptions 是一個Native的方法。

270      /** 271       * Sets the list of exemptions from hidden API access enforcement. 272       * 273       * @param signaturePrefixes 274       *         A list of signature prefixes. Each item in the list is a prefix match on the type 275       *         signature of a blacklisted API. All matching APIs are treated as if they were on 276       *         the whitelist: access permitted, and no logging.. 277       */ 278      public native void setHiddenApiExemptions(String[] signaturePrefixes);

在VMRuntime.java 類中 發現可以呼叫的native 方法。

搜尋方法的全域性呼叫:

/art/test/674-hiddenapi/src-art/Main.java

99      if (whitelistAllApis) { 100        VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"}); 101     }

猜測這麼寫可以,訪問任何Api。

由此 我們簡單實現一個訪問系統Api的功能。

public void bypassHideApi() {    try {            Class VMRuntime = Class.forName("dalvik.system.VMRuntime");            Method setHiddenApiExemptions = VMRuntime.getDeclaredMethod("setHiddenApiExemptions", String[].class);            setHiddenApiExemptions.setAccessible(true);            Method getRuntime = VMRuntime.getDeclaredMethod( "getRuntime", new Class[]{});            getRuntime.setAccessible(true);            Object runtime = getRuntime.invoke(null);            setHiddenApiExemptions.invoke(runtime, new String[]{"Landroid/app/LoadedApk"});   } catch (Throwable e) {        e.printStackTrace();   } }

17:17:29.693 9140-9140/com.araon.demo W/com.araon.demo: Accessing hidden method Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V (blacklist, reflection) 17:17:29.694 9140-9140/com.araon.demo W/System.err: java.lang.NoSuchMethodException: setHiddenApiExemptions [class [Ljava.lang.String;]

但這時,我們發現VMRuntime類的setHiddenApiExemptions 方法本身就是被限制呼叫的,尷尬!!!

我回頭看我們上面GetMemberAction 的方法分析時,有一個點沒有仔細看

// Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access. 220    // This can be *very* expensive. Save it for last. 221    if (fn_caller_is_trusted(self)) { 222      // Caller is trusted. Exit. 223      return kAllow; 224   }

fn_caller_is_trusted 根據註釋和方法名字可以看出,如果是被信任的caller的話,是被允許呼叫的,fn_caller_is_trusted 就是 IsCallerTrusted 的函式指標

我們去看IsCallerTrusted 的實現。

​ 52   53  // Returns true if the first caller outside of the Class class or java.lang.invoke package 54  // is in a platform DEX file. 55  static bool IsCallerTrusted(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) { 56    // Walk the stack and find the first frame not from java.lang.Class and not from java.lang.invoke. 57    // This is very expensive. Save this till the last. 58    struct FirstExternalCallerVisitor : public StackVisitor { 59      explicit FirstExternalCallerVisitor(Thread* thread) 60         : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), 61            caller(nullptr) { 62     } 63   64      bool VisitFrame() REQUIRES_SHARED(Locks::mutator_lock_) { 65        ArtMethod *m = GetMethod(); 66        if (m == nullptr) { 67          // Attached native thread. Assume this is *not* boot class path. 68          caller = nullptr; 69          return false; 70       } else if (m->IsRuntimeMethod()) { 71          // Internal runtime method, continue walking the stack. 72          return true; 73       } 74   75        ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass(); 76        if (declaring_class->IsBootStrapClassLoaded()) { 77          if (declaring_class->IsClassClass()) { 78            return true; 79         } 80          // Check classes in the java.lang.invoke package. At the time of writing, the 81          // classes of interest are MethodHandles and MethodHandles.Lookup, but this 82          // is subject to change so conservatively cover the entire package. 83          // NB Static initializers within java.lang.invoke are permitted and do not 84          // need further stack inspection. 85          ObjPtr<mirror::Class> lookup_class = mirror::MethodHandlesLookup::StaticClass(); 86          if ((declaring_class == lookup_class || declaring_class->IsInSamePackage(lookup_class)) 87              && !m->IsClassInitializer()) { 88            return true; 89         } 90       } 91   92        caller = m; 93        return false; 94     } 95   96      ArtMethod* caller; 97   }; 98   99    FirstExternalCallerVisitor visitor(self); 100    visitor.WalkStack(); 101    return visitor.caller != nullptr && 102           hiddenapi::IsCallerTrusted(visitor.caller->GetDeclaringClass()); 103 }

先看最上面的註釋

// Returns true if the first caller outside of the Class class or java.lang.invoke package // is in a platform DEX file.

可以猜測意思 是 如果Class類或java.lang.invoke包之外的第一個呼叫方在platform DEX檔案中,則返回true。

也就是上面方法的主要內容:如果呼叫的類是 platform DEX的 是可以獲得方法的。

我們經過多次嘗試及網上大家的資料,通過下面方式嘗試過關。

try {            Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);            getDeclaredMethod.setAccessible(true); ​            Class VMRuntime = Class.forName("dalvik.system.VMRuntime");            Method getRuntime = (Method) getDeclaredMethod.invoke(VMRuntime, "getRuntime", new Class[]{});            Object runtime = getRuntime.invoke(null);            Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(VMRuntime, "setHiddenApiExemptions", newClass[]                   {String[].class});            setHiddenApiExemptions.setAccessible(true);            String[] args = {"Landroid/", "Lcom/android/"};            setHiddenApiExemptions.invoke(runtime, new Object[]{args}); } catch (Throwable e) {            e.printStackTrace(); }

\ \