Android源码中现在有大量的方法和变量被@hide所修饰,而这些被hide修饰的方法和变量是不允许应用层进行反射获取的,所以富有探索精神的程序员们就开始想尽各种办法绕过系统hide限制来使用@hide修饰的方法和变量。
1、套娃(适配Android10即之前)
Android11之前我们可以使用套娃的形式来欺骗系统,让系统误以为是系统调用的hide方法。而到了Android11之后,套娃就已经失效了喽,要寻找新的方法来和系统对抗。
我们通过反射 API 拿到 getDeclaredMethod 方法。getDeclaredMethod 是 public 的,不存在问题;这个通过反射拿到的方法我们称之为元反射方法。
我们通过刚刚反射拿到元反射方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们的元反射方法也是被系统类加载的方法;所以我们的元反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法。
例子:
Method metaGetDeclaredMethod =
Class.class.getDeclaredMethod("getDeclardMethod"); // 公开API,无问题
Method hiddenMethod = metaGetDeclaredMethod.invoke(hiddenClass,
"hiddenMethod", "hiddenMethod参数列表"); // 系统类通过反射使用隐藏 API,检查直接通过。
hiddenMethod.invoke // 正确找到 Method 直接反射调用
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的真正调用者。
具体做法:
通过在jni层创建线程来执行真正的反射操作,当然,只是这样还不足以欺骗系统。
通过对线程调用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;
}
总结:
源码已上传到gitee:https://gitee.com/gggl/reflect_helper
Android11&Android12