ART Learning Series: Get Method Process Analysis and Access Restriction Breakthrough

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

Guess you like

Origin blog.csdn.net/ziyunLLL/article/details/123140619