Bypassing the Android Linker Namespace
The Android's linker (bionic) disallows loading most of the shared libraries from /system when a request is going from the executable code belonging to "classloader-namespace". (Source code is updated for Android 11).
Quick Guide
JNI
-> android_create_namespace
-> android_dlopen_ext
Full solution: https://github.com/fadeevab/android-custom-jni-namespace/
Detailed explanation: Android Linker Namespace: Security Flaws
Goal
Recently, I investigated Android Linker Namespace: Security Flaws, and here I would like to show you how to bypass linker namespaces access control.
Starting from Android 8.0 (Project Treble) while trying to access the private system API from a Java application, you may see the following error:
java.lang.UnsatisfiedLinkError: dlopen failed: library "libwifi-system.so" ("/system/lib64/libwifi-system.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
It is a consequence of doing things like:
// From Java code
System.loadLibrary("wifi-system");
or
// From JNI
dlopen("libwifi-system.so", RTLD_NOW);
The Android's linker (bionic
) disallows to load the most of the shared libraries from /system
when the request is carried out from the executable code belonging to "classloader-namespace".
Namespace Inheritance
Zygote process assigns the "classloader-namespace" to a Java application with the help of the libnativeloader.so. System.loadLibrary
loads a shared library into the same "classloader-namespace", preserving the namespace of parent process.
An ordinary application from the market should not use the private system libraries (not included into NDK) in order to be portable among many devices. And the linker namespace mechanism is a nice thing to control undesirable dependencies. But if you are really sure about to use a private system API, see the method below.
Steps
The current example shows how to load libwifi-system.so
: any uncommon system library to which access is most likely denied. The nuance of the linker namespace API is going to be used.
Full example is here: https://github.com/fadeevab/android-custom-jni-namespace/.
-
The first step is to get access to native code from Java application. Create a Java Native Interface (JNI) and declare a native method (e.g.
linkSystemLib
):public final class RoadToSystem { public native int linkSystemLib(); static { System.loadLibrary("roadtosystem"); } }
-
Build a shared library, implementing
linkSystemLib
method:#include "dlext_namespaces.h" JNIEXPORT jint JNICALL Java_com_example_android_jniapi_RoadToSystem_linkSystemLib( JNIEnv *env, jobject obj) { ... }
"dlext_namespaces.h" is copied from Android 8.0 sources, the header provides prototypes of the linker namespace functions, which are being the exported symbols of libdl.so linked into the process.
-
JNI shared library resides in "classloader-namespace" after loading by
System.loadLibrary
call.dlopen()
returnsNULL
for the most of the system libraries.
ANDROID 8: Need to create a namespace with "no rules" (permitted paths will contain /system/lib[64]) and load a system shared library into the one:struct android_namespace_t *ns = android_create_namespace( "trustme", "/system/lib64/", "/system/lib64/", ANDROID_NAMESPACE_TYPE_SHARED | ANDROID_NAMESPACE_TYPE_ISOLATED, "/system/:/data/:/vendor/", NULL);
ANDROID 11:
android_create_namespace
is removed from libdl.so, alsoandroid_get_exported_namespace
is moved to libc.so and it becomes private. The workaround is to findandroid_get_exported_namespace
by an offset from any public symbol and retrieve thevndk
namespace.struct android_namespace_t* (*android_get_exported_namespace)(const char*) = dlsym(RTLD_DEFAULT, "android_get_device_api_level"); if (!android_get_exported_namespace) { LOGV("failed to get android_get_device_api_level\n"); return NULL; } android_get_exported_namespace += 0x650e0; ns = android_get_exported_namespace("vndk");
And the rest of the code looks like the following:
const android_dlextinfo dlextinfo = { .flags = ANDROID_DLEXT_USE_NAMESPACE, .library_namespace = ns, }; // Access granted void *so = android_dlopen_ext("libwifi-system-iface.so", RTLD_LOCAL | RTLD_NOW, &dlextinfo);
android_dlopen_call
returns a valid non-zero pointer in a case of success, whiledlopen
returns NULL trying to load a system library from JNI.