首页 > 其他分享 >再聊解除HiddenApi限制

再聊解除HiddenApi限制

时间:2024-04-22 21:58:45浏览次数:21  
标签:clazz return global HiddenApi 解除 params env 再聊 method

炒冷饭,再聊聊大家都知晓的隐藏接口的限制解除。

说明

由于我们容器产品的特性,需要将应用完整的运行起来,所以必须涉及一些隐藏接口的反射调用,而突破反射限制则成为我们实现的基础。现将我们的解决方案分享给大家,一起学习。

Android 9.0 → 首次启用

这个大家都知道原理了,简单巴拉巴拉下,从下往上溯源。

1、找到API判断规则豁免点。

// 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、找到成员属性位置。

// 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、找到设置方法

// 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、找到上层调用入口。

// 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、形成解决方案。

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 → 限制升级

从此版本开始,系统升级了上层接口的访问限制,直接将VMRuntime的类接口限制升级,因此只能通过native层进行访问。原理不变,利用系统加载lib库时JNI_OnLoad通过反射调用setHiddenApiExemptions,此时callerjava.lang.Systemdomain级别为libcore.api.CorePlatformApi,就可以访问hiddenapi了。

方式1:反射调用

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;
}

方式2:直接函数调用。

将系统的libart.so导出来,在IDA中查看导出的c函数名为:_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);
    } else {
        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 & 鸿蒙4 → 异常补丁

通常情况下以上方法均可以达到隐藏接口的访问解除,但是我们通过兼容性测试,在鸿蒙和小米的最新版本系统,某些时候依然还是会出现一下日志:

Accessing hidden method Landroid/app/IUiModeManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IUiModeManager; (max-target-p, reflection, denied)

而实际上其他的隐藏类是可以正常访问的,并且在一段时间内该类也是可以访问的,运行一段时间后就出现此问题。猜测ROM定制了一些缓存机制。于是尝试另一种方案:利用VM无法识别调用者的方式破坏调用堆栈。这可以通过函数创建的新线程,此时,我们处于一个新的VM调用堆栈中,没有任何调用历史记录。

#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();
}

和上面一样,我们可以扩展出对应其他常用的函数实现(如getMethodgetDeclaredFieldgetField等)。只不过我们的容器项目需要兼容较久的版本,因此不能使用高版本的std::async特性,为此我们写了一个pthead的兼容性版本,可以适配低版本的ndk编译。

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;
}

此方法作为当获取失败时,再调用此方法补偿,由于方案实现为异步线程转同步,故效率低下,通常只有在我们确定存在但获取失败的时候才会使用。

标签:clazz,return,global,HiddenApi,解除,params,env,再聊,method
From: https://www.cnblogs.com/iofomo/p/18151624

相关文章

  • C# 冻结Excel窗口以锁定行列、或解除冻结
    在处理大型Excel工作簿时,有时候我们需要在工作表中冻结窗格,这样可以在滚动查看数据的同时保持某些行或列固定不动。冻结窗格可以帮助我们更容易地导航和理解复杂的数据集。相反,当你不需要冻结窗格时,你可能需要解冻它们以获得完整的视野。下面将介绍如何使用免费.NET库通过C#实现......
  • 极域电子教室解除控制
    文章目录前言法一、法三、前言学校机房里的极域很烦人,会影响到我们的工作下面分享几个可以破解极域的方法。法一、(推荐指数:3)右键桌面图标,点击文件所在位置。将程序全选并右键删除。点击重试点击跳过(若对话框没有消失则重复2)win+l,重新登陆好好工作吧#缺点:老师......
  • 【技巧】如何解除Excel“打开密码”?
    给Excel表格设置“打开密码”,可以保护表格不被他人随意打开,那如果后续不需要保护了,不想每次打开Excel都需要输密码,要怎么去除“打开密码”呢?今天分享3个方法,最后一个方法记得收藏起来,以备不时之需!方法一1.通过密码打开Excel表格后,点击菜单选项卡【文件】,出现新页面后,再依次......
  • 再聊SPI机制
    前言去年更新了一系列和SPI相关的内容,最近因为业务需要,我又基于业务场景,实现了一版。对于什么是spi,很久之前有写过一篇文章,java之spi机制简介感兴趣的朋友可以蛮看一下需求分析用过原生jdk提供的spi的朋友,应该会知道原生jdk的spi有个缺陷,就是没法实现按需加载,因此本文的实现就......
  • 达梦数据库如何解除主从关系
    环境:OS:Centos7DB:DMV8 1.停掉dmwatcher并删除服务[root@host02root]#systemctlstopDmWatcherServiceGRP1[root@host02root]#cd/dmdbms/product/script/root[root@host02root]#./dm_service_uninstaller.sh-nDmWatcherServiceGRP1 2.删除实时归档[dmdba@host......
  • vim 批量注释与解除注释
    方法一:视图模式(ctrl+v)注:插入操作仅vim可用删除(当删除内容为行首的#时,即可批量解除注释):光标移动(上,下,左,右键/k,j,l,h)选中块然后del删除块(del/dd)插入(当行首块批量插入内容为#时,即可完成批量注释)光标移动(上,下,左,右键/k,j,l,h)选中块然后按插入符(I:光标前插入;A:光......
  • 【操作系统】线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防,死锁的避免(银行
    目录线程、程序、进程死锁的必要条件?如何避免死锁?死锁的预防死锁的避免(银行家)死锁的检测进程-资源分配图死锁检测步骤死锁的解除线程、程序、进程进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行......
  • 解除网页限制复制的问题
    新建一个网页书签书签内容:javascript:(function(bookmarklets)%7Bfor(vari=0;i<bookmarklets.length;i++)%7Bvarcode=bookmarklets%5Bi%5D.url;if(code.indexOf("javascript:")!=-1)%7Bcode=code.replace("javascript:","");eval(code)%7Delse%7Bc......
  • 再聊对架构决策记录的一些思考
    1引言第一次在社区发文聊ADR(架构决策记录)是在2022年8月份,在文章( 轻量级ADR机制 )中,详细介绍了以下几个主题:•团队研发面临的主要问题•ADR的结构剖析•ADR的存储形式•ADR在研发流程中所处的位置•ADR常见的误区与疑问在实践中发现仍然有一些普遍性问题与挑战可以探讨。......
  • 最高法--合同履行过程中因一方当事人瑕疵违约导致合同履行存在障碍,但仍属正常商业风险
    (2021)最高法民终891号  陈锦江与顺联房地产(武汉)开发有限公司、伟联置业建筑有限公司房地产开发经营合同纠纷二审上诉人主张:三、原判决适用法律错误。一审判决认为根据邮件投递记录,陈锦江向伟联公司邮寄的书面通知书未妥投,书面解除通知未能送达对方全部合同当事人,即使陈锦江享......