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();
}