首页
社区
课程
招聘
[原创]改一个参数即可绕过 Android N 的私有 API 链接限制
发表于: 2017-5-26 14:28 14626

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

2017-5-26 14:28
14626

从 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


[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 1
支持
分享
最新回复 (4)
雪    币: 43
活跃值: (388)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
2
其实也简单,搞个内存补丁,或者自定义linker都可以
2017-5-26 16:52
0
雪    币: 312
活跃值: (123)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
感觉这么做意义不大,就是憧憬一下罢了
2017-5-27 03:19
0
雪    币: 2030
活跃值: (1303)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
这种方式在个别手机上不行,无法通用。
2017-7-13 10:12
0
雪    币: 55
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
https://github.com/rrrfff/ndk_dlopen
2018-1-23 16:51
0
游客
登录 | 注册 方可回帖
返回
//