Android 9.0 → First enabled

0 21
DescriptionDue to the characteristics of our container products, we need to run...

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.

Android 9.0 → First enabled

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, directlyVMRuntimeclass interface restriction upgrade, so it can only be accessed throughnativelayer for access. The principle remains the same, using system loadinglibwhen the library is loadedJNI_OnLoadinvoked by reflectionsetHiddenApiExemptionsat this timecallerasjava.lang.Systemitsdomainat 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'slibart.soExported, check the exported functions in IDA.cThe 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. GuessROMCustomized some caching mechanisms. Then try another approach: usingVMThe 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 newVMThere 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,getFieldetc.). However, our container project needs to be compatible with older versions, so we cannot use the higher versionstd::asyncfeatures, for this reason we wrote aptheadcompatibility version that can adapt to lower versions,ndkCompile.

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.

你可能想看:
最后修改时间:
admin
上一篇 2025年03月27日 03:11
下一篇 2025年03月27日 03:34

评论已关闭