This blog post is about techniques to disable Android runtime restrictions
Introduction
With the release of Android Nougat, Google introduced [1] restriction about native libraries that can be loaded from an Android application. Basically, it prevents developers to link against some internal libraries such as libart.so.
Later on and with the release of Android Pie, they introduced a new restriction on the access to internal Java methods (or fields). Basically, these restrictions are used to prevent developers to access parts of the Android internal framework.
Whereas these limitations aim to be used for compatibility purposes, this article shows how we can take advantage of Android internal to disable them. We briefly explain how these restrictions work and how to disable them from an application without privileges.
The first part deals with the native library loading restriction while the second is about Java internal framework restriction.
dlopen() restrictions
As explained in a blog post [1], Android 7 prevents loading or linking against private libraries such as /system/lib/libart.so. It is mainly used to avoid applications relying on libraries and functions that could change in further updates of Android.
This limitation is implemented by associating a namespace for each module (i.e library or executable) loaded in a process. When a module tries to load a library, the Android loader (/system/bin/linker) checks if the given module is in the right namespace to load the library. If not, the loader throws an error and does not load the library.
In the case of an Android application, JNI libraries are associated with a namespace that is not allowed to use libraries from /system/lib. We can trigger the error by using a JNI function that attempts to open /system/lib64/libart.so:
void Java_re_android_restrictions_MainActivity_openRestrict(...) {
void* art_handle = dlopen("/system/lib64/libart.so", RTLD_NOW);
}
When this function is executed, we can observe the following error on the logcat output:
adb logcat -s "linker:E"
4729 4729 E linker :
library "/system/lib64/libart.so" ("/system/lib64/libart.so") needed or dlopened by
/data/app/re.android.restrictions-yALrH==/lib/arm64/librestrictions-bypass.so
is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="",
default_library_paths="/data/app/re.android.restrictions/...,
permitted_paths="/data:/mnt/expand:/data/data/re.android.restrictions"]
From this error, we can see that our JNI library is associated with the namespace classloader-namespace and is limited to load libraries from the following directories:
/data
/mnt/expand
/data/data/re.android.restrictions
As libart.so does not belong to the allowed directories, the library is not loaded and dlopen() returns a null pointer.
The soinfo structure
All loaded libraries are tied to a structure that contains metadata regarding the context in which the library has been loaded. This structure — named soinfo — registers various information like:
The name of the library (e.g. libc.so)
The path to the library (e.g. /system/lib64/libc.so)
The base address choose by the ASLR (e.g. 0x7f13e09a5000)
The Namespace(s) associated with the library.
...
This structure is defined in linker/linker_soinfo.h as a part of the Android Bionic module. From this structure, we can notice two fields that are related to the Android namespace implementation:
struct soinfo {
...
android_namespace_t* primary_namespace_;
android_namespace_list_t secondary_namespaces_;
...
};
If somehow, the application manages to access and modify these fields, it can add its own namespaces and load libraries from directories that were not allowed.
soinfo and library mapping
As explained in the previous section, all loaded libraries are tied to the soinfo structure. It turns out that such mapping is performed using an std::unordered_map whose the key is a unique id [9] and whose the value is a pointer on the soinfo structure.
This mapping is stored in an exported static variable named g_soinfo_handles_map (see linker/linker_globals.cpp) which is located in linker module (/system/bin/linker).
From an application point of view, it means that the relative virtual address of this variable can be resolved using the ELF dynamic symbol table. The absolute address can then be computed using dl_iterate_phdr() and looking for the base address of /system/bin/linker.
Once the application resolved the absolute virtual address of g_soinfo_handles_map, it can iterate over the std::unordered_map and look for the soinfo associated with its JNI library.
static const std::string JNI_LIBRARY_NAME = "...";
for (auto&& [hdl, info] : *g_soinfo_handles_map) {
const char* name = get_soname(info);
if (name == JNI_LIBRARY_NAME) {
return info;
}
}
return nullptr; // Not found
Now that the library has a pointer on the soinfo structure, it can access to the library namespaces using get_primary_namespace():
android_namespace_t* ns = get_primary_namespace(soinfo_ptr);
On this pointer, the application can call the set_ld_library_paths() and set_isolated() functions to extend the list of allowed directories:
ns->set_ld_library_paths({"/system/lib64", "/sytem/lib"});
ns->set_isolated(false);
Finally, the loader will not complain about loading a library in /system/lib64.
Summary
To disable the library loading restriction, we took advantage of the fact that the implementation is based on exported static variable that can be access in read / write in the memory space of applications.
Key points: | Since Android Nougat, loading libraries can be restricted using namespace.
|
---|
Conclusion
As explained in the documentation about these restrictions, Google does not consider these limitations as security features. Nevertheless having a way to disable them is useful while analyzing applications or developing reverse engineering tools for Android.
We sent the details of these techniques to the Android security team [6], they acknowledged quickly and allow us to make them public.
A PoC with sources is available on Quarkslab Github repository:
References
[1] | (1, 2) https://developer.android.com/about/versions/nougat/android-7.0-changes#ndk |
[2] | Restrictions on non-SDK interfaces |
[3] | Namespace based Dynamic Linking - Isolating Native Library of Application and System of Android (WANG Zhenhua) |
[4] | Android Linker Namespace Security Flaws |
[5] | VNDK Linker Namespace |
[6] | https://issuetracker.google.com/issues/117854171 |
[7] | https://android.googlesource.com/platform/art/+/dcfa89bfc06a6c211bbb64fa81313eaf6454ab67/libdexfile/dex/dex_file.h#136 |
[8] | https://android.googlesource.com/platform/art/+/dcfa89bfc06a6c211bbb64fa81313eaf6454ab67/libdexfile/dex/dex_file.h#424 |
[9] | https://android.googlesource.com/platform/bionic/+/refs/tags/android-9.0.0_r34/linker/linker_soinfo.cpp#753 |