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

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

本文是之前日常学习时写下,现在上传发布,文章借鉴了部分大佬的思路,如有不妥,请多指教。

ART方法调用分析:

通常情况下,我们通过反射去调用某个类的方法时,在Java层我们会这么实现。

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

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 方法。

先分析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 方法内部先是去找 虚方法,在找类自身的正常方法,再去找接口方法。

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方法里, 先判断是否要开启访问限制的检查。并判断方法属于那个限制名单里还是直接拒绝。

在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。

也就是上面方法的主要内容:if (declaring_class->IsBootStrapClassLoaded()) 是调用的类是 IsBootStrapClassLoader的 是可以获得方法的。

我们经过多次尝试及网上大家的资料,通过下面方式尝试过关。

 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", new Class[]                    {String[].class});
            setHiddenApiExemptions.setAccessible(true);
            String[] args = {"Landroid/", "Lcom/android/"};
            setHiddenApiExemptions.invoke(runtime, new Object[]{args});
 } catch (Throwable e) {
            e.printStackTrace();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值