Android 11 绕过反射限制

作者: 红橙Darren | 来源:发表于2021-07-14 19:45 被阅读0次

    1. 问题出现的背景

    腾讯视频在集成我们 replay sdk 的时候发现这么个错误,导致整个 db mock 功能完全失效。

    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)
    

    我清晰的记得我们引入了一个第三方解决方案,在 9.0 以上已经解决了这个问题,大致的方案是这样的:

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

    吓得我赶紧去看下到底有没有猫腻,发现在 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. 分析问题出现的原因

    本着时间紧任务重尽量不影响进度的情况下,我还是想去网上搜索看看,但是发现都是一堆旧的方案。迫不得已去看看到底为什么?到底为什么?刚好前几天找同事要了一份 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());
    }
    

    如果 ShouldDenyAccessToMember 返回 true,那么就会返回 null,上层就会抛出方法找不到的异常。这里和 Android P 没什么不同,只是把 ShouldBlockAccessToMember 改了个名而已。
    ShouldDenyAccessToMember 会调用到 hiddenapi::ShouldDenyAccessToMember,该函数是这样实现的:

    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:如果该成员是公开API,直接通过
      if ((runtime_flags & kAccPublicApi) != 0) {
        return false;
      }
    
      // 2:不是公开API(即为隐藏API),获取调用者和被访问成员的 Domain 
      // 主要看这个
      const AccessContext caller_context = fn_get_access_context();
      const AccessContext callee_context(member->GetDeclaringClass());
    
      // 3:如果调用者是可信的,直接返回
      if (caller_context.CanAlwaysAccess(callee_context)) {
        return false;
      }
      // ......
      }
    

    原来的方案失效了能在 FirstExternalCallerVisitor 的 VisitFrame 方法中找到答案

    bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
        ArtMethod *m = GetMethod();
        ......
        ObjPtr<mirror::Class> declaring_class = m->GetDeclaringClass();
        if (declaring_class->IsBootStrapClassLoaded()) {
            ......
            // 如果 PREVENT_META_REFLECTION_BLACKLIST_ACCESS 为 Enabled,跳过来自 java.lang.reflect.* 的访问
            // 系统对“套娃反射”的限制的关键就在此
            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. 解决方案

    • native hook 住 ShouldDenyAccessToMember 方法,直接返回 false
    • 破坏调用堆栈绕过去,使 VM 无法识别调用方

    我们采用的是第二种方案,有什么方法可以让 VM 无法识别我的调用栈呢?这可以通过 JniEnv::AttachCurrentThread(…) 函数创建一个新的 Thread 来完成。具体我们可以看下这里 https://developer.android.com/training/articles/perf-jni ,然后配合 std::async(…) 与 std::async::get(..) 就能搞定了,下面是关键代码:

    // java 层直接用 jni 调用这个方法
    static jobject Java_getDeclaredMethod(
        JNIEnv *env,
        jclass interface,
        jobject clazz,
        jstring method_name,
        jobjectArray params) {
      // ...... 省掉一些转换代码
      //  先用 std::async 调用 getDeclaredMethod_internal 方法
      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) {
      // 这里就是一些普通的 jni 操作了
      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 核心在这里
      int res = _vm->AttachCurrentThread(&env, nullptr);
      return env;
    }
    

    相关文章

      网友评论

        本文标题:Android 11 绕过反射限制

        本文链接:https://www.haomeiwen.com/subject/tieaultx.html