Description
Due to the characteristics of our container products, we need to run the application completely, so we must involve some reflection calls of hidden interfaces, and breaking the reflection restrictions has become the foundation of our implementation. Now we share our solution with everyone, let's learn together.
Android 9.0 → First enabled
This is a principle that everyone knows, let's talk about it simply, tracing back from the bottom up.

1. Find the exemption points of API judgment rules.
// source code: art/runtime/hidden_api.cc
template<typename T>
bool ShouldDenyAccessToMemberImpl(T* member, ApiList api_list, AccessMethod access_method) {
// ......
// Check for an exemption first. Exempted APIs are treated as SDK.
if (member_signature.DoesPrefixMatchAny(runtime->GetHiddenApiExemptions())) {
// Avoid re-examining the exemption list next time.
// Note this results in no warning for the member, which seems like what one would expect.
// Exemptions effectively adds new members to the public API list.
MaybeUpdateAccessFlags(runtime, member, kAccPublicApi);
return false;
}
// ......
return deny_access;
}
2. Find the member attribute location.
// source code /art/runtime/runtime.h
class Runtime {
public:
// ......
void SetHiddenApiExemptions(const std::vector<std::string>& exemptions) {
hidden_api_exemptions_ = exemptions;
}
const std::vector<std::string>& GetHiddenApiExemptions() {
return hidden_api_exemptions_;
}
// ......
};
3. Find the setting method
// source code: /art/runtime/native/dalvik_system_VMRuntime.cc
// ......
static void VMRuntime_setHiddenApiAccessLogSamplingRate(JNIEnv*, jclass, jint rate) {
Runtime::Current()->SetHiddenApiEventLogSampleRate(rate);
}
// ......
static JNINativeMethod gMethods[] = {
// ......
NATIVE_METHOD(VMRuntime, setHiddenApiExemptions, "([Ljava/lang/String;)V"),
// ......
};
void register_dalvik_system_VMRuntime(JNIEnv* env) {
REGISTER_NATIVE_METHODS("dalvik/system/VMRuntime");
}
4. Find the upper call entry.
// source code /libcore/libart/src/main/java/dalvik/system/VMRuntime.java
package dalvik.system;
public final class VMRuntime {
/**
* Sets the list of exemptions from hidden API access enforcement.
*
* @param signaturePrefixes
* A list of signature prefixes. Each item in the list is a prefix match on the type
* signature of a blacklisted API. All matching APIs are treated as if they were on
* the whitelist: access permitted, and no logging.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
public native void setHiddenApiExemptions(String[] signaturePrefixes);
}
5. Formulate a solution.
try {
Method mm = Class.class.getDeclaredMethod("forName", String.class);
Class<?> cls = (Class)mm.invoke((Object)null, "dalvik.system.VMRuntime");
mm = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
Method m = (Method)mm.invoke(cls, "getRuntime", null);
Object vr = m.invoke((Object)null);
m = (Method)mm.invoke(cls, "setHiddenApiExemptions", new Class[]{String[].class});
String[] args = new String[]{"L"};
m.invoke(vr, args);
} catch (Throwable e) {
e.printStackTrace();
}
Android 11.0 → Access restriction upgrade
Starting from this version, the system has upgraded the access restrictions of the upper-level interfaces, directlyVMRuntime
class interface restriction upgrade, so it can only be accessed throughnative
layer for access. The principle remains the same, using system loadinglib
when the library is loadedJNI_OnLoad
invoked by reflectionsetHiddenApiExemptions
at this timecaller
asjava.lang.System
itsdomain
at levellibcore.api.CorePlatformApi
,and you can accesshiddenapi
。
Method 1: Reflection invocation
static int setApiBlacklistExemptions(JNIEnv* env) {
jclass jcls = env->FindClass("dalvik/system/VMRuntime");
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return -1;
}
jmethodID jm = env->GetStaticMethodID(jcls, "setHiddenApiExemptions", "([Ljava/lang/String;)V");
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
return -2;
}
jclass stringCLass = env->FindClass("java/lang/String");
jstring fakeStr = env->NewStringUTF("L");
jobjectArray fakeArray = env->NewObjectArray(1, stringCLass, NULL);
env->SetObjectArrayElement(fakeArray, 0, fakeStr);
env->CallStaticVoidMethod(jcls, jm, fakeArray);
env->DeleteLocalRef(fakeStr);
env->DeleteLocalRef(fakeArray);
return 0;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
//......
JNIEnv * env = NULL;// got env from JavaVM
// make sure call here
setApiBlacklistExemptions(env);
//......
return 0;
}
Method 2: Direct function call.
Replace the system's
libart.so
Exported, check the exported functions in IDA.c
The function name is:_ZN3artL32VMRuntime_setHiddenApiExemptionsEP7_JNIEnvP7_jclassP13_jobjectArray
void* utils_dlsym_global(const char* libName, const char* funcName) {
void* funcPtr = NULL;
void* handle = dlopen(libName, RTLD_LAZY|RTLD_GLOBAL);
if (__LIKELY(handle)) {
funcPtr = dlsym(handle, funcName);
}
LOGE("dlsym: %s, %s, %d, %s", libName, funcName, errno, strerror(errno));
__ASSERT(0);
}
return funcPtr;
}
typedef void *(*setHiddenApiExemptions_Func)(JNIEnv* env, jclass, jobjectArray exemptions);
int fixHiddenApi(JNIEnv* env) {
setHiddenApiExemptions_Func func = (setHiddenApiExemptions_Func)utils_dlsym_global("libart.so", "_ZN3artL32VMRuntime_setHiddenApiExemptionsEP7_JNIEnvP7_jclassP13_jobjectArray");
__ASSERT(func)
if (__UNLIKELY(!func)) return -1;
jclass stringCLass = env->FindClass("java/lang/String");
jstring fakeStr = env->NewStringUTF("L");
jobjectArray fakeArray = env->NewObjectArray(1, stringCLass, NULL);
env->SetObjectArrayElement(fakeArray, 0, fakeStr);
func(env, NULL, fakeArray);
env->DeleteLocalRef(fakeArray);
if (env->ExceptionCheck()) {
LOG_JNI_EXCEPTION(env, true)
return -2;
}
return 0;
}
Android 14 & Hongmeng 4 → Exception patch
Under normal circumstances, the above methods can all achieve the unlocking of access to hidden interfaces, but through compatibility testing, in the latest versions of Hongmeng and Xiaomi systems, there are still some logs that appear occasionally:
Accessing hidden method Landroid/app/IUiModeManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IUiModeManager; (max-target-p, reflection, denied)
In fact, other hidden classes can be accessed normally, and this class can also be accessed for a period of time, and this problem will appear after running for a period of time. GuessROM
Customized some caching mechanisms. Then try another approach: usingVM
The way to identify the caller that breaks the call stack cannot be recognized. This can be achieved through a new thread created by the function, at this point, we are in a newVM
There is no call history record in the call stack.
#include <future>
static jobject reflect_getDeclaredMethod_internal(jobject clazz, jstring method_name, jobjectArray params) {
bool attach = false;
JNIEnv *env = jni_get_env(attach);
if (!env) return;
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);
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
}
jobject global_res = nullptr;
if (res != nullptr) {
global_res = env->NewGlobalRef(res);
}
jni_env_thread_detach();
return global_res;
}
jobject reflect_getDeclaredMethod(JNIEnv *env, jclass interface, jobject clazz, jstring method_name, jobjectArray params) {
jobject global_clazz = env->NewGlobalRef(clazz);
jstring global_method_name = (jstring) env->NewGlobalRef(method_name);
int arg_length = env->GetArrayLength(params);
jobjectArray global_params = nullptr;
if (params != nullptr) {
jobject element;
for (int i = 0; i < arg_length; i++) {
element = (jobject) env->GetObjectArrayElement(params, i);
env->SetObjectArrayElement(params, i, env->NewGlobalRef(element));
}
global_params = (jobjectArray) env->NewGlobalRef(params);
}
auto future = std::async(&reflect_getDeclaredMethod_internal, global_clazz, global_method_name, global_params);
return future.get();
}
As mentioned above, we can extend corresponding functions for other commonly used implementations (such asgetMethod
,getDeclaredField
,getField
etc.). However, our container project needs to be compatible with older versions, so we cannot use the higher versionstd::async
features, for this reason we wrote apthead
compatibility version that can adapt to lower versions,ndk
Compile.
int ThreadAsyncUtils::threadAsync(BaseThreadAsyncArgument& argument) {
pthread_t thread;
int ret = pthread_create(&thread, NULL, threadAsyncInternal, &argument);
if (0 != ret) {
LOGE("thread async create error: %d, %s", errno, strerror(errno))
return ret;
}
ret = pthread_join(thread, NULL);
if (0 != ret) {
LOGE("thread async join error: %d, %s", errno, strerror(errno))
return ret;
}
return 0;
}
static void reflect_getDeclaredMethod_internal(BaseThreadAsyncArgument* _args) {
ReflectThreadAsyncArgument* args = (ReflectThreadAsyncArgument*)_args;
jobject clazz = args->jcls_clazz;
jstring method_name = args->jcls_name;
jobjectArray params = args->jcls_params;
bool attach = false;
JNIEnv *env = jni_get_env(attach);
if (!env) return;
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);
if (env->ExceptionCheck()) {
LOG_JNI_CLEAR_EXCEPTION(env)
}
if (res != nullptr) {
args->jcls_result = env->NewGlobalRef(res);
}
jni_env_thread_detach();
}
jobject ReflectUtils::getDeclaredMethod(JNIEnv *env, jclass interface, jobject clazz, jstring method_name, jobjectArray params) {
auto global_clazz = env->NewGlobalRef(clazz);
jstring global_method_name = (jstring) env->NewGlobalRef(method_name);
int arg_length = env->GetArrayLength(params);
jobjectArray global_params = nullptr;
if (params != nullptr) {
jobject element;
for (int i = 0; i < arg_length; i++) {
element = (jobject) env->GetObjectArrayElement(params, i);
env->SetObjectArrayElement(params, i, env->NewGlobalRef(element));
}
global_params = (jobjectArray) env->NewGlobalRef(params);
}
ReflectThreadAsyncArgument argument(reflect_getDeclaredMethod_internal);
argument.setMethod(global_clazz, global_method_name, global_params);
if (0 == ThreadAsyncUtils::threadAsync(argument)) {
return argument.jcls_result;
}
return NULL;
}
This method is used as compensation when the retrieval fails, as the implementation is asynchronous thread to synchronous, which is inefficient. It is usually only used when we are sure that it exists but failed to retrieve it.

评论已关闭