Android源码中现在有大量的方法和变量被@hide所修饰,而这些被hide修饰的方法和变量是不允许应用层进行反射获取的,所以富有探索精神的程序员们就开始想尽各种办法绕过系统hide限制来使用@hide修饰的方法和变量。

1、套娃(适配Android10即之前)

Android11之前我们可以使用套娃的形式来欺骗系统,让系统误以为是系统调用的hide方法。而到了Android11之后,套娃就已经失效了喽,要寻找新的方法来和系统对抗。

  1. 我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法。

  1. 我们通过刚刚反射拿到元反射方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法。

例子:

Method metaGetDeclaredMethod =
        Class.class.getDeclaredMethod("getDeclardMethod"); // 公开API,无问题
Method hiddenMethod = metaGetDeclaredMethod.invoke(hiddenClass,
        "hiddenMethod", "hiddenMethod参数列表"); // 系统类通过反射使用隐藏 API,检查直接通过。
hiddenMethod.invoke // 正确找到 Method 直接反射调用

代表:FreeReflection

2、源码分析

而目前的Android11和Android12系统修复了这个漏洞,套娃无法在继续使用。

所以我们只能另辟蹊径。

系统在判断调用者的时候是通过调用栈来判断调用者的方向的,所以只要我们在调用栈上做手脚,让系统误以为不是应用层的调用栈,即可绕过@hide限制。

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,则getDeclaredMethodInternal会返回null,则上层会抛出异常。

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

套娃为什么会失效,原因就在VisitFrame中

3、重点来了--解决方案(适配Anroid11&Android12)

我们解决方法的方向就是破坏调用堆栈,让系统无法识别api的真正调用者。

具体做法:

  1. 通过在jni层创建线程来执行真正的反射操作,当然,只是这样还不足以欺骗系统。

  1. 通过对线程调用attachthread方法来改变调用堆栈,从而达到欺骗系统的目的。google对attachthread的部分讲解:https://developer.android.com/training/articles/perf-jni?hl=zh-cn

4、源码

话不多说,直接上源码:

1、通过async来创建线程,因为async可以返回future来把异步同步化,线程内执行getDeclaredField_internal

Java_com_macoli_reflect_1helper_NativeReflect_getDeclaredField(JNIEnv *env, jobject t,
                                                               jclass clz, jstring fieldName) {
    auto global_clazz = env->NewGlobalRef(clz);
    jstring global_method_name = static_cast<jstring>(env->NewGlobalRef(fieldName)) ;
    //通过async来创建线程,因为async可以返回future来把异步同步化,线程内执行getDeclaredField_internal
    auto future = std::async(&getDeclaredField_internal, global_clazz, global_method_name);
    auto result = future.get();

    env->DeleteGlobalRef(global_clazz) ;
    env->DeleteGlobalRef(global_method_name) ;

    return result ;
}

2、真正执行反射操作

关键:attachCurrentThread()来对调用堆栈进行转变。


JNIEnv *attachCurrentThread() {
    JNIEnv *env;
    int res = _vm->AttachCurrentThread(&env, nullptr);
    __android_log_print(ANDROID_LOG_DEBUG, "native", "Found attached %d", res);
    return env;
}

void detachCurrentThread() {
    _vm->DetachCurrentThread();
}


static jobject getDeclaredField_internal(jobject object, jstring field_name) {

    JNIEnv *env = attachCurrentThread();//这里是重点

    jclass clazz_class = env->GetObjectClass(object);
    jmethodID methodId = env->GetMethodID(clazz_class, "getDeclaredField",
                                          "(Ljava/lang/String;)Ljava/lang/reflect/Field;");
    jobject res = env->CallObjectMethod(object, methodId, field_name);
    jobject global_res = nullptr;
    if (res != nullptr) {
        global_res = env->NewGlobalRef(res);
    }

    detachCurrentThread();
    return global_res;
}

测试:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.reflect_btn).setOnClickListener(v -> {
            try {

                 /** {@hide} */
                 /*
                public void setTraceTag(long traceTag) {
                    mTraceTag = traceTag;
                }
                * */
                Method m = ReflectHelper.getDeclaredMethod(Looper.class , "setTraceTag" , new Class[]{long.class}) ;
                m.invoke(Looper.getMainLooper() , 1000) ;
                Log.d("gggl" , "setTraceTag " + m.toString()) ;

                Class<?> activityThreadCls = Class.forName("android.app.ActivityThread") ;
                Field f = ReflectHelper.getDeclaredField(activityThreadCls , "DEBUG_BROADCAST") ;
                Object tag = f.get(null) ;
                Log.d("gggl" , f.toString()) ;
                Log.d("gggl" , tag.toString()) ;
//                CpuUsageInfo cpuUsageInfo = new CpuUsageInfo() ;
                /** @hide */
                /*public CpuUsageInfo(long activeTime, long totalTime) {
                    mActive = activeTime;
                    mTotal = totalTime;
                }*/
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    Constructor cpuUsageInfoConstructor = ReflectHelper.getDeclaredConstructor(CpuUsageInfo.class , new Class[]{long.class , long.class}) ;
                    Object cpuUsageObj = cpuUsageInfoConstructor.newInstance(100 , 100) ;
                    Method getTotalM = CpuUsageInfo.class.getDeclaredMethod("getTotal") ;
                    Object r = getTotalM.invoke(cpuUsageObj) ;
                    Log.d("gggl" , "getTotal = " + r) ;
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

华为mate 40 pro 测试结果:

总结:

源码已上传到gitee:https://gitee.com/gggl/reflect_helper

反射Android @hide api 适配

Android11&Android12

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐