Android11 Approach to Bypass Reflection Limitation

  • 2021-12-13 17:14:54
  • OfStack

Directory 1. Background of the problem 2. Analyze the causes of the problem 3. Solutions

1. Background of the problem

Tencent Video found such an error when integrating our replay sdk, which caused the whole db mock function to completely fail.

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)

I clearly remember that we introduced a third-party solution, which solved this problem above 9.0. The general solution is as follows:


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

Scared me to see if there was anything fishy, and found that there was a problem on 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;]
......

2. Analyze the causes of the problem

In line with the tight time and heavy tasks as far as possible without affecting the progress, I still want to search on the Internet, but I found that they are all a bunch of old schemes. Have to go and see why. Why on earth? Just a few days ago, I asked my colleague for the source code of Android 11.


static jobject Class_getDeclaredMethodInternal(JNIEnv* env, 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());
}

If ShouldDenyAccessToMember returns true, then null is returned, and the upper layer throws an exception that the method cannot find. This is no different from Android P, except that ShouldBlockAccessToMember has been renamed.

ShouldDenyAccessToMember calls hiddenapi:: ShouldDenyAccessToMember, which is implemented as follows:


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 exposed API , directly through 
  if ((runtime_flags & kAccPublicApi) != 0) {
    return false;
  }

  // 2 : Not public API (That is, hidden API Objects of the caller and the accessed member  Domain 
  //  Mainly look at this 
  const AccessContext caller_context = fn_get_access_context();
  const AccessContext callee_context(member->GetDeclaringClass());

  // 3 If the caller is trusted, return directly 
  if (caller_context.CanAlwaysAccess(callee_context)) {
    return false;
  }
  // ......
  }

The original scheme failed, and the answer can be found in VisitFrame method of FirstExternalCallerVisitor


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  For  Enabled , skipping from  java.lang.reflect.*  Access to 
        //  This is the key to the system's limitation of "doll reflex" 
        ObjPtr<mirror::Class> proxy_class = GetClassRoot<mirror::Proxy>();
        if (declaring_class->IsInSamePackage(proxy_class) && declaring_class != proxy_class) {
            if (Runtime::Current()->isChangeEnabled(kPreventMetaReflectionBlacklistAccess)) {
                return true;
            }
        }
    }

    caller = m;
    return false;
}

3. Solutions

native hook takes the ShouldDenyAccessToMember method and returns directly to false Break the call stack around so that VM cannot recognize the caller

We are using the second scheme. Is there any way to make VM unable to recognize my call stack? This can be done by creating a new Thread with the JniEnv:: AttachCurrentThread (…) function. Specifically, we can look at https://developer.android.com/training/articles/perf-jni here, and then cooperate with std:: async (...) and std:: async:: get (...). The following are the key codes:


// java  Layer direct use  jni  Call this method 
static jobject Java_getDeclaredMethod(
    JNIEnv *env,
    jclass interface,
    jobject clazz,
    jstring method_name,
    jobjectArray params) {
  // ......  Omit 1 Some conversion codes 
  //   First use  std::async  Call  getDeclaredMethod_internal  Method 
  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,
    jobjectArray params) {
  //  This is where 1 Something ordinary  jni  Operated 
  JNIEnv *env = 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;
  // AttachCurrentThread  The core is here 
  int res = _vm->AttachCurrentThread(&env, nullptr);
  return env;
}

Related articles: