freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

再聊解除HiddenApi限制
2024-04-22 21:41:44

说明

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

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

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

# android安全 # Android # android安全工具 # Android开发 # android逆向
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
  • 0 文章数
  • 0 关注者
文章目录