ART学习系列: Android 9 Get Method 过程分析及 访问限制突破

1,153 阅读11分钟

ART学习系列: Android 9 Get Method 过程分析及 访问限制突破

背景:

从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制

api.png

但是 VSA 要完成一些功能,必须得去调用隐藏的Api,所以要进行限制突破。现在就是把这个过程 总结起来,跟大家一起学习。

参考:developer.android.com/guide/app-c…

学习过程:

我们先了解一下,反射调用一个方法,在虚拟机里怎么实现的

前提基础:

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<1hs(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* selfREQUIRES_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();
}


\