Android11 Approach to Bypass Reflection Limitation
- 2021-12-13 17:14:54
- OfStack
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;
}