1. The background of the problem

Tencent Video found such a mistake when integrating our Replay SDK, resulting in the complete failure of the whole DB mock function.

Accessing hidden field Landroid/database/sqlite/SQLiteCursor;
->mDriver:Landroid/database/sqlite/SQLiteCursorDriver; (greylist-max-o, reflection, denied)

java.lang.NoSuchFieldException: No field mDriver in class Landroid/database/sqlite/SQLiteCursor; 
(declaration of 'android.database.sqlite.SQLiteCursor' appears in /system/framework/framework.jar)
Copy the code

I clearly remember that we introduced a third party solution that solved this problem in 9.0 +. The general solution looks like this:

if (SDK_INT >= Build.VERSION_CODES.P) { try { Method forName = Class.class.getDeclaredMethod("forName", String.class); Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); Class<? > vmRuntimeClass = (Class<? >) forName.invoke(null, "dalvik.system.VMRuntime"); Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null); setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class}); sVmRuntime = getRuntime.invoke(null); } catch (Throwable e) { Log.e(TAG, "reflect bootstrap failed:", e); }}Copy the code

Horrified, I went to see if there was anything wrong with Android 11:

Accessing hidden method Ldalvik/system/VMRuntime;
->setHiddenApiExemptions([Ljava/lang/String;)V (blacklist,core-platform-api, reflection, denied)

Caused by: java.lang.NoSuchMethodException: dalvik.system.VMRuntime.setHiddenApiExemptions [class [Ljava.lang.String;]
......
Copy the code

2. Analyze the cause of the problem

Under the condition of tight time and heavy task without affecting the progress, I still wanted to search online, but found that they were all a bunch of old schemes. Forced to find out why? Why on earth? Just a few days ago, I asked my colleague for an Android 11 source code.

Env static jobject Class_getDeclaredMethodInternal (JNIEnv *, jobject javaThis, jstring name, jobjectArray args) {/ /... Handle<mirror::Method> result = hs.NewHandle( mirror::Class::GetDeclaredMethodInternal<kRuntimePointerSize>( soa.Self(),  klass, soa.Decode<mirror::String>(name), soa.Decode<mirror::ObjectArray<mirror::Class>>(args), GetHiddenapiAccessContextFunction(soa.Self()))); if (result == nullptr || ShouldDenyAccessToMember(result->GetArtMethod(), soa.Self())) { return nullptr; } return soa.AddLocalReference<jobject>(result.Get()); }Copy the code

If ShouldDenyAccessToMember returns true, then null is returned and the upper layer throws an exception that the method couldn’t find. And Android P here is no different, just changed ShouldBlockAccessToMember a name. ShouldDenyAccessToMember will call to hiddenapi: : ShouldDenyAccessToMember, the function is this:

template<typename T> inline bool ShouldDenyAccessToMember(T* member, const std::function<AccessContext()>& fn_get_access_context, AccessMethod access_method) REQUIRES_SHARED(Locks::mutator_lock_) { const uint32_t runtime_flags = GetRuntimeFlags(member); // 1: If the member is a public API, use if ((runtime_flags & kAccPublicApi)! = 0) { return false; } // 2: not public API, get the Domain of the caller and the accessed member // const AccessContext caller_context = fn_get_access_context(); const AccessContext callee_context(member->GetDeclaringClass()); // 3: If the caller is trusted, return if (caller_context.canalwaysAccess (callee_context)) {return false; } / /... }Copy the code

The original scheme failed to find the answer in FirstExternalCallerVisitor VisitFrame method

bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) { ArtMethod *m = GetMethod(); . ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass(); if (declaring_class->IsBootStrapClassLoaded()) { ...... // If PREVENT_META_REFLECTION_BLACKLIST_ACCESS is Enabled, ObjPtr<mirror::Class> proxy_class = GetClassRoot<mirror::Proxy>(); ObjPtr<mirror::Class> proxy_class = GetClassRoot >(); if (declaring_class->IsInSamePackage(proxy_class) && declaring_class ! = proxy_class) { if (Runtime::Current()->isChangeEnabled(kPreventMetaReflectionBlacklistAccess)) { return true; } } } caller = m; return false; }Copy the code

3. Solutions

  • Native hook ShouldDenyAccessToMember and return false directly
  • Breaking the call stack goes around so that the VM cannot recognize the caller

We took the second approach. Is there any way that the VM can’t recognize my call stack? This can be achieved by a JniEnv: : AttachCurrentThread (…). The function creates a new Thread to complete. Specific developer.android.com/training/ar we can see here… And then with STD ::async(…) And STD: : async: : get (..) Here’s the key code:

Static jobject Java_getDeclaredMethod(JNIEnv *env, jclass interface, jobject clazz, jstring method_name, jobjectArray params) { // ...... Auto Future = STD ::async(&getDeclaredmethod_internal) global_clazz, global_method_name, global_params); auto result = future.get(); return result; } static jobject getDeclaredMethod_internal( jobject clazz, jstring method_name, JNIEnv *env = attachCurrentThread(); jobjectArray params) {attachCurrentThread(); jclass clazz_class = env->GetObjectClass(clazz); jmethodID get_declared_method_id = env->GetMethodID(clazz_class, "getDeclaredMethod", "(Ljava/lang/String; [Ljava/lang/Class;)Ljava/lang/reflect/Method;"); jobject res = env->CallObjectMethod(clazz, get_declared_method_id, method_name, params); detachCurrentThread(); return env->NewGlobalRef(res); } JNIEnv *attachCurrentThread() { JNIEnv *env; // Int res = _vm->AttachCurrentThread(&env, nullptr);Copy the code