ART Learning Series: Get Method Process Analysis and Access Restriction Breakthrough
This article was written during my daily study before, and now it is uploaded and published. The article draws on the ideas of some big guys. If there is anything wrong, please advise.
ART method call analysis:
Usually, when we call a method of a class through reflection, we will implement this in the Java layer.
try {
Class loadedApk = Class.forName("android.app.LoadedApk");
Method getApplication = loadedApk.getDeclaredMethod("getApplication",null);
} catch (Throwable e) {
e.printStackTrace();
}
The getDeclaredMethod method of the Class class finds the corresponding Method structure according to the incoming method name and signature.
Let's analyze how getDeclaredMethod is implemented.
First take Android 9 as an example:
getDeclaredMethod is implemented in Class.java as follows
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
return getMethod(name, parameterTypes, false);
}
Look at the implementation of 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;
}
The value of recursivePublicMethods is passed false in getMethod(name, parameterTypes, false); so call getDeclaredMethodInternal at this time
private native Method getDeclaredMethodInternal(String name, Class<?>[] args);
At this point, you can see that getDeclaredMethodInternal is a native method. Then let's go to native to find the method.
/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 }
Also in the Native layer, the GetDeclaredMethodInternal method of Class is called, and we also found the ShouldBlockAccessToMember method in the returned result.
First analyze the GetDeclaredMethodInternal method.
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 }
From the above, the GetDeclaredMethodInternal method first looks for the virtual method, then looks for the normal method of the class itself, and then looks for the interface 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);
From this step, whether the virtual method or the standard method is to find the corresponding ArtMethod structure based on cheapness.
Next, analyze the ShouldBlockAccessToMember method. After getting the structure of ART Method from getDeclaredMethod in the previous step, it will judge again whether it can return the real 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 }
It mainly depends on whether the Action returned by GetMemberAction is 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 }
The first step is to judge whether the Flag of the method is access restriction.
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 }
In the GetActionFromAccessFlags method, first determine whether to enable access restriction checks. And judge whether the method belongs to the restricted list or reject it directly.
In GetMemberAction, if the method denies access, it will execute 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 }
In this method we see a difference. The method first obtains the instance object of the runtime, and calls the GetHiddenApiExemptions method.
The GetHiddenApiExemptions returns a list of arrays that are listed as specially accessible methods.
That is to say, to check the access rights, even if it is in the restricted list, there needs to be a white list of allowed methods at the end.
Therefore, after a series of searches and permission access judgments, it is returned whether the Method method object can be obtained.
In the process of analyzing the above we see that.
Break out:
Added access restrictions for hidden APIs on Android P. If you want to break through the restricted access to the hidden API, you can start from two points.
(1) Runtime::Current()->GetHiddenApiEnforcementPolicy(); This method in Runtime returns whether restriction is required.
You can hook from this point. Runtime's GetHiddenApiEnforcementPolicy method cancels the restriction judgment.
(2) if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {} has such a judgment.
There is a whitelist in Runtime, even hidden APIs in the whitelist can be accessed.
We will continue to analyze this.
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 }
We found that there is a SetHiddenApiExemptions method above the GetHiddenApiExemptions method.
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 is a Native method.
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);
The native methods that can be called are found in the VMRuntime.java class.
A global call to the search method:
/art/test/674-hiddenapi/src-art/Main.java
99 if (whitelistAllApis) {
100 VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{
"L"});
101 }
Guess it can be written like this, and access any Api.
From this, we simply implement a function of accessing the system 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;]
But at this time, we found that the setHiddenApiExemptions method of the VMRuntime class itself is restricted to call, embarrassing! ! !
When I looked back at the method analysis of GetMemberAction above, there was one point that I didn’t look at carefully.
// 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 According to the comments and method names, it can be seen that if it is a trusted caller, it is allowed to call, and fn_caller_is_trusted is the function pointer of IsCallerTrusted
Let's look at the implementation of 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 }
Look at the top comment first
// Returns true if the first caller outside of the Class class or java.lang.invoke package
// is in a platform DEX file.
You can guess that it means if the Class class or java.lang.invoke package Returns true if the first outside caller is in the platform DEX file.
That is the main content of the above method: if (declaring_class->IsBootStrapClassLoaded()) is that the called class is IsBootStrapClassLoader and the method can be obtained.
After many attempts and everyone's information on the Internet, we tried to pass the level through the following methods.
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();
}