https://github.com/Pangu-Immortal
先来分析一下问题出现的原因
我们看一眼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;
}
我们看一下如何解决
- 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;
}