欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]改一个参数即可绕过 Android N 的私有 API 链接限制

[原创]改一个参数即可绕过 Android N 的私有 API 链接限制

从 Android N 开始,对 NDK 调用私有 API 的行为做了限制。在 Android 7.0 行为变更中明确提到:

从 Android 7.0 开始,系统将阻止应用动态链接非公开 NDK 库,这种库可能会导致您的应用崩溃。此行为变更旨在为跨平台更新和不同设备提供统一的应用体验。即使您的代码可能不会链接私有库,但您的应用中的第三方静态库可能会这么做。因此,所有开发者都应进行相应检查,确保他们的应用不会在运行 Android 7.0 的设备上崩溃。如果您的应用使用原生代码,则只能使用公开 NDK API

通常使用私有 API 的做法是,使用 dlopen 加载 API 所在的动态库,然后通过 dlsym 获取函数地址并调用之。例如如下代码通过不被 NDK 推荐的方式,解析私有函数 JNI_GetCreatedJavaVMs 获取 jvm 实例:

int result;

jsize nVMs = 0;

JavaVM *buffer[1];

void *handle = dlopen("libart.so", RTLD_NOW);

if (!handle) {

    ALOGE(dlerror());

} else {

    GetCreatedJavaVMs JNI_GetCreatedJavaVMs = (GetCreatedJavaVMs)dlsym(handle, "JNI_GetCreatedJavaVMs");

    if (JNI_GetCreatedJavaVMs) {

        result = JNI_GetCreatedJavaVMs(buffer, 1, &nVMs);

        // ..

    }

}

Android N 对此的处理方式是,使用 classloader-namespace 限制了 dlopen 参数允许加载的路径。对于 NDK 公开可用的链接库 (如 libandroid, libc, libcamera2ndk, libdl, libGLES, libjnigraphics, liblog, libm, libmediandk, libOpenMAXAL, libOpenSLES, libstdc++, libvulkan, libz 等),以及 apk 自带的 libs 下的共享对象,可以自由地加载。但尝试 dlopen 其他路径的文件则会返回 null 并在 logcat 中打印类似这样的错误:

03 10 12:06:22.981 14193 14193/com.example.app W/linker: library “libutils.so” (“/system/lib/libutils.so”) needed or dlopened by “/data/app/com.example.app-1/lib/arm/libsqlcipher_android.so” is not accessible for the namespace “classloader-namespace” — the access is temporarily granted as a workaround for http://b/26394120

为了兼容性,在应用的 target API level 小于 24 时,linker 输出一个警告并弹出 toast。

03-21 17:07:51.502 31234 31234 W linker  : library "libart.so"

("/system/lib64/libart.so") needed or dlopened by

"/data/app/com.example.app-1/lib/arm64/libjvmfun.so" is not accessible

for the namespace "classloader-namespace" - the access is temporarily granted

as a workaround for http://b/26394120

如果 target API level 为 Android N 甚至更高版本,那么 dlopen 将返回 null,并可能抛出 java.lang.UnsatisfiedLinkError:


Android 的链接器源码在 platform/bionic/linker/linker.cpp 实现。执行 C 标准库的 dlopen 函数时,在内部函数 load_library 中加入了对链接库合法性的判断:

https://android.googlesource.com/platform/bionic/+/master/linker/linker.cpp#1210


小于 API Level 24 时允许加载的“灰名单”如下:

static const char* const kLibraryGreyList[] = {

    "libandroid_runtime.so",

    "libbinder.so",

    "libcrypto.so",

    "libcutils.so",

    "libexpat.so",

    "libgui.so",

    "libmedia.so",

    "libnativehelper.so",

    "libskia.so",

    "libssl.so",

    "libstagefright.so",

    "libsqlite.so",

    "libui.so",

    "libutils.so",

    "libvorbisidec.so",

    nullptr

  };


上面废话铺垫了这么多,其实绕过的办法巨弱智……只要不传入 NULL 作为 dlopen 的文件名就行了。

typedef jint (JNICALL *GetCreatedJavaVMs)(JavaVM **, jsize, jsize *);

void *handle = dlopen(NULL, RTLD_NOW);

JNI_GetCreatedJavaVMs = (GetCreatedJavaVMs) dlsym(handle, "JNI_GetCreatedJavaVMs");

if (JNI_GetCreatedJavaVMs) {

    jsize nVMs = 0;

    JavaVM *buffer[1];

    int result = JNI_GetCreatedJavaVMs(buffer, 1, &nVMs);

    if (result != JNI_OK) {

        stream << "failed to call JNI_GetCreatedJavaVMs" << endl;

    }

    stream << "jvms: " << nVMs << endl;

    stream << "jvm: " << buffer[0] << endl;

}

局限性是只能加载系统库中的共享对象,而不能指定使用某一个具体的库文件,或者从任意路径加载 shared object

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功